// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.

using System;
using UnityEngine;
using HoloToolkit.Unity;

namespace HoloToolkit.Unity.Buttons
{
    /// <summary>
    /// Mesh button is a mesh renderer interactable with state data for button state
    /// </summary>
    [RequireComponent(typeof(CompoundButton))]
    public class CompoundButtonMesh : ProfileButtonBase<ButtonMeshProfile>
    {
        const float AnimationSpeedMultiplier = 25f;
        
        [Tooltip("Transform that scale and offset will be applied to.")]
        [DropDownComponent]
        public Transform TargetTransform;
        
        [Tooltip("Mesh renderer button for mesh button.")]
        [DropDownComponent]
        public MeshRenderer Renderer;
        
        /// <summary>
        /// Mesh Button State Data Set
        /// </summary>
        [Serializable]
        public class MeshButtonDatum
        {
            /// <summary>
            /// Constructor for mesh button datum
            /// </summary>
            public MeshButtonDatum(ButtonStateEnum state) { this.ActiveState = state; this.Name = state.ToString(); }

            /// <summary>
            /// Name string for datum entry
            /// </summary>
            public string Name;
            /// <summary>
            /// Button state the datum is active in
            /// </summary>
            public ButtonStateEnum ActiveState;
            /// <summary>
            /// Button mesh color to use in active state
            /// </summary>
            public Color StateColor = Color.white;
            /// <summary>
            /// Button mesh shader property to use in active state
            /// </summary>
            public float StateValue = 0f;
            /// <summary>
            /// Offset to translate mesh to in active state.
            /// </summary>
            public Vector3 Offset;
            /// <summary>
            /// Scale for mesh button in active state
            /// </summary>
            public Vector3 Scale;
        }

        private MeshButtonDatum currentDatum;

        /// <summary>
        /// The material used by button's Renderer after being modified
        /// </summary>
        private Material instantiatedMaterial;

        /// <summary>
        /// The material used by button's Renderer before being modified
        /// </summary>
        private Material sharedMaterial;


        private float lastTimePressed = 0f;

#if UNITY_EDITOR
        /// <summary>
        /// Called by CompoundButtonSaveInterceptor
        /// Prevents saving a scene with instanced materials
        /// </summary>
        public void OnWillSaveScene ()
        {
            if (Renderer != null && instantiatedMaterial != null)
            {
                Renderer.sharedMaterial = sharedMaterial;
                GameObject.DestroyImmediate(instantiatedMaterial);
            }
        }
#endif

        protected void Start ()
        {
            Button button = GetComponent<Button>();
            if (button == null)
            {
                Debug.LogError("No button attached to CompoundButtonMesh in " + name);
                enabled = false;
                return;
            }       

            if (Profile == null)
            {
                Debug.LogError("No profile selected for CompoundButtonMesh in " + name);
                enabled = false;
                return;
            }

            button.StateChange += StateChange;
            // Disable this script if we're not using smooth changes
            enabled = Profile.SmoothStateChanges;
            // Set the current datum so our first state is activated
            currentDatum = Profile.ButtonStates[(int)ButtonStateEnum.Observation];
            UpdateButtonProperties(false);
        }

        /// <summary>
        /// On state change swap out the active mesh based on the state
        /// </summary>
        protected void StateChange(ButtonStateEnum newState)
        {
            if (newState == ButtonStateEnum.Pressed)
            {
                lastTimePressed = Time.time;
            }

            currentDatum = Profile.ButtonStates[(int)newState];
                        
            // If we're not using smooth states, just set them now
            if (!Profile.SmoothStateChanges)
            {
                TargetTransform.localScale = currentDatum.Scale;
                TargetTransform.localPosition = currentDatum.Offset;

                if (Renderer != null)
                {
                    if (instantiatedMaterial == null)
                    {
                        sharedMaterial = Renderer.sharedMaterial;
                        instantiatedMaterial = new Material(sharedMaterial);
                        Renderer.sharedMaterial = instantiatedMaterial;
                    }

                    if (!string.IsNullOrEmpty(Profile.ColorPropertyName))
                    {
                        Renderer.sharedMaterial.SetColor(Profile.ColorPropertyName, currentDatum.StateColor);
                    }
                    if (!string.IsNullOrEmpty(Profile.ValuePropertyName))
                    {
                        Renderer.sharedMaterial.SetFloat(Profile.ValuePropertyName, currentDatum.StateValue);
                    }
                }
            }
        }

        protected void OnDisable()
        {
            StateChange(ButtonStateEnum.Disabled);
            UpdateButtonProperties(false);
        }

        protected void OnEnable() {
            Button button = GetComponent<Button>();
            if (button != null) {
                StateChange(button.ButtonState);
            }
        }

        protected void Update ()
        {
            UpdateButtonProperties(true);
        }

        protected void UpdateButtonProperties(bool smooth)
        {
            if (currentDatum == null)
            {
                return;
            }

            MeshButtonDatum datum = currentDatum;

            // If we're using sticky events, and we're still not past the 'sticky' pressed time, use that datum
            if (Profile.StickyPressedEvents && Time.time < lastTimePressed + Profile.StickyPressedTime)
            {
                datum = Profile.ButtonStates[(int)ButtonStateEnum.Pressed];
            }

            if (TargetTransform != null)
            {
                if (smooth)
                {
                    TargetTransform.localScale = Vector3.Lerp(
                        TargetTransform.localScale, datum.Scale,
                        Time.deltaTime * Profile.AnimationSpeed * AnimationSpeedMultiplier);
                    TargetTransform.localPosition = Vector3.Lerp(
                        TargetTransform.localPosition, datum.Offset,
                        Time.deltaTime * Profile.AnimationSpeed * AnimationSpeedMultiplier);
                } else
                {
                    TargetTransform.localScale = datum.Scale;
                    TargetTransform.localPosition = datum.Offset;
                }
            }

            // Set the color from the datum 
            if (Renderer != null)
            {
                if (instantiatedMaterial == null)
                {
                    sharedMaterial = Renderer.sharedMaterial;
                    instantiatedMaterial = new Material(sharedMaterial);
                    Renderer.sharedMaterial = instantiatedMaterial;
                }

                if (!string.IsNullOrEmpty(Profile.ColorPropertyName))
                {
                    if (smooth)
                    {
                        Renderer.sharedMaterial.SetColor(
                            Profile.ColorPropertyName,
                            Color.Lerp(Renderer.material.GetColor(Profile.ColorPropertyName),
                            datum.StateColor,
                            Time.deltaTime * Profile.AnimationSpeed * AnimationSpeedMultiplier));
                    } else
                    {
                        Renderer.sharedMaterial.SetColor(
                            Profile.ColorPropertyName,
                            datum.StateColor);
                    }
                }
                if (!string.IsNullOrEmpty(Profile.ValuePropertyName))
                {
                    if (smooth)
                    {
                        Renderer.sharedMaterial.SetFloat(
                            Profile.ValuePropertyName,
                            Mathf.Lerp(Renderer.material.GetFloat(Profile.ValuePropertyName),
                            datum.StateValue,
                            Time.deltaTime * Profile.AnimationSpeed * AnimationSpeedMultiplier));
                    } else
                    {
                        Renderer.sharedMaterial.SetFloat(Profile.ValuePropertyName, datum.StateValue);
                    }
                }
            }
        }

#if UNITY_EDITOR
        [UnityEditor.CustomEditor(typeof(CompoundButtonMesh))]
        public class CustomEditor : MRTKEditor { }
#endif
    }
}