// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
// All additional code examples as shown in the iX article (Hololens / C.Zimmer, C.Geiger)
// will be provided for free under the MIT License. You are welcome to show your support
// by obtaining an iX magazine at the local store or online.

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

namespace HoloToolkit.Unity.InputModule
{
    /// <summary>
    /// The object cursor can switch between different game objects based on its state.
    /// It simply links the game object to set to active with its associated cursor state.
    /// </summary>
    public class InteractibleObjectCursor : Cursor
    {
        /// <summary>
        /// Sprite renderer to change.  If null find one in children
        /// </summary>
        public Transform parentTransform;

        [Serializable]
        public struct ObjectCursorDatum
        {
            public string Name;
            public CursorStateEnum CursorState;
            public GameObject CursorObject;
        }

        // Array of custom cursor states
        [Header("Basic cursor definitions")]
        [SerializeField]
        public ObjectCursorDatum[] CursorStateData;

        [Serializable]
        public struct ObjectCursorDatumInteractibles
        {
            public string Name;
            public CursorStateEnum CursorState;
            public bool considerInteractibleState;
            public HoloInteractibleHandler.InteractionModeBehaviour InteractibleState;
            public GameObject CursorObject;
        }

        // Array of custom cursor states
        [Header("Cursor definitions for interactibles")]
        [SerializeField]
        public ObjectCursorDatumInteractibles[] CursorStateDataInteractibles;

        // The currently activated interaction mode of the focused object
        HoloInteractibleHandler.InteractionModeBehaviour interactionModeFocused;

        // indicates if the currently focused object is interactible
        private bool isInteractible = false;

        // the currently received cursor state
        private CursorStateEnum currentState;

        /// <summary>
        /// On enable look for a sprite renderer on children
        /// </summary>
        protected override void OnEnable()
        {
            if (parentTransform == null)
            {
                parentTransform = transform;
            }

            for (int i = 0; i < parentTransform.childCount; i++)
            {
                parentTransform.GetChild(i).gameObject.SetActive(false);
            }

            base.OnEnable();
        }

        /// <summary>
        /// Override OnCursorState change to set the correct animation
        /// state for the cursor
        /// </summary>
        /// <param name="state"></param>
        public override void OnCursorStateChange(CursorStateEnum state)
        {
            // Send state change to base class
            base.OnCursorStateChange(state);

            // Save the current state for "focused changed" events
            currentState = state;

            // check if the current object is interactible 
            isInteractible = GetInteractibilityFocusedObject();

            if (state != CursorStateEnum.Contextual)
            {
                var interactibleCursor = new ObjectCursorDatumInteractibles();

                // cursor definitions which consider the interactible state of an object have higher priority than general cursor definitions
                if (isInteractible)
                {
                    // get the current interaction mode of the focused object
                    interactionModeFocused = GetInteractionModeFocusedObject();
                    // get the right cursor for the current state
                    interactibleCursor = GetInteractibleCursorForState(state);
                }

                // if no matching interactible cursor has been found or if the object isn't interactible
                // look for a base cursor definition otherwise display the interactible cursor
                if (interactibleCursor.Name == null)
                {
                    ObjectCursorDatum baseCursor = GetBaseCursorForState(state);
                    
                    // if no base cursor was found as well keep the last cursor
                    if (baseCursor.Name == null)
                    {
                        return;
                    }else{
                        // If the new cursor gets activated the previous cursor should be hidden
                        DeactivateActiveCursor();
                        // Enable the new base cursor
                        baseCursor.CursorObject.SetActive(true);
                    }
                }
                else
                {
                    // If the new cursor gets activated the previous cursor should be hidden
                    DeactivateActiveCursor();
                    
                    // Show the new cursor
                    interactibleCursor.CursorObject.SetActive(true);
                }
            }
        }

        /// <summary>
        /// Get the current interaction mode of the focused object
        /// <summary>
        private HoloInteractibleHandler.InteractionModeBehaviour GetInteractionModeFocusedObject()
        {
            if (GazeManager.Instance.HitObject != null)
            {
                if (GazeManager.Instance.HitObject.GetComponent<HoloInteractibleHandler>() != null)
                {
                    interactionModeFocused = GazeManager.Instance.HitObject.GetComponent<HoloInteractibleHandler>().GetCurrentInteractionMode();
                    isInteractible = true;
                }
            }

            return interactionModeFocused;
        }

        private ObjectCursorDatum GetBaseCursorForState(CursorStateEnum state)
        {
            for (int cursorIndex = 0; cursorIndex < CursorStateData.Length; ++cursorIndex)
            {
                ObjectCursorDatum cursor = CursorStateData[cursorIndex];
                if (state == cursor.CursorState)
                {
                    return cursor;
                }
            }

            return new ObjectCursorDatum();
        }

        private ObjectCursorDatumInteractibles GetInteractibleCursorForState(CursorStateEnum state)
        {
            for (int cursorIndex = 0; cursorIndex < CursorStateDataInteractibles.Length; ++cursorIndex)
            {
                ObjectCursorDatumInteractibles cursor = CursorStateDataInteractibles[cursorIndex];
                // if the interactible state of the focused object matches 
                if (cursor.CursorState == state && cursor.InteractibleState == interactionModeFocused && cursor.considerInteractibleState)
                {
                    
                    return cursor;
                }
            }

            return new ObjectCursorDatumInteractibles();
        }

        /// <summary>
        /// Checks if the currently focused object is interactible
        /// <summary>
        private bool GetInteractibilityFocusedObject()
        {
            if (GazeManager.Instance != null)
            {
                if (GazeManager.Instance.HitObject != null)
                {
                    if (GazeManager.Instance.HitObject.GetComponent<HoloInteractibleHandler>() != null)
                    {
                        return true;
                    }
                    else { return false; }
                }
                else { return false; }
            }
            else { return false; }
        }

        private void DeactivateActiveCursor()
        {
            for (int cursorIndex = 0; cursorIndex < CursorStateDataInteractibles.Length; ++cursorIndex)
            {
                ObjectCursorDatumInteractibles cursor = CursorStateDataInteractibles[cursorIndex];
                if (cursor.CursorObject.activeSelf)
                {
                    cursor.CursorObject.SetActive(false);
                    break;
                }
            }

            for (int cursorIndex = 0; cursorIndex < CursorStateData.Length; ++cursorIndex)
            {
                ObjectCursorDatum cursor = CursorStateData[cursorIndex];
                if (cursor.CursorObject.activeSelf)
                {
                    cursor.CursorObject.SetActive(false);
                    break;
                }
            }
        }

        /// <summary>
        /// IMPORTANT: Updates the active cursor for the new object which is in focus now
        /// </summary>
        protected override void OnPointerSpecificFocusChanged(IPointingSource pointer, GameObject previousObject, GameObject newObject)
        {
            base.OnPointerSpecificFocusChanged(pointer, previousObject, newObject);
            OnCursorStateChange(currentState);
        }

    }
}
