// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using System.Collections;
using UnityEngine;

namespace HoloToolkit.Unity.InputModule
{
    public class XboxControllerHandlerBase : GamePadHandlerBase, IXboxControllerHandler
    {
        protected enum GestureState
        {
            SelectButtonUnpressed,
            SelectButtonPressed,
            NavigationStarted,
            HoldStarted
        }

        [SerializeField]
        [Tooltip("Elapsed time for hold started gesture in seconds.")]
        protected float HoldStartedInterval = 2.0f;

        [SerializeField]
        [Tooltip("Elapsed time for hold completed gesture in seconds.")]
        protected float HoldCompletedInterval = 3.0f;

        [SerializeField]
        [Tooltip("The action button that is used to select.  Analogous to air tap on HoloLens and trigger press with motion controllers.")]
        protected XboxControllerMappingTypes SelectButton = XboxControllerMappingTypes.XboxA;

        [SerializeField]
        [Tooltip("The Horizontal Axis that navigation events take place")]
        protected XboxControllerMappingTypes HorizontalNavigationAxis = XboxControllerMappingTypes.XboxLeftStickHorizontal;

        [Tooltip("The Vertical Axis that navigation events take place")]
        protected XboxControllerMappingTypes VerticalNavigationAxis = XboxControllerMappingTypes.XboxLeftStickVertical;

        protected GestureState CurrentGestureState = GestureState.SelectButtonUnpressed;

        protected Vector3 NormalizedOffset;

        protected Coroutine HoldStartedRoutine;

        public virtual void OnXboxInputUpdate(XboxControllerEventData eventData)
        {
            if (string.IsNullOrEmpty(GamePadName))
            {
                GamePadName = eventData.GamePadName;
            }

            if (XboxControllerMapping.GetButton_Down(SelectButton, eventData))
            {
                CurrentGestureState = GestureState.SelectButtonPressed;

                InputManager.Instance.RaiseSourceDown(eventData.InputSource, eventData.SourceId, InteractionSourcePressInfo.Select);

                HoldStartedRoutine = StartCoroutine(HandleHoldStarted(eventData));
            }

            if (XboxControllerMapping.GetButton_Pressed(SelectButton, eventData))
            {
                HandleNavigation(eventData);
            }

            if (XboxControllerMapping.GetButton_Up(SelectButton, eventData))
            {
                HandleSelectButtonReleased(eventData);
            }

            // Consume this event
            eventData.Use();
        }

        protected virtual void HandleSelectButtonReleased(XboxControllerEventData eventData)
        {
            InputManager.Instance.RaiseSourceUp(eventData.InputSource, eventData.SourceId, InteractionSourcePressInfo.Select);

            if (HoldStartedRoutine != null)
            {
                StopCoroutine(HoldStartedRoutine);
            }

            switch (CurrentGestureState)
            {
                case GestureState.NavigationStarted:
                    InputManager.Instance.RaiseNavigationCompleted(eventData.InputSource, eventData.SourceId, Vector3.zero);
                    break;
                case GestureState.HoldStarted:
                    InputManager.Instance.RaiseHoldCompleted(eventData.InputSource, eventData.SourceId);
                    break;
                default:
                    InputManager.Instance.RaiseInputClicked(eventData.InputSource, eventData.SourceId, InteractionSourcePressInfo.Select, 1);
                    break;
            }

            CurrentGestureState = GestureState.SelectButtonUnpressed;
        }

        protected virtual IEnumerator HandleHoldStarted(XboxControllerEventData eventData)
        {
            yield return new WaitForSeconds(HoldStartedInterval);

            if (CurrentGestureState == GestureState.HoldStarted || CurrentGestureState == GestureState.NavigationStarted)
            {
                yield break;
            }

            CurrentGestureState = GestureState.HoldStarted;

            InputManager.Instance.RaiseHoldStarted(eventData.InputSource, eventData.SourceId);
        }

        protected virtual void HandleNavigation(XboxControllerEventData eventData)
        {
            float displacementAlongX = XboxControllerMapping.GetAxis(HorizontalNavigationAxis, eventData);
            float displacementAlongY = XboxControllerMapping.GetAxis(VerticalNavigationAxis, eventData);

            if (displacementAlongX == 0.0f && displacementAlongY == 0.0f && CurrentGestureState != GestureState.NavigationStarted) { return; }

            NormalizedOffset.x = displacementAlongX;
            NormalizedOffset.y = displacementAlongY;
            NormalizedOffset.z = 0f;

            if (CurrentGestureState != GestureState.NavigationStarted)
            {
                if (CurrentGestureState == GestureState.HoldStarted)
                {
                    InputManager.Instance.RaiseHoldCanceled(eventData.InputSource, eventData.SourceId);
                }

                CurrentGestureState = GestureState.NavigationStarted;

                // Raise navigation started event.
                InputManager.Instance.RaiseNavigationStarted(eventData.InputSource, eventData.SourceId);
            }
            else
            {
                // Raise navigation updated event.
                InputManager.Instance.RaiseNavigationUpdated(eventData.InputSource, eventData.SourceId, NormalizedOffset);
            }
        }

        [Obsolete("Use XboxControllerMapping.GetButton_Up")]
        protected static bool OnButton_Up(XboxControllerMappingTypes buttonType, XboxControllerEventData eventData)
        {
            return XboxControllerMapping.GetButton_Up(buttonType, eventData);
        }

        [Obsolete("Use XboxControllerMapping.GetButton_Pressed")]
        protected static bool OnButton_Pressed(XboxControllerMappingTypes buttonType, XboxControllerEventData eventData)
        {
            return XboxControllerMapping.GetButton_Pressed(buttonType, eventData);
        }

        [Obsolete("Use XboxControllerMapping.GetButton_Down")]
        protected static bool OnButton_Down(XboxControllerMappingTypes buttonType, XboxControllerEventData eventData)
        {
            return XboxControllerMapping.GetButton_Down(buttonType, eventData);
        }
    }
}
