/*
Copyright 2019 Dr. Thomas Benjamin Senior, Michael Zoeller 

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using EyeSkills;

namespace EyeSkills
{

    /// <summary>
    /// Watching for hold long (within a threshold) the headset has remained still (used for selection)
    /// </summary>
    public class EyeSkillsVRHeadsetSelectByStillness : MonoBehaviour
    {
        /// <summary>
        /// The camera reflecting the position of the head.
        /// </summary>
        public GameObject trackedCamera;

        /// <summary>
        /// The degree to which the largest sequential reading within a time window may drift apart before we consider the movement to no longer be still.
        /// </summary>
        public float sequentialSensitivity = 2f;

        /// <summary>
        /// The degree to which the average movement over the measured time window may drift (e.g. the head could move very slowly - within the sequentialSensitivity threshold - quite far away
        /// </summary>
        public float averageSensitivity = 3f;

        /// <summary>
        /// The sampling frequency for reading the headset position in seconds.
        /// </summary>
        public float samplingFrequency = 0.2f;

        /// <summary>
        /// The maximum size of our monitoring queue. The length of time being monitored is maxQueueSize * samplingFrequency.
        /// </summary>
        private int maxQueueSize = 10;

        /// <summary>
        /// The last difference in angle between two readings of direction of gaze.
        /// </summary>
        private Queue<float> lastDifference = new Queue<float>();

        /// <summary>
        /// The initial gaze direction.
        /// </summary>
        private Quaternion lastGazeDirection;

        /// <summary>
        /// The still time.
        /// </summary>
        private float stillTime = 0f;

        private IEnumerator coroutine;

        private float firstTime;

        private bool saidSelecting = false;

        /// <summary>
        /// The initial activation delay in seconds. The time before the selector becomes active.
        /// </summary>
        public float initialActivationDelayInSeconds = 3f;

        /// <summary>
        /// The delay after cancellation.  Gives the user some time to make another deliberate choice to stay still.
        /// </summary>
        public float delayAfterCancellation = 2f;

        private void GetGazeAngle(){
            lastGazeDirection = trackedCamera.transform.localRotation;
        }

        public void Start()
        {
            stillTime = 0f;
            //Hack. Really not happy to be linking this gestures to this practitioner mode.
            //These really ought to be role based permissions (RBAC) style.
            if (PlayerPrefs.GetString("EyeSkills.practitionerMode") !="1"){
                coroutine = TrackStillness();
                StartCoroutine(coroutine);
            }

        }

        /// <summary>
        /// Are the thresholds for stillness exceeded. We check both the indidual and collective thresholds.
        /// </summary>
        /// <returns><c>true</c>, if thresholds exceeded was ared, <c>false</c> otherwise.</returns>
        /// <param name="q">Q.</param>
        private bool AreThresholdsExceeded(Queue<float> q){
            float drift = 0;
            foreach (float i in q){
                if (i > sequentialSensitivity) {
                    //Debug.Log("sequentialSensitivity threshold exceeded " + i);
                    return true;
                }
                drift += i;
            }
            if (drift > averageSensitivity) {
                Debug.Log("averageSensitivity threshold exceeded " + drift);
                return true;
            }
            //Debug.Log("Thresholds not exceeded ");
            return false;
        }

        IEnumerator TrackStillness(){

            //Give the user some time to orient themselves
            yield return new WaitForSeconds(initialActivationDelayInSeconds);

            //Initialise
            GetGazeAngle();
            firstTime = Time.time;

            //Debug.Log("Starting TrackStillness");
            while (true){
            
                //Calculate the difference in readings.
                float lastGazeDifference = Quaternion.Angle(lastGazeDirection, trackedCamera.transform.localRotation);
                lastDifference.Enqueue(lastGazeDifference);
                //Debug.Log("Last Gaze difference was "+ lastGazeDifference);

                //We might also want to detect direction changes here. Add the direction to a similar queue.
                //If we count more than X direction changes across the period, we have a head shake.


                if (lastDifference.Count>maxQueueSize){
                    //Debug.Log("Shrinking Queue");
                    lastDifference.Dequeue(); 
                }

                //Calculate if we have exceeded any of our movement thresholds
                if (AreThresholdsExceeded(lastDifference)){
                    //Reset our stillTime and empty our queue
                    //Debug.Log("Resetting sillTime ");

                    if (stillTime > delayAfterCancellation){
                        AudioManager.instance.Say("cancellingSelection");
                    }

                    stillTime = 0;
                    firstTime = Time.time;
                    saidSelecting = false;
                    lastDifference.Clear();

                } else {

                    if (stillTime.Equals(delayAfterCancellation) && (saidSelecting==false))
                    {
                        AudioManager.instance.Say("selecting");
                        saidSelecting = true;
                    }

                    //Extend our stillTime
                    stillTime = (Time.time - firstTime);
                    //Debug.Log("Extending sillTime to "+stillTime);

                }

                GetGazeAngle();
                yield return new WaitForSeconds(samplingFrequency);
            }
        }

     
        /// <summary>
        /// Returns the amount of ms within which the headset has been still. The consumer can decide what to do with that information.
        /// </summary>
        /// <returns>The time still.</returns>
        public float getTimeStill()
        {
            if (stillTime<=delayAfterCancellation){
                return 0;
            } else {
                return stillTime - delayAfterCancellation;
            }

        }
    }
}