namespace UnityEngine.XR.Content.Rendering
{
    /// 
    /// Draws an outline on an object when highlighting. Can either transition the color and or size of the out line
    /// as selected or be instant on.
    /// 
    public class OutlineHighlight : MonoBehaviour, IMaterialHighlight
    {
        enum OutlineSource
        {
            Shader = 0,
            Material
        }
        const float k_OutlineWidth = 0.005f;
        const string k_ShaderColorParameter = "_Color";
        const string k_ShaderWidthParameter = "g_flOutlineWidth";
        static readonly int k_GFlOutlineWidth = Shader.PropertyToID(k_ShaderWidthParameter);
        static readonly int k_Color = Shader.PropertyToID(k_ShaderColorParameter);
#pragma warning disable 649
        [SerializeField]
        [Tooltip("How the highlight material will be applied to the renderer's material array.")]
        MaterialHighlightMode m_HighlightMode = MaterialHighlightMode.Replace;
        [SerializeField]
        [Tooltip("Selects source for the highlight material. Either using a shader or material.")]
        OutlineSource m_OutlineSource = OutlineSource.Shader;
        [SerializeField]
        [Tooltip("Outline highlight shader to use for highlight material.")]
        Shader m_Shader;
        [SerializeField]
        [Tooltip("Material used for drawing the outline highlight.")]
        Material m_HighlightMaterial;
        [SerializeField]
        [Tooltip("Transition outline width over time")]
        bool m_TransitionWidth;
        [SerializeField]
        [Tooltip("The outline width used if no transition or the end value for transition width of outline")]
        [Range(0f, 1f)]
        float m_OutlineScale = 1f;
        [SerializeField]
        [Tooltip("Starting value for transition width of outline")]
        [Range(0f, 1f)]
        float m_StartingOutlineScale;
        [SerializeField]
        [Tooltip("Transition outline color over time")]
        bool m_TransitionColor;
        [SerializeField]
        [Tooltip("The outline color used if no transition or the end value for transition color of outline")]
        Color m_OutlineColor = new Color(0.3f, 0.6f, 1f, 1f);
        [SerializeField]
        [Tooltip("Starting value for transition color of outline")]
        Color m_StartingOutlineColor = Color.black;
        [SerializeField]
        [Tooltip("Time it takes to transition from start to end on highlight")]
        float m_TransitionDuration = 0.3f;
        [SerializeField]
        [Tooltip("Use material values for starting color and width")]
        bool m_StartWithMaterialValues;
#pragma warning restore 649
        Material m_InstanceOutlineMaterial;
        bool m_Animating = false;
        bool m_AnimatingIn = false;
        float m_TransitionTimer = 0.0f;
        /// 
        /// Time it takes to transition from start to end on highlight
        /// 
        public float transitionDuration
        {
            get => m_TransitionDuration;
            set => m_TransitionDuration = value;
        }
        /// 
        /// Transition outline width over time
        /// 
        public bool transitionWidth
        {
            get => m_TransitionWidth;
            set => m_TransitionWidth = value;
        }
        /// 
        /// Transition outline color over time
        /// 
        public bool transitionColor
        {
            get => m_TransitionColor;
            set => m_TransitionColor = value;
        }
        /// 
        /// The outline color used if no transition or the end value for transition color of outline
        /// 
        public Color outlineColor
        {
            get => m_OutlineColor;
            set => m_OutlineColor = value;
        }
        /// 
        /// Starting value for transition color of outline
        /// 
        public Color startingOutlineColor
        {
            get => m_StartingOutlineColor;
            set => m_StartingOutlineColor = value;
        }
        /// 
        /// The outline width used if no transition or the end value for transition width of outline
        /// 
        public float outlineScale
        {
            get => m_OutlineScale;
            set => m_OutlineScale = value;
        }
        /// 
        /// Starting value for transition width of outline
        /// 
        public float startingOutlineScale
        {
            get => m_StartingOutlineScale;
            set => m_StartingOutlineScale = value;
        }
        /// 
        /// A 0-1 relative outline scale that takes into account the ideal base outline width,
        /// multiplied by the user specified value. This allows for more intuitive adjustment of the value.
        /// This is the value used if there is no transition otherwise this is the end value of a transition.
        /// 
        float relativeOutlineScale => outlineScale * k_OutlineWidth;
        /// 
        /// A 0-1 relative outline scale that takes into account the ideal base outline width,
        /// multiplied by the user specified value. This allows for more intuitive adjustment of the value.
        /// This is the start value of a transition otherwise this value is not used.
        /// 
        float startingRelativeOutlineScale => startingOutlineScale * k_OutlineWidth;
        /// 
        /// How the highlight material will be applied to the renderer's material array.
        /// 
        public MaterialHighlightMode highlightMode
        {
            get => m_HighlightMode;
            set => m_HighlightMode = value;
        }
        /// 
        /// Material to use for highlighting
        /// 
        public Material highlightMaterial => m_InstanceOutlineMaterial;
        void IMaterialHighlight.Initialize()
        {
            InstantiateHighlightMaterial();
            if (m_StartWithMaterialValues)
            {
                startingOutlineScale = m_HighlightMaterial.GetFloat(k_GFlOutlineWidth) / k_OutlineWidth;
                startingOutlineColor = m_HighlightMaterial.GetColor(k_Color);
            }
        }
        void IMaterialHighlight.Deinitialize()
        {
            if (m_InstanceOutlineMaterial)
            {
                Destroy(m_InstanceOutlineMaterial);
                m_InstanceOutlineMaterial = null;
            }
        }
        protected void OnDestroy()
        {
            ((IMaterialHighlight)(this)).Deinitialize();
        }
        protected void Update()
        {
            if (m_Animating)
            {
                m_TransitionTimer += Time.unscaledDeltaTime;
                var transitionPercent = Mathf.Clamp01(m_TransitionTimer / m_TransitionDuration);
                var alpha = m_AnimatingIn ? transitionPercent : (1.0f - transitionPercent);
                if (m_TransitionWidth)
                {
                    var size = Mathf.Lerp(startingRelativeOutlineScale, relativeOutlineScale, alpha);
                    m_InstanceOutlineMaterial.SetFloat(k_GFlOutlineWidth, size);
                }
                if (m_TransitionColor)
                {
                    var color = Color.Lerp(startingOutlineColor, outlineColor, alpha);
                    m_InstanceOutlineMaterial.SetColor(k_Color, color);
                }
                if (transitionPercent >= 1.0f)
                {
                    m_TransitionTimer = 0.0f;
                    m_Animating = false;
                }
            }
        }
        void IMaterialHighlight.OnHighlight()
        {
            if (m_InstanceOutlineMaterial == null)
                return;
            PlayPulseAnimation();
        }
        float IMaterialHighlight.OnUnhighlight()
        {
            if (m_InstanceOutlineMaterial == null)
                return 0.0f;
            PlayPulseAnimation(false);
            if (!m_TransitionWidth && !m_TransitionColor || Mathf.Approximately(m_TransitionDuration, 0f))
                return 0.0f;
            else
                return m_TransitionDuration;
        }
        /// 
        /// Pulses the highlight - even if it is already active
        /// 
        /// Whether the highlight is fading in or out
        public void PlayPulseAnimation(bool pulseUp = true)
        {
            if (!m_TransitionWidth && !m_TransitionColor || Mathf.Approximately(m_TransitionDuration, 0f))
            {
                m_InstanceOutlineMaterial.SetFloat(k_GFlOutlineWidth, relativeOutlineScale);
                m_InstanceOutlineMaterial.SetColor(k_Color, m_OutlineColor);
            }
            else
            {
                // If the same animation is already occurring, we just let it play.  If it is playing backwards, we seamlessly transition
                if (m_Animating)
                {
                    if (m_AnimatingIn != pulseUp)
                    {
                        m_TransitionTimer = 1.0f - m_TransitionTimer;
                    }
                }
                else
                {
                    m_Animating = true;
                    m_AnimatingIn = pulseUp;
                    m_TransitionTimer = 0.0f;
                }
            }
        }
        void InstantiateHighlightMaterial()
        {
            if (m_Shader == null && m_HighlightMaterial == null)
            {
                Debug.LogError($"{gameObject.name} has no highlight material or shader set!", this);
                enabled = false;
                return;
            }
            const string outlineMaterialName = "Outline Material Instance";
            switch (m_OutlineSource)
            {
                case OutlineSource.Material:
                    if (m_HighlightMaterial == null)
                    {
                        Debug.LogError($"{gameObject.name} Outline highlight has no material assigned. Please assign outline material.", this);
                        enabled = false;
                        break;
                    }
                    m_InstanceOutlineMaterial = new Material(m_HighlightMaterial) { name = outlineMaterialName };
                    break;
                case OutlineSource.Shader:
                    if (m_Shader == null)
                    {
                        Debug.LogError($"{gameObject.name} Outline highlight has no shader assigned. Please assign outline shader. ", this);
                        enabled = false;
                        break;
                    }
                    m_InstanceOutlineMaterial = new Material(m_Shader) { name = outlineMaterialName };
                    break;
                default:
                    Debug.LogError($"{gameObject.name} Outline highlight has an invalid highlight mode {m_OutlineSource}.", this);
                    enabled = false;
                    break;
            }
        }
        protected void OnValidate()
        {
            if (m_TransitionDuration < 0f)
            {
                m_TransitionDuration = 0f;
            }
        }
    }
}