// Controller Tooltips|Prefabs|0070
namespace VRTK
{
    using UnityEngine;

    /// <summary>
    /// Event Payload
    /// </summary>
    /// <param name="element">The tooltip element being affected.</param>
    public struct ControllerTooltipsEventArgs
    {
        public VRTK_ControllerTooltips.TooltipButtons element;
    }

    /// <summary>
    /// Event Payload
    /// </summary>
    /// <param name="sender">this object</param>
    /// <param name="e"><see cref="ControllerTooltipsEventArgs"/></param>
    public delegate void ControllerTooltipsEventHandler(object sender, ControllerTooltipsEventArgs e);

    /// <summary>
    /// Adds a collection of Object Tooltips to the Controller providing information to what the controller buttons may do.
    /// </summary>
    /// <remarks>
    /// **Prefab Usage:**
    ///  * Place the `VRTK/Prefabs/ControllerTooltips/ControllerTooltips` prefab as a child of the relevant controller script alias GameObject in the scene hierarchy.
    ///  * If no `Button Transform Settings` are provided in the inspector at Edit time then the button transforms will attempt to be set to the transforms of the current SDK default controller model.
    ///  * If one of the `Button Text Settings` text options are not provided, then the tooltip for that specific button will be hidden.
    ///
    ///   > There are a number of parameters that can be set on the Prefab which are provided by the `VRTK_ControllerTooltips` script which is applied to the prefab.
    /// </remarks>
    /// <example>
    /// `VRTK/Examples/029_Controller_Tooltips` displays two cubes that have an object tooltip added to them along with tooltips that have been added to the controllers.
    /// </example>
    public class VRTK_ControllerTooltips : MonoBehaviour
    {
        public enum TooltipButtons
        {
            None,
            TriggerTooltip,
            GripTooltip,
            TouchpadTooltip,
            TouchpadTwoTooltip,
            ButtonOneTooltip,
            ButtonTwoTooltip,
            StartMenuTooltip
        }

        [Header("Button Text Settings")]

        [Tooltip("The text to display for the trigger button action.")]
        public string triggerText;
        [Tooltip("The text to display for the grip button action.")]
        public string gripText;
        [Tooltip("The text to display for the touchpad action.")]
        public string touchpadText;
        [Tooltip("The text to display for the touchpad two action.")]
        public string touchpadTwoText;
        [Tooltip("The text to display for button one action.")]
        public string buttonOneText;
        [Tooltip("The text to display for button two action.")]
        public string buttonTwoText;
        [Tooltip("The text to display for the start menu action.")]
        public string startMenuText;

        [Header("Tooltip Colour Settings")]

        [Tooltip("The colour to use for the tooltip background container.")]
        public Color tipBackgroundColor = Color.black;
        [Tooltip("The colour to use for the text within the tooltip.")]
        public Color tipTextColor = Color.white;
        [Tooltip("The colour to use for the line between the tooltip and the relevant controller button.")]
        public Color tipLineColor = Color.black;

        [Header("Button Transform Settings")]

        [Tooltip("The transform for the position of the trigger button on the controller.")]
        public Transform trigger;
        [Tooltip("The transform for the position of the grip button on the controller.")]
        public Transform grip;
        [Tooltip("The transform for the position of the touchpad button on the controller.")]
        public Transform touchpad;
        [Tooltip("The transform for the position of the touchpad two button on the controller.")]
        public Transform touchpadTwo;
        [Tooltip("The transform for the position of button one on the controller.")]
        public Transform buttonOne;
        [Tooltip("The transform for the position of button two on the controller.")]
        public Transform buttonTwo;
        [Tooltip("The transform for the position of the start menu on the controller.")]
        public Transform startMenu;

        [Header("Custom Settings")]

        [Tooltip("The controller to read the controller events from. If this is blank then it will attempt to get a controller events script from the same or parent GameObject.")]
        public VRTK_ControllerEvents controllerEvents;
        [Tooltip("The headset controller aware script to use to see if the headset is looking at the controller. If this is blank then it will attempt to get a controller events script from the same or parent GameObject.")]
        public VRTK_HeadsetControllerAware headsetControllerAware;
        [Tooltip("If this is checked then the tooltips will be hidden when the headset is not looking at the controller.")]
        public bool hideWhenNotInView = true;

