using System;
using UnityEngine;
using YVR.Core.XR;
using YVR.Utilities;

namespace YVR.Core
{
    public class YVRHandManager : MonoBehaviorSingleton<YVRHandManager>
    {
        /// <summary>
        /// Left hand tracking
        /// </summary>
        public YVRHand leftHand { get; private set; }

        /// <summary>
        /// Right hand tracking
        /// </summary>
        public YVRHand rightHand { get; private set; }

        public HandJointLocations leftHandData = new HandJointLocations();
        public HandJointLocations rightHandData = new HandJointLocations();
        [NonSerialized] private HandStatus m_PreLeftHandStatus;
        [NonSerialized] private HandStatus m_PreRightHandStatus;

        /// <summary>
        /// Initialize hand Transform
        /// </summary>
        public void Initialize()
        {
            YVRPlugin.Instance.SetAPPHandEnable(YVRXRSettings.instance.handTrackingSupport !=
                                                HandTrackingSupport.ControllersOnly);

            Application.onBeforeRender += UpdateHandData;

            leftHand = FindHandTracking("TrackingSpace/HandRoot/YVRLeftHand");
            rightHand = FindHandTracking("TrackingSpace/HandRoot/YVRRightHand");
        }

        /// <summary>
        /// This method returns a boolean value indicating whether hand tracking is enabled or not
        /// </summary>
        /// <returns></returns>
        public bool GetHandTrackingEnable() { return YVRPlugin.Instance.GetHandEnable(); }

        /// <summary>
        /// Returns the pinch strength of the specified finger for the given hand type.
        /// </summary>
        /// <param name="handType">hand type </param>
        /// <param name="fingerID">finger type </param>
        /// <returns></returns>
        public float GetFingerPinchStrength(HandType handType, XRHandFingerID fingerID)
        {
            HandJointLocations handJointLocations = handType == HandType.HandLeft ? leftHandData : rightHandData;
            if (handJointLocations.isActive != 0)
            {
                switch (fingerID)
                {
                    case XRHandFingerID.Index:
                        return handJointLocations.aimState.pinchStrengthIndex;
                    case XRHandFingerID.Middle:
                        return handJointLocations.aimState.pinchStrengthMiddle;
                    case XRHandFingerID.Ring:
                        return handJointLocations.aimState.pinchStrengthRing;
                    case XRHandFingerID.Little:
                        return handJointLocations.aimState.pinchStrengthLittle;
                    default:
                        return 0;
                }
            }

            return 0.0f;
        }

        public bool GetPinch(HandType handType, XRHandFingerID fingerID)
        {
            HandJointLocations currentHandData = handType == HandType.HandLeft ? leftHandData : rightHandData;

            switch (fingerID)
            {
                case XRHandFingerID.Index:
                    return (currentHandData.aimState.status & HandStatus.IndexPinching) != 0;
                case XRHandFingerID.Middle:
                    return (currentHandData.aimState.status & HandStatus.MiddlePinching) != 0;
                case XRHandFingerID.Ring:
                    return (currentHandData.aimState.status & HandStatus.RingPinching) != 0;
                case XRHandFingerID.Little:
                    return (currentHandData.aimState.status & HandStatus.PinkPinching) != 0;
                default:
                    return false;
            }
        }

        public bool GetPinchDown(HandType handType, XRHandFingerID fingerID)
        {
            HandStatus lastHandStatus = handType == HandType.HandLeft ? m_PreLeftHandStatus : m_PreRightHandStatus;
            HandJointLocations currentHandData = handType == HandType.HandLeft ? leftHandData : rightHandData;
            switch (fingerID)
            {
                case XRHandFingerID.Index:
                    return (lastHandStatus & HandStatus.IndexPinching) == 0 &&
                           (currentHandData.aimState.status & HandStatus.IndexPinching) != 0;
                case XRHandFingerID.Middle:
                    return (lastHandStatus & HandStatus.MiddlePinching) == 0 &&
                           (currentHandData.aimState.status & HandStatus.MiddlePinching) != 0;
                case XRHandFingerID.Ring:
                    return (lastHandStatus & HandStatus.RingPinching) == 0 &&
                           (currentHandData.aimState.status & HandStatus.RingPinching) != 0;
                case XRHandFingerID.Little:
                    return (lastHandStatus & HandStatus.PinkPinching) == 0 &&
                           (currentHandData.aimState.status & HandStatus.PinkPinching) != 0;
                default:
                    return false;
            }
        }

        public bool GetPinchUp(HandType handType, XRHandFingerID fingerID)
        {
            HandStatus lastHandStatus = handType == HandType.HandLeft ? m_PreLeftHandStatus : m_PreRightHandStatus;
            HandJointLocations currentHandData = handType == HandType.HandLeft ? leftHandData : rightHandData;
            switch (fingerID)
            {
                case XRHandFingerID.Index:
                    return (lastHandStatus & HandStatus.IndexPinching) != 0 &&
                           (currentHandData.aimState.status & HandStatus.IndexPinching) == 0;
                case XRHandFingerID.Middle:
                    return (lastHandStatus & HandStatus.MiddlePinching) != 0 &&
                           (currentHandData.aimState.status & HandStatus.MiddlePinching) == 0;
                case XRHandFingerID.Ring:
                    return (lastHandStatus & HandStatus.RingPinching) != 0 &&
                           (currentHandData.aimState.status & HandStatus.RingPinching) == 0;
                case XRHandFingerID.Little:
                    return (lastHandStatus & HandStatus.PinkPinching) != 0 &&
                           (currentHandData.aimState.status & HandStatus.PinkPinching) == 0;
                default:
                    return false;
            }
        }

        private void UpdateHandData()
        {
            if (YVRXRSettings.instance.handTrackingSupport == HandTrackingSupport.ControllersOnly) return;

            m_PreLeftHandStatus = leftHandData.aimState.status;
            m_PreRightHandStatus = rightHandData.aimState.status;
            YVRPlugin.Instance.GetHandJointLocations(HandType.HandLeft, ref leftHandData);
            YVRPlugin.Instance.GetHandJointLocations(HandType.HandRight, ref rightHandData);

            leftHand?.UpdateHandTransform();
            rightHand?.UpdateHandTransform();
        }

        /// <summary>
        /// This method finds a hand tracking component by its anchor name
        /// </summary>
        /// <param name="anchorName"></param>
        /// <returns></returns>
        private YVRHand FindHandTracking(string anchorName)
        {
            Transform anchor = this.transform.Find(anchorName);
            return anchor?.GetComponent<YVRHand>();
        }

        public ActiveInputDevice GetInputDevice()
        {
            ActiveInputDevice activeInputDevice = ActiveInputDevice.ControllerActive;
            YVRPlugin.Instance.GetCurrentInputDevice(ref activeInputDevice);
            return activeInputDevice;
        }

        private void OnDestroy() { Application.onBeforeRender -= UpdateHandData; }
    }
}