/*
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;

namespace EyeSkills
{
    /// <summary>
    /// Multi sprite container object
    /// </summary>
    [System.Serializable]
    public class MultiSprite
    {
        /// <summary>
        /// The sprite.
        /// </summary>
        public GameObject sprite;
        /// <summary>
        /// Its key.
        /// </summary>
        public string key;
    }

    /// <summary>
    /// Multi fixation.  It is a common use case that a designer wishes to alternate between multiple different fixation objects, and to be able to alter their brightness/distance and other common properties, whilst these changes are preserved between changes of the currently active fixation object.
    /// This class helps achieve this in an extensible way - by offering an interface for developers to simply drop in sprites and configure it in the U.I.
    /// 
    /// </summary>
    public class MultiFixationSprite : MonoBehaviour
    {
        public List<MultiSprite> fixationObjects;

        private MultiSprite visibleObject;

        private Color absoluteColor = new Color(1f, 1f, 1f);
        private Vector3 absolutePosition = Vector3.zero;

        public float alterLuminance(float _v, bool absolute)
        {
            return alterLuminance(visibleObject.sprite, _v, absolute);
        }

        public float AlterDistance(float _d, bool absolute)
        {
            return alterDistance(gameObject, _d, absolute); //We move our sprite's parent container, rather than the sprites themselves.
        }

        /// <summary>
        /// Sets the luminance.  Will ignore desired values smaller than 0 or greater than 1 in a hs*V* model.
        /// </summary>
        /// <returns>The luminance.</returns>
        /// <param name="obj">Object.</param>
        /// <param name="_v">V.</param>
        private float alterLuminance(GameObject obj, float _v, bool absolute)
        {
            float h, s, v;
            Material material = obj.GetComponent<Renderer>().material;
            Color color = material.color;
            Color.RGBToHSV(color, out h, out s, out v);
            float newV;
            newV = (absolute) ? _v : (v + _v);
            if ((newV >= -0.01) && (newV <= 1.1))
            {
                // Float rouding issues
                absoluteColor = Color.HSVToRGB(h, s, newV);
                material.color = absoluteColor;
                return newV;
            }
            else
            {
                // Ignore out of bounds request
                return v;
            }
        }

        private float alterDistance(GameObject obj, float _d, bool absolute)
        {
            Vector3 position = obj.transform.position;
            float distance = position.z;
            float newDistance;
            newDistance = (absolute) ? _d : distance + _d;
            if ((newDistance >= 1) && (newDistance <= 20))
            {
                absolutePosition = new Vector3(obj.transform.position.x, obj.transform.position.y, newDistance);
                obj.transform.position = absolutePosition;
            }

            return obj.transform.position.z;
        }

        private GameObject TransferProperties(GameObject visibleObject)
        {
            visibleObject.GetComponent<Renderer>().material.color = absoluteColor;
            visibleObject.transform.position = absolutePosition;
            visibleObject.SetActive(true);
            Debug.Log("Setting active to true for " + visibleObject);
            return visibleObject;
        }

        private void HideAll()
        {
            foreach (MultiSprite obj in fixationObjects)
            {
                obj.sprite.SetActive(false);
            }
        }

        /// <summary>
        /// Picks a random Sprite.
        /// </summary>
        /// <returns>The random.</returns>
        public MultiSprite PickRandom()
        {
            HideAll();
            // Pick a random object
            MultiSprite nextObject;
            do
            {
                nextObject = fixationObjects[Random.Range(0, fixationObjects.Count)];
            } while (nextObject == visibleObject);

            visibleObject = nextObject;

            // transfer our properties
            TransferProperties(visibleObject.sprite);

            return visibleObject;
        }

        /// <summary>
        /// Picks the index of the by.
        /// </summary>
        /// <returns>The by index.</returns>
        /// <param name="index">Index.</param>
        public MultiSprite PickByIndex(int index)
        {
            HideAll();

            // MultiSprite nextObject;
            if (index <= fixationObjects.Count)
            {
                visibleObject = fixationObjects[index];
                TransferProperties(visibleObject.sprite);
                Debug.Log("Picking by index " + index);
            }

            return visibleObject;
        }

        /// <summary>
        /// Gets the current key for the visible sprite. Visible for reporting what is currently chosen, and speaking its name at an appropriate moment.
        /// </summary>
        /// <returns>The current key.</returns>
        public string GetCurrentKey()
        {
            return visibleObject.key;
        }

        /// <summary>
        /// Gets the current luminosity.
        /// </summary>
        /// <returns>The current luminosity.</returns>
        public float GetCurrentLuminosity()
        {
            Color color = visibleObject.sprite.GetComponent<Renderer>().material.color;
            float h, s, v;
            Color.RGBToHSV(color, out h, out s, out v);
            return v;
        }

        /// <summary>
        /// Gets the current distance.
        /// </summary>
        /// <returns>The current distance.</returns>
        public float GetCurrentDistance()
        {
            return gameObject.transform.position.z;
        }

        /// <summary>
        /// Hide this instance.
        /// </summary>
        public void Hide()
        {
            HideAll();
        }

        /// <summary>
        /// Show this instance.
        /// </summary>
        public void Show()
        {
            PickRandom();
        }

        void Start()
        {
            HideAll();
        }
    }
}