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

using Microsoft.MixedReality.Toolkit.Input;
using Microsoft.MixedReality.Toolkit.Utilities;
using System.Collections.Generic;
using UnityEngine;

namespace Microsoft.MixedReality.Toolkit.UI
{
    /// <summary>
    /// This class must be instantiated by a script that implements the <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealitySourceStateHandler"/>,
    /// <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputHandler"/> and <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputHandler{T}"/>.
    /// 
    /// ***It must receive EventData arguments from OnInputDown(), OnInputUp(), OnInputChanged() and OnSourceLost().***
    /// 
    /// This class manages the states of input necessary to calculate a proper grab position
    /// The eventData received on inputdown has the point on the target that was hit by the gaze;
    /// the mixedrealitypose - eventdata received on input changed contains the handposition in eventdata.inputdata.position
    /// It also contains useful retrieval functions.
    /// </summary>
    [System.Obsolete("This component is no longer supported", true)]
    public class GazeHandHelper
    {
        #region Private Variables
        private readonly Dictionary<uint, bool> positionAvailableMap = new Dictionary<uint, bool>();
        private readonly Dictionary<uint, IMixedRealityInputSource> handSourceMap = new Dictionary<uint, IMixedRealityInputSource>();
        private readonly Dictionary<uint, Vector3> handStartPositionMap = new Dictionary<uint, Vector3>();
        private readonly Dictionary<uint, Vector3> handPositionMap = new Dictionary<uint, Vector3>();
        private readonly Dictionary<uint, Vector3> gazePointMap = new Dictionary<uint, Vector3>();
        #endregion Private Variables

        #region Public Methods

        /// <summary>
        /// This function must be called from the OnInputDown handler in a script implementing the <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputHandler{T}"/>.
        /// </summary>
        /// <param name="eventData">The InputEventData argument 'eventData' is passed through to GazeHandHelper</param>
        public void AddSource(InputEventData eventData)
        {
            IMixedRealityInputSource source = eventData.InputSource;
            if (source != null && IsInDictionary(source.SourceId) == false && source.Pointers != null && source.Pointers.Length > 0)
            {
                handSourceMap.Add(source.SourceId, source);
                gazePointMap.Add(source.SourceId, source.Pointers[0].Position);
                handStartPositionMap.Add(source.SourceId, Vector3.zero);
                handPositionMap.Add(source.SourceId, Vector3.zero);
                positionAvailableMap.Add(source.SourceId, false);
            }
        }

        /// <summary>
        /// This function must be called from the OnInputUp handler in a script implementing the <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputHandler{T}"/>.
        /// </summary>
        /// <param name="eventData">he InputEventData argument 'eventData' is passed through to GazeHandHelper</param>
        public void RemoveSource(InputEventData eventData)
        {
            uint sourceId = eventData.SourceId;
            handPositionMap.Remove(sourceId);
            handSourceMap.Remove(sourceId);
            gazePointMap.Remove(sourceId);
            handStartPositionMap.Remove(sourceId);
            positionAvailableMap.Remove(sourceId);
        }

        /// <summary>
        /// This function must be called from the OnSourceLost handler in a script implementing the IMixedRealitySourceStateHandler interface.
        /// </summary>
        public void RemoveSource(SourceStateEventData eventData)
        {
            uint sourceId = eventData.SourceId;
            handPositionMap.Remove(sourceId);
            handSourceMap.Remove(sourceId);
            gazePointMap.Remove(sourceId);
            handStartPositionMap.Remove(sourceId);
            positionAvailableMap.Remove(sourceId);
        }

        /// <summary>
        /// This function must be called from the OnInputChanged handler in a script implementing the <see cref="Microsoft.MixedReality.Toolkit.Input.IMixedRealityInputHandler{T}"/>.
        /// </summary>
        public void UpdateSource(InputEventData<MixedRealityPose> eventData)
        {
            uint id = eventData.SourceId;
            Vector3 handPosition = eventData.InputData.Position;

            if (IsInDictionary(id) == true)
            {
                if (handStartPositionMap[id] == Vector3.zero)
                {
                    handStartPositionMap[id] = handPosition;
                    positionAvailableMap[id] = true;
                }

                if (true == TryGetPointerPosition(id, out Vector3 currentGazePoint))
                {
                    handPositionMap[id] = handPosition + (currentGazePoint - gazePointMap[id]);
                }
            }
        }

        /// <summary>
        /// This function returns the number of active hands.
        /// </summary>
        public int GetActiveHandCount()
        {
            int count = 0;
            foreach (uint key in positionAvailableMap.Keys)
            {
                if (positionAvailableMap[key] == true)
                {
                    count++;
                }
            }
            return count;
        }

