using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.XR.Hands;


#if UNITY_EDITOR
using UnityEditor;
#endif

namespace XRMultiplayer
{
    /// <summary>
    /// This class controls the curl of the fingers based on hand tracking or controller tracking.
    /// </summary>
    public class JointBasedHand : MonoBehaviour
    {
        /// <summary>
        /// Controls how the fingers are updated.
        /// Curl allows for a much more lightweight approximation of finger movements.
        /// </summary>
        public bool useCurl
        {
            get => m_UseCurl;
            set => m_UseCurl = value;
        }
        bool m_UseCurl;

        /// <summary>
        /// Controls the level of fidelity for fingers.
        /// </summary>
        /// <remarks>Set from <see cref="XRHandPoseReplicator.SetFidelity(int)"/>.</remarks>
        public int fidelityLevel
        {
            get => m_FidelityLevel;
            set => m_FidelityLevel = value;
        }
        int m_FidelityLevel;

        [Header("Setup Settings")]
        /// <summary>
        /// Specify where the root of the hand is.
        /// </summary>
        [SerializeField, Tooltip("Specify where the root of the hand is.")]
        protected Transform m_HandRoot;

        /// <summary>
        /// Specify the names of the fingers.
        /// </summary>
        [SerializeField, Tooltip("Specify the names of the fingers.")]
        protected string[] m_FingerNames = { "Thumb", "Index", "Middle", "Ring", "Little" };

        /// <summary>
        /// Specify the start index of the finger joints.
        /// </summary>
        [SerializeField, Tooltip("Specify the start index of the finger joints.")]
        protected XRHandJointID[] m_FingerStartJointIds = { XRHandJointID.ThumbMetacarpal, XRHandJointID.IndexMetacarpal, XRHandJointID.MiddleMetacarpal, XRHandJointID.RingMetacarpal, XRHandJointID.LittleMetacarpal };

        [Header("Hand Fidelity Options")]
        /// <summary>
        /// Groups of <see cref="JointToTransformReference"/>.
        /// </summary>
        [Tooltip("Groups of Joint To Transform References. Use Context Menu for auto generation.")]
        public HandFidelityOption[] handFidelityOptions;

        /// <summary>
        /// Sets the Min/Max euler rotation of the fingers.
        /// </summary>
        [SerializeField, Tooltip("Sets the Min/Max euler rotation of the fingers.")]
        protected Vector2 m_MinMaxEulerX = new Vector2(0, 100);


        //3, 4, 5           -- Thumb
        //7, 8, 9, 10       -- Index
        //12, 13, 14, 15    -- Middle
        //17, 18, 19, 20    -- Ring
        //22, 23, 24, 25    -- Little

        /// <inheritdoc/>
        private void Update()
        {
            if (!m_UseCurl) return;

            m_FidelityLevel = Mathf.Clamp(m_FidelityLevel, 0, handFidelityOptions.Length);
            foreach (var joint in handFidelityOptions[m_FidelityLevel].fingerJoints)
            {
                foreach (var finger in joint.jointTransformReferences)
                {
                    Vector3 rot = Vector3.zero;
                    rot.x = Mathf.Lerp(m_MinMaxEulerX.x, m_MinMaxEulerX.y, joint.curlAmount);
                    finger.jointTransform.localRotation = Quaternion.Euler(rot);
                }
            }
        }

        /// <summary>
        /// Controls the Curl level of fingers.
        /// </summary>
        /// <remarks>Called from <see cref="XRHandPoseReplicator.GetNetworkCurl()"/>.</remarks>
        /// <param name="fingerID">ID of the specific finger.</param>
        /// <param name="curlAmount">Amount to curl the finger.</param>
        public void SetCurl(int fingerID, float curlAmount)
        {
            handFidelityOptions[m_FidelityLevel].fingerJoints[fingerID].curlAmount = curlAmount;
        }

#if UNITY_EDITOR
        /// <summary>
        /// Clears the hand references.
        /// </summary>
        public void ClearHandReferences()
        {
            handFidelityOptions = new HandFidelityOption[3];
        }