        [Header("Obsolete Settings")]

        [System.Obsolete("`VRTK_ControllerTooltips.retryInitMaxTries` has been deprecated as tooltip initialisation now uses the `VRTK_TrackedController.ControllerModelAvailable` event.")]
        [ObsoleteInspector]
        public int retryInitMaxTries = 10;
        [System.Obsolete("`VRTK_ControllerTooltips.retryInitCounter` has been deprecated as tooltip initialisation now uses the `VRTK_TrackedController.ControllerModelAvailable` event.")]
        [ObsoleteInspector]
        public float retryInitCounter = 0.1f;

        /// <summary>
        /// Emitted when the controller tooltip is turned on.
        /// </summary>
        public event ControllerTooltipsEventHandler ControllerTooltipOn;
        /// <summary>
        /// Emitted when the controller tooltip is turned off.
        /// </summary>
        public event ControllerTooltipsEventHandler ControllerTooltipOff;

        protected TooltipButtons[] availableButtons = new TooltipButtons[0];
        protected VRTK_ObjectTooltip[] buttonTooltips = new VRTK_ObjectTooltip[0];
        protected bool[] tooltipStates = new bool[0];
        protected bool overallState = true;
        protected VRTK_TrackedController trackedController;

        public virtual void OnControllerTooltipOn(ControllerTooltipsEventArgs e)
        {
            if (ControllerTooltipOn != null)
            {
                ControllerTooltipOn(this, e);
            }
        }

        public virtual void OnControllerTooltipOff(ControllerTooltipsEventArgs e)
        {
            if (ControllerTooltipOff != null)
            {
                ControllerTooltipOff(this, e);
            }
        }

        /// <summary>
        /// The Reset method reinitalises the tooltips on all of the controller elements.
        /// </summary>
        public virtual void ResetTooltip()
        {
            InitialiseTips();
        }

        /// <summary>
        /// The UpdateText method allows the tooltip text on a specific controller element to be updated at runtime.
        /// </summary>
        /// <param name="element">The specific controller element to change the tooltip text on.</param>
        /// <param name="newText">A string containing the text to update the tooltip to display.</param>
        public virtual void UpdateText(TooltipButtons element, string newText)
        {
            switch (element)
            {
                case TooltipButtons.ButtonOneTooltip:
                    buttonOneText = newText;
                    break;
                case TooltipButtons.ButtonTwoTooltip:
                    buttonTwoText = newText;
                    break;
                case TooltipButtons.StartMenuTooltip:
                    startMenuText = newText;
                    break;
                case TooltipButtons.GripTooltip:
                    gripText = newText;
                    break;
                case TooltipButtons.TouchpadTooltip:
                    touchpadText = newText;
                    break;
                case TooltipButtons.TouchpadTwoTooltip:
                    touchpadTwoText = newText;
                    break;
                case TooltipButtons.TriggerTooltip:
                    triggerText = newText;
                    break;
            }
            ResetTooltip();
        }

        /// <summary>
        /// The ToggleTips method will display the controller tooltips if the state is `true` and will hide the controller tooltips if the state is `false`. An optional `element` can be passed to target a specific controller tooltip to toggle otherwise all tooltips are toggled.
        /// </summary>
        /// <param name="state">The state of whether to display or hide the controller tooltips, true will display and false will hide.</param>
        /// <param name="element">The specific element to hide the tooltip on, if it is `TooltipButtons.None` then it will hide all tooltips. Optional parameter defaults to `TooltipButtons.None`</param>
        public virtual void ToggleTips(bool state, TooltipButtons element = TooltipButtons.None)
        {
            if (element == TooltipButtons.None)
            {
                overallState = state;
                for (int i = 1; i < buttonTooltips.Length; i++)
                {
                    if (buttonTooltips[i].displayText.Length > 0)
                    {
                        buttonTooltips[i].gameObject.SetActive(state);
                    }
                }
            }
            else
            {
                if (buttonTooltips[(int)element].displayText.Length > 0)
                {
                    buttonTooltips[(int)element].gameObject.SetActive(state);
                }
            }
            EmitEvent(state, element);
        }

        protected virtual void Awake()
        {
            VRTK_SDKManager.AttemptAddBehaviourToToggleOnLoadedSetupChange(this);
            InitButtonsArray();
        }

