// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using Microsoft.MixedReality.Toolkit.UI;
using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

namespace Microsoft.MixedReality.Toolkit.Input
{
    /// <summary>
    /// This component handles the speech input events raised form the <see cref="IMixedRealityInputSystem"/>.
    /// </summary>
    [DisallowMultipleComponent]
    [AddComponentMenu("Scripts/MRTK/SDK/SpeechInputHandler")]
    public class SpeechInputHandler : BaseInputHandler, IMixedRealitySpeechHandler
    {
        /// <summary>
        /// The keywords to be recognized and optional keyboard shortcuts.
        /// </summary>
        public KeywordAndResponse[] Keywords => keywords;

        [SerializeField]
        [Tooltip("The keywords to be recognized and optional keyboard shortcuts.")]
        private KeywordAndResponse[] keywords = Array.Empty<KeywordAndResponse>();

        [SerializeField]
        [Tooltip("Keywords are persistent across all scenes.  This Speech Input Handler instance will not be destroyed when loading a new scene.")]
        private bool persistentKeywords = false;

        [SerializeField]
        [Tooltip("Assign SpeechConfirmationTooltip.prefab here to display confirmation label. Optional.")]
        private SpeechConfirmationTooltip speechConfirmationTooltipPrefab = null;

        /// <summary>
        /// Tooltip prefab used to display confirmation label. Optional.
        /// </summary>
        public SpeechConfirmationTooltip SpeechConfirmationTooltipPrefab
        {
            get { return speechConfirmationTooltipPrefab; }
            set { speechConfirmationTooltipPrefab = value; }
        }

        private SpeechConfirmationTooltip speechConfirmationTooltipPrefabInstance = null;

        private readonly Dictionary<string, UnityEvent> responses = new Dictionary<string, UnityEvent>();

        #region MonoBehaviour Implementation

        protected override void Start()
        {
            base.Start();

            if (persistentKeywords)
            {
                Debug.Assert(gameObject.transform.parent == null, "Persistent keyword GameObject must be at the root level of the scene hierarchy.");
                DontDestroyOnLoad(gameObject);
            }

            // Convert the struct array into a dictionary, with the keywords and the methods as the values.
            // This helps easily link the keyword recognized to the UnityEvent to be invoked.
            int keywordCount = keywords.Length;
            for (int index = 0; index < keywordCount; index++)
            {
                KeywordAndResponse keywordAndResponse = keywords[index];
                string keyword = keywordAndResponse.Keyword.ToLower();

                if (responses.ContainsKey(keyword))
                {
                    Debug.LogError($"Duplicate keyword \'{keyword}\' specified in \'{gameObject.name}\'.");
                }
                else
                {
                    responses.Add(keyword, keywordAndResponse.Response);
                }
            }
        }

        #endregion MonoBehaviour Implementation

        #region InputSystemGlobalHandlerListener Implementation

        protected override void RegisterHandlers()
        {
            CoreServices.InputSystem?.RegisterHandler<IMixedRealitySpeechHandler>(this);
        }

        protected override void UnregisterHandlers()
        {
            CoreServices.InputSystem?.UnregisterHandler<IMixedRealitySpeechHandler>(this);
        }

        #endregion InputSystemGlobalHandlerListener Implementation

        #region SpeechInputHandler public methods
        public void AddResponse(string keyword, UnityAction action)
        {
            string lowerKeyword = keyword.ToLower();
            if (!responses.ContainsKey(lowerKeyword))
            {
                responses[lowerKeyword] = new UnityEvent();
            }

            responses[lowerKeyword].AddListener(action);
        }

        public void RemoveResponse(string keyword, UnityAction action)
        {
            string lowerKeyword = keyword.ToLower();
            if (responses.ContainsKey(lowerKeyword))
            {
                responses[lowerKeyword].RemoveListener(action);
            }
        }
        #endregion SpeechInputHandler public methods

        #region IMixedRealitySpeechHandler Implementation

        void IMixedRealitySpeechHandler.OnSpeechKeywordRecognized(SpeechEventData eventData)
        {
            // early return if the script is already heading for destruction
            if (this.IsNull())
            {
                return;
            }

            // Check to make sure the recognized keyword exists in the methods dictionary, then invoke the corresponding method.
            if (enabled && responses.TryGetValue(eventData.Command.Keyword.ToLower(), out UnityEvent keywordResponse))
            {
                keywordResponse.Invoke();
                eventData.Use();

                // Instantiate the Speech Confirmation Tooltip prefab if assigned
                // Ignore "Select" keyword since OS will display the tooltip.
                if (SpeechConfirmationTooltipPrefab != null
                    && speechConfirmationTooltipPrefabInstance == null
                    && !eventData.Command.Keyword.Equals("select", StringComparison.CurrentCultureIgnoreCase))
                {
                    speechConfirmationTooltipPrefabInstance = Instantiate(speechConfirmationTooltipPrefab);

                    // Update the text label with recognized keyword
                    speechConfirmationTooltipPrefabInstance.SetText(eventData.Command.Keyword);

                    // Trigger animation of the Speech Confirmation Tooltip prefab
                    speechConfirmationTooltipPrefabInstance.TriggerConfirmedAnimation();

                    // Tooltip prefab instance will be destroyed on animation complete
                    // by DestroyOnAnimationComplete.cs in the SpeechConfirmationTooltip.prefab
                }
            }
        }
        #endregion  IMixedRealitySpeechHandler Implementation
    }
}