        /// <summary>
        /// Attempts to find and automatically assign the hand references.
        /// </summary>
        [ContextMenu("Setup Hand References")]
        public void SetupHandReferences()
        {
            try
            {
                handFidelityOptions = new HandFidelityOption[3];

                for (int i = 0; i < handFidelityOptions.Length; i++)
                {
                    handFidelityOptions[i].fingerJoints = new FingerJoints[i <= 1 ? 5 : 3];

                    for (int j = 0; j < handFidelityOptions[i].fingerJoints.Length; j++)
                    {
                        handFidelityOptions[i].fingerJoints[j].fingerName = m_FingerNames[j];
                        handFidelityOptions[i].fingerJoints[j].jointTransformReferences = new List<JointToTransformReference>();

                        int jointDepth = i == 0 ? 4 : 3;
                        if(j == 0) jointDepth -= 1;  // Thumb has 1 less joint than the other fingers

                        int startDepth = i == 0 ? 0 : 1;

                        handFidelityOptions[i].fingerJoints[j].jointTransformReferences = GetFingerJoints(m_FingerNames[j], startDepth, jointDepth, m_FingerStartJointIds[j]);
                    }

                    //Get extra fingers as mittens
                    if(i == 2)
                    {
                        handFidelityOptions[i].fingerJoints[2].jointTransformReferences.AddRange(GetFingerJoints(m_FingerNames[3], 1, 3, m_FingerStartJointIds[3]));
                        handFidelityOptions[i].fingerJoints[2].jointTransformReferences.AddRange(GetFingerJoints(m_FingerNames[4], 1, 3, m_FingerStartJointIds[4]));
                    }
                }
            }
            catch (Exception e)
            {
                Utils.LogError($"Error in FindNetworkHandReferences: {e}");
            }
        }

        List<JointToTransformReference> GetFingerJoints(string fingerName, int startDepth, int jointDepth, XRHandJointID fingerStartJointId)
        {
            try
            {
                List<JointToTransformReference> fingerJoints = new();
                JointToTransformReference currentJoint = new();

                foreach (Transform child in m_HandRoot)
                {
                    if (child.name.Contains(fingerName))
                    {
                        Transform currentChild = child;

                        //Navigate to the starting joint based on the startDepth
                        for(int i = 0; i < startDepth; i++)
                        {
                            currentChild = currentChild.GetChild(0);
                        }

                        // Get all joints in the finger and add them to the list based on the jointDepth
                        for (int i = 0; i < jointDepth; i++)
                        {
                            currentJoint.jointTransform = currentChild;
                            int currentHandJointId = (int)fingerStartJointId + i + startDepth;
                            currentJoint.xrHandJointID = (XRHandJointID)currentHandJointId;
                            fingerJoints.Add(currentJoint);
                            currentChild = currentChild.GetChild(0);
                        }
                    }
                }

                return fingerJoints;
            }
            catch (Exception e)
            {
                Utils.LogError($"Error in GetFingerJoints: {e}");
                return null;
            }
        }

#endif
    }

    [Serializable]
    public struct HandFidelityOption
    {
        public FingerJoints[] fingerJoints;
    }

    [Serializable]
    public struct FingerJoints
    {
        /// <summary>
        /// Finger Name.
        /// </summary>
        public string fingerName;

        /// <summary>
        /// The current curl amount of the finger.
        /// </summary>
        [Range(0.0f, 1.0f)]
        public float curlAmount;

        /// <summary>
        /// <see cref="JointToTransformReference"/> List.
        /// </summary>
        public List<JointToTransformReference> jointTransformReferences;
    }

#if UNITY_EDITOR
    [CustomEditor(typeof(JointBasedHand))]
    public class HandCurlerEditor : Editor
    {
        public override void OnInspectorGUI()
        {
            DrawDefaultInspector();
            JointBasedHand myScript = (JointBasedHand)target;
            if (GUILayout.Button("Setup References"))
            {
                myScript.SetupHandReferences();
            }

            if (GUILayout.Button("Clear Hand References"))
            {
                myScript.ClearHandReferences();
            }
        }
    }
#endif
}