        protected virtual void OnEnable()
        {
            controllerEvents = (controllerEvents != null ? controllerEvents : GetComponentInParent<VRTK_ControllerEvents>());
            InitButtonsArray();
            InitListeners();
            ResetTooltip();
        }

        protected virtual void OnDisable()
        {
            if (controllerEvents != null)
            {
                controllerEvents.ControllerEnabled -= DoControllerEnabled;
                controllerEvents.ControllerVisible -= DoControllerVisible;
                controllerEvents.ControllerHidden -= DoControllerInvisible;
                controllerEvents.ControllerModelAvailable -= DoControllerModelAvailable;
            }
            else if (trackedController != null)
            {
                trackedController.ControllerModelAvailable -= TrackedControllerDoControllerModelAvailable;
            }

            if (headsetControllerAware != null)
            {
                headsetControllerAware.ControllerGlanceEnter -= DoGlanceEnterController;
                headsetControllerAware.ControllerGlanceExit -= DoGlanceExitController;
            }
        }

        protected virtual void OnDestroy()
        {
            VRTK_SDKManager.AttemptRemoveBehaviourToToggleOnLoadedSetupChange(this);
        }

        protected virtual void EmitEvent(bool state, TooltipButtons element)
        {
            ControllerTooltipsEventArgs e;
            e.element = element;
            if (state)
            {
                OnControllerTooltipOn(e);
            }
            else
            {
                OnControllerTooltipOff(e);
            }
        }

        protected virtual void InitButtonsArray()
        {
            availableButtons = new TooltipButtons[]
            {
                TooltipButtons.None,
                TooltipButtons.TriggerTooltip,
                TooltipButtons.GripTooltip,
                TooltipButtons.TouchpadTooltip,
                TooltipButtons.TouchpadTwoTooltip,
                TooltipButtons.ButtonOneTooltip,
                TooltipButtons.ButtonTwoTooltip,
                TooltipButtons.StartMenuTooltip
            };

            buttonTooltips = new VRTK_ObjectTooltip[availableButtons.Length];
            tooltipStates = new bool[availableButtons.Length];

            for (int i = 1; i < availableButtons.Length; i++)
            {
                buttonTooltips[i] = transform.Find(availableButtons[i].ToString()).GetComponent<VRTK_ObjectTooltip>();
            }
        }

        protected virtual void InitListeners()
        {
            if (controllerEvents != null)
            {
                controllerEvents.ControllerEnabled += DoControllerEnabled;
                controllerEvents.ControllerVisible += DoControllerVisible;
                controllerEvents.ControllerHidden += DoControllerInvisible;
                controllerEvents.ControllerModelAvailable += DoControllerModelAvailable;
            }
            else
            {
                trackedController = GetComponentInParent<VRTK_TrackedController>();
                if (trackedController != null)
                {
                    trackedController.ControllerModelAvailable += TrackedControllerDoControllerModelAvailable;
                }
            }

            headsetControllerAware = (headsetControllerAware != null ? headsetControllerAware : FindObjectOfType<VRTK_HeadsetControllerAware>());
            if (headsetControllerAware != null)
            {
                headsetControllerAware.ControllerGlanceEnter += DoGlanceEnterController;
                headsetControllerAware.ControllerGlanceExit += DoGlanceExitController;
                ToggleTips(false);
            }
        }

        protected virtual void DoControllerEnabled(object sender, ControllerInteractionEventArgs e)
        {
            if (controllerEvents != null)
            {
                GameObject actualController = VRTK_DeviceFinder.GetActualController(controllerEvents.gameObject);
                if (actualController != null && actualController.activeInHierarchy)
                {
                    ResetTooltip();
                }
            }
        }

        protected virtual void DoControllerVisible(object sender, ControllerInteractionEventArgs e)
        {
            for (int i = 0; i < availableButtons.Length; i++)
            {
                ToggleTips(tooltipStates[i], availableButtons[i]);
            }
        }

        protected virtual void DoControllerInvisible(object sender, ControllerInteractionEventArgs e)
        {
            for (int i = 1; i < buttonTooltips.Length; i++)
            {
                tooltipStates[i] = buttonTooltips[i].gameObject.activeSelf;
            }
            ToggleTips(false);
        }