        /// <summary>
        /// This function gets the average of the positions of all active hands.
        /// </summary>
        /// <returns>Vector3 representing the average position</returns>
        public Vector3 GetHandsCentroid()
        {
            if (true == TryGetHandsCentroid(out Vector3 centroid))
            {
                return centroid;
            }
            return Vector3.zero;
        }

        /// <summary>
        /// This function gets an array of all active hand positions
        /// </summary>
        /// <returns>enumerable of Vector3</returns>
        public IEnumerable<Vector3> GetAllHandPositions()
        {
            foreach (uint key in positionAvailableMap.Keys)
            {
                if (positionAvailableMap[key] == true)
                {
                    yield return handPositionMap[key];
                }
            }
            yield break;
        }

        /// <summary>
        /// This function retrieves the position of the first active hand.
        /// </summary>
        /// <returns>Vector3 representing position</returns>
        public Vector3 GetFirstHand()
        {
            foreach (Vector3 hand in GetAllHandPositions())
                return hand;

            return Vector3.zero;
        }

        /// <summary>
        /// This function retrieves a reference to the Dictionary that maps hand positions to sourceIds.
        /// This return value is NOT filtered for whether the hands are active. User should check first
        /// using GetActiveHandCount().
        /// </summary>
        /// <returns>Dictionary with uint Keys mapping to Vector3 positions</returns>
        public Dictionary<uint, Vector3> GetHandPositionsDictionary()
        {
            return handPositionMap;
        }
        #endregion  Public Methods

        #region Safe TryGet Style Public Methods
        /// <summary>
        /// TryGet style function to return HandPosition of a certain handedness if available.
        /// </summary>
        /// <param name="handedness">asks for left or right hand or either</param>
        /// <param name="position">out value that gets filled with a Vector3 representing position</param>
        /// <returns>true or false- whether the hand existed</returns>
        public bool TryGetHandPosition(Handedness handedness, out Vector3 position)
        {
            foreach (uint key in positionAvailableMap.Keys)
            {
                if (positionAvailableMap[key] == true)
                {
                    if (handSourceMap[key].Pointers != null && handSourceMap[key].Pointers.Length > 0 && handSourceMap[key].Pointers[0].Controller.ControllerHandedness == handedness)
                    {
                        position = handPositionMap[key];
                        return true;
                    }
                }
            }

            position = Vector3.zero;
            return false;
        }

        /// <summary>
        /// TryGet style function to return HandPosition of a certain sourceId if available.
        /// </summary>
        /// <param name="id">asks for the hand position associated with a certain IMixedRealityInputSource id</param>
        /// <param name="handPosition">out value that gets filled with a Vector3 representing position</param>
        /// <returns>true or false- whether the hand existed</returns>
        public bool TryGetHandPosition(uint id, out Vector3 handPosition)
        {
            if (IsInDictionary(id) == true && positionAvailableMap[id] == true)
            {
                handPosition = handPositionMap[id];
                return true;
            }

            handPosition = Vector3.zero;
            return false;
        }

        /// <summary>
        /// TryGet style function to get the average of all active hand positions.
        /// </summary>
        /// <param name="handsCentroid">out value filled with Vector3 representing average of hand positions</param>
        /// <returns>true if there were any active hands; false if there were no active hands</returns>
        public bool TryGetHandsCentroid(out Vector3 handsCentroid)
        {
            if (GetActiveHandCount() > 0)
            {
                Vector3 agg = Vector3.zero;
                int activeCount = 0;
                foreach (uint key in handPositionMap.Keys)
                {
                    if (positionAvailableMap[key] == true)
                    {
                        agg += handPositionMap[key];
                        activeCount++;
                    }
                }

                if (activeCount > 0)
                {
                    handsCentroid = agg / (float)activeCount;
                    return true;
                }
            }

            handsCentroid = Vector3.zero;
            return false;
        }
        #endregion Safe TryGet Style Public Methods

        #region Private Methods
        private bool IsInDictionary(uint id)
        {
            return handSourceMap.ContainsKey(id);
        }
        private bool GetInitialHandPosition(uint id, out Vector3 initialHandPosition)
        {
            if (IsInDictionary(id) == true)
            {
                initialHandPosition = handStartPositionMap[id];
                return true;
            }

            initialHandPosition = Vector3.zero;
            return false;
        }
        private bool GetInitialGazePosition(uint id, out Vector3 initialGazePosition)
        {
            if (IsInDictionary(id) == true)
            {
                initialGazePosition = gazePointMap[id];
                return true;
            }

            initialGazePosition = Vector3.zero;
            return false;
        }
        private bool TryGetPointerPosition(uint id, out Vector3 position)
        {
            IMixedRealityInputSource source = handSourceMap[id];
            if (source != null && source.Pointers != null && source.Pointers.Length > 0)
            {
                position = source.Pointers[0].Position;
                return true;
            }

            position = Vector3.zero;
            return false;
        }
        #endregion Private Methods
    }
}