        protected virtual void DoControllerModelAvailable(object sender, ControllerInteractionEventArgs e)
        {
            ResetTooltip();
        }


        protected virtual void TrackedControllerDoControllerModelAvailable(object sender, VRTKTrackedControllerEventArgs e)
        {
            ResetTooltip();
        }

        protected virtual void DoGlanceEnterController(object sender, HeadsetControllerAwareEventArgs e)
        {
            if (controllerEvents != null && hideWhenNotInView)
            {
                VRTK_ControllerReference checkControllerReference = VRTK_ControllerReference.GetControllerReference(controllerEvents.gameObject);
                if (checkControllerReference == e.controllerReference)
                {
                    ToggleTips(true);
                }
            }
        }

        protected virtual void DoGlanceExitController(object sender, HeadsetControllerAwareEventArgs e)
        {
            if (controllerEvents != null && hideWhenNotInView)
            {
                VRTK_ControllerReference checkControllerReference = VRTK_ControllerReference.GetControllerReference(controllerEvents.gameObject);
                if (checkControllerReference == e.controllerReference)
                {
                    ToggleTips(false);
                }
            }
        }

        protected virtual void InitialiseTips()
        {
            VRTK_ObjectTooltip[] tooltips = GetComponentsInChildren<VRTK_ObjectTooltip>(true);
            for (int i = 0; i < tooltips.Length; i++)
            {
                VRTK_ObjectTooltip tooltip = tooltips[i];
                string tipText = "";
                Transform tipTransform = null;

                switch (tooltip.name.Replace("Tooltip", "").ToLower())
                {
                    case "trigger":
                        tipText = triggerText;
                        tipTransform = GetTransform(trigger, SDK_BaseController.ControllerElements.Trigger);
                        break;
                    case "grip":
                        tipText = gripText;
                        tipTransform = GetTransform(grip, SDK_BaseController.ControllerElements.GripLeft);
                        break;
                    case "touchpad":
                        tipText = touchpadText;
                        tipTransform = GetTransform(touchpad, SDK_BaseController.ControllerElements.Touchpad);
                        break;
                    case "touchpadtwo":
                        tipText = touchpadTwoText;
                        tipTransform = GetTransform(touchpadTwo, SDK_BaseController.ControllerElements.TouchpadTwo);
                        break;
                    case "buttonone":
                        tipText = buttonOneText;
                        tipTransform = GetTransform(buttonOne, SDK_BaseController.ControllerElements.ButtonOne);
                        break;
                    case "buttontwo":
                        tipText = buttonTwoText;
                        tipTransform = GetTransform(buttonTwo, SDK_BaseController.ControllerElements.ButtonTwo);
                        break;
                    case "startmenu":
                        tipText = startMenuText;
                        tipTransform = GetTransform(startMenu, SDK_BaseController.ControllerElements.StartMenu);
                        break;
                }

                tooltip.displayText = tipText;
                tooltip.drawLineTo = tipTransform;

                tooltip.containerColor = tipBackgroundColor;
                tooltip.fontColor = tipTextColor;
                tooltip.lineColor = tipLineColor;

                tooltip.ResetTooltip();

                if (tipTransform == null || tipText.Trim().Length == 0)
                {
                    tooltip.gameObject.SetActive(false);
                }
            }

            if (headsetControllerAware == null || !hideWhenNotInView)
            {
                ToggleTips(overallState);
            }
        }

        protected virtual Transform GetTransform(Transform setTransform, SDK_BaseController.ControllerElements findElement)
        {
            Transform returnTransform = null;
            if (setTransform != null)
            {
                returnTransform = setTransform;
            }
            else if (controllerEvents != null)
            {
                GameObject modelController = VRTK_DeviceFinder.GetModelAliasController(controllerEvents.gameObject);

                if (modelController != null && modelController.activeInHierarchy)
                {
                    SDK_BaseController.ControllerHand controllerHand = VRTK_DeviceFinder.GetControllerHand(controllerEvents.gameObject);
                    string elementPath = VRTK_SDK_Bridge.GetControllerElementPath(findElement, controllerHand, true);
                    returnTransform = (elementPath != null ? modelController.transform.Find(elementPath) : null);
                }
            }

            return returnTransform;
        }
    }
}
