using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.UI;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace Coffee.UIExtensions
{
    /// <summary>
    /// UIEffect.
    /// </summary>
    [RequireComponent(typeof(Graphic))]
    [AddComponentMenu("UI/UIEffect/UIShadow", 100)]
    public class UIShadow : BaseMeshEffect, IParameterTexture
#if UNITY_EDITOR
    , ISerializationCallbackReceiver
#endif
    {

        /// <summary>
        /// Additional shadow.
        /// </summary>
        [System.Obsolete]
        [System.Serializable]
        public class AdditionalShadow
        {
            /// <summary>
            /// How far is the blurring shadow from the graphic.
            /// </summary>
            [FormerlySerializedAs("shadowBlur")]
            [Range(0, 1)] public float blur = 0.25f;

            /// <summary>
            /// Shadow effect mode.
            /// </summary>
            [FormerlySerializedAs("shadowMode")]
            public ShadowStyle style = ShadowStyle.Shadow;

            /// <summary>
            /// Color for the shadow effect.
            /// </summary>
            [FormerlySerializedAs("shadowColor")]
            public Color effectColor = Color.black;

            /// <summary>
            /// How far is the shadow from the graphic.
            /// </summary>
            public Vector2 effectDistance = new Vector2(1f, -1f);

            /// <summary>
            /// Should the shadow inherit the alpha from the graphic?
            /// </summary>
            public bool useGraphicAlpha = true;
        }

        //################################
        // Serialize Members.
        //################################
        [Tooltip("How far is the blurring shadow from the graphic.")]
        [FormerlySerializedAs("m_Blur")]
        [SerializeField][Range(0, 1)] float m_BlurFactor = 1;

        [Tooltip("Shadow effect style.")]
        [SerializeField] ShadowStyle m_Style = ShadowStyle.Shadow;

#pragma warning disable 0414
        [HideInInspector]
        [System.Obsolete]
        [SerializeField] List<AdditionalShadow> m_AdditionalShadows = new List<AdditionalShadow>();
#pragma warning restore 0414


        [SerializeField]
        private Color m_EffectColor = new Color(0f, 0f, 0f, 0.5f);

        [SerializeField]
        private Vector2 m_EffectDistance = new Vector2(1f, -1f);

        [SerializeField]
        private bool m_UseGraphicAlpha = true;

        private const float kMaxEffectDistance = 600f;

        public Color effectColor
        {
            get { return m_EffectColor; }
            set
            {
                m_EffectColor = value;
                if (graphic != null)
                    graphic.SetVerticesDirty();
            }
        }

        public Vector2 effectDistance
        {
            get { return m_EffectDistance; }
            set
            {
                if (value.x > kMaxEffectDistance)
                    value.x = kMaxEffectDistance;
                if (value.x < -kMaxEffectDistance)
                    value.x = -kMaxEffectDistance;

                if (value.y > kMaxEffectDistance)
                    value.y = kMaxEffectDistance;
                if (value.y < -kMaxEffectDistance)
                    value.y = -kMaxEffectDistance;

                if (m_EffectDistance == value)
                    return;

                m_EffectDistance = value;

                if (graphic != null)
                    graphic.SetVerticesDirty();
            }
        }

        public bool useGraphicAlpha
        {
            get { return m_UseGraphicAlpha; }
            set
            {
                m_UseGraphicAlpha = value;
                if (graphic != null)
                    graphic.SetVerticesDirty();
            }
        }

        //################################
        // Public Members.
        //################################
        /// <summary>
        /// How far is the blurring shadow from the graphic.
        /// </summary>
        [System.Obsolete("Use blurFactor instead (UnityUpgradable) -> blurFactor")]
        public float blur
        {
            get { return m_BlurFactor; }
            set
            {
                m_BlurFactor = Mathf.Clamp(value, 0, 2);
                _SetDirty();
            }
        }

        /// <summary>
        /// How far is the blurring shadow from the graphic.
        /// </summary>
        public float blurFactor
        {
            get { return m_BlurFactor; }
            set
            {
                m_BlurFactor = Mathf.Clamp(value, 0, 2);
                _SetDirty();
            }
        }

        /// <summary>
        /// Shadow effect style.
        /// </summary>
        public ShadowStyle style
        {
            get { return m_Style; }
            set
            {
                m_Style = value;
                _SetDirty();
            }
        }

        /// <summary>
        /// Gets or sets the parameter index.
        /// </summary>
        public int parameterIndex { get; set; }

        /// <summary>
        /// Gets the parameter texture.
        /// </summary>
        public ParameterTexture ptex { get; private set; }

        int _graphicVertexCount;
        static readonly List<UIShadow> tmpShadows = new List<UIShadow>();

        protected override void OnEnable()
        {
            base.OnEnable();

            _uiEffect = GetComponent<UIEffect>();
            if (_uiEffect)
            {
                ptex = _uiEffect.ptex;
                ptex.Register(this);
            }

#if TMP_PRESENT
			if (isTMPro)
			{
				textMeshPro.onCullStateChanged.AddListener (OnCullStateChanged);
			}
#endif
        }

        protected override void OnDisable()
        {
            base.OnDisable();
            _uiEffect = null;
            if (ptex != null)
            {
                ptex.Unregister(this);
                ptex = null;
            }
        }


#if UNITY_EDITOR
        protected override void OnValidate()
        {
            effectDistance = m_EffectDistance;
            base.OnValidate();
        }
#endif

#if TMP_PRESENT
		protected void OnCullStateChanged (bool state)
		{
			SetVerticesDirty ();
		}

		Vector2 res;
		protected override void LateUpdate ()
		{
			if (res.x != Screen.width || res.y != Screen.height)
			{
				res.x = Screen.width;
				res.y = Screen.height;
				SetVerticesDirty ();
			}
			if (textMeshPro && transform.hasChanged)
			{
				transform.hasChanged = false;
			}
			base.LateUpdate ();
		}
#endif

        /// <summary>
        /// Modifies the mesh.
        /// </summary>
        public override void ModifyMesh(VertexHelper vh)
        {
            if (!isActiveAndEnabled || vh.currentVertCount <= 0 || m_Style == ShadowStyle.None)
            {
                return;
            }

            vh.GetUIVertexStream(s_Verts);

            GetComponents<UIShadow>(tmpShadows);

            foreach (var s in tmpShadows)
            {
                if (s.isActiveAndEnabled)
                {
                    if (s == this)
                    {
                        foreach (var s2 in tmpShadows)
                        {
                            s2._graphicVertexCount = s_Verts.Count;
                        }
                    }
                    break;
                }
            }

            tmpShadows.Clear();

            //================================
            // Append shadow vertices.
            //================================
            {
                _uiEffect = _uiEffect ?? GetComponent<UIEffect>();
                var start = s_Verts.Count - _graphicVertexCount;
                var end = s_Verts.Count;

                if (ptex != null && _uiEffect && _uiEffect.isActiveAndEnabled)
                {
                    ptex.SetData(this, 0, _uiEffect.effectFactor);  // param.x : effect factor
                    ptex.SetData(this, 1, 255); // param.y : color factor
                    ptex.SetData(this, 2, m_BlurFactor);    // param.z : blur factor
                }

                _ApplyShadow(s_Verts, effectColor, ref start, ref end, effectDistance, style, useGraphicAlpha);
            }

            vh.Clear();
            vh.AddUIVertexTriangleStream(s_Verts);

            s_Verts.Clear();
        }

        UIEffect _uiEffect;

        //################################
        // Private Members.
        //################################
        static readonly List<UIVertex> s_Verts = new List<UIVertex>(4096);

        /// <summary>
        /// Append shadow vertices.
        /// * It is similar to Shadow component implementation.
        /// </summary>
        void _ApplyShadow(List<UIVertex> verts, Color color, ref int start, ref int end, Vector2 effectDistance, ShadowStyle style, bool useGraphicAlpha)
        {
            if (style == ShadowStyle.None || color.a <= 0)
                return;

            // Append Shadow.
            _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, effectDistance.x, effectDistance.y, useGraphicAlpha);

            // Append Shadow3.
            if (ShadowStyle.Shadow3 == style)
            {
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, effectDistance.x, 0, useGraphicAlpha);
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, 0, effectDistance.y, useGraphicAlpha);
            }

            // Append Outline.
            else if (ShadowStyle.Outline == style)
            {
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, effectDistance.x, -effectDistance.y, useGraphicAlpha);
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, -effectDistance.x, effectDistance.y, useGraphicAlpha);
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, -effectDistance.x, -effectDistance.y, useGraphicAlpha);
            }

            // Append Outline8.
            else if (ShadowStyle.Outline8 == style)
            {
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, effectDistance.x, -effectDistance.y, useGraphicAlpha);
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, -effectDistance.x, effectDistance.y, useGraphicAlpha);
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, -effectDistance.x, -effectDistance.y, useGraphicAlpha);
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, -effectDistance.x, 0, useGraphicAlpha);
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, 0, -effectDistance.y, useGraphicAlpha);
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, effectDistance.x, 0, useGraphicAlpha);
                _ApplyShadowZeroAlloc(s_Verts, color, ref start, ref end, 0, effectDistance.y, useGraphicAlpha);
            }
        }

        /// <summary>
        /// Append shadow vertices.
        /// * It is similar to Shadow component implementation.
        /// </summary>
        void _ApplyShadowZeroAlloc(List<UIVertex> verts, Color color, ref int start, ref int end, float x, float y, bool useGraphicAlpha)
        {
            // Check list capacity.
            int count = end - start;
            var neededCapacity = verts.Count + count;
            if (verts.Capacity < neededCapacity)
                verts.Capacity *= 2;

            float normalizedIndex = ptex != null && _uiEffect && _uiEffect.isActiveAndEnabled
                ? ptex.GetNormalizedIndex(this)
                : -1;

            // Add 
            UIVertex vt = default(UIVertex);
            for (int i = 0; i < count; i++)
            {
                verts.Add(vt);
            }

            // Move
            for (int i = verts.Count - 1; count <= i; i--)
            {
                verts[i] = verts[i - count];
            }

            // Append shadow vertices to the front of list.
            // * The original vertex is pushed backward.
            for (int i = 0; i < count; ++i)
            {
                vt = verts[i + start + count];

                Vector3 v = vt.position;
                vt.position.Set(v.x + x, v.y + y, v.z);

                Color vertColor = effectColor;
                vertColor.a = useGraphicAlpha ? color.a * vt.color.a / 255 : color.a;
                vt.color = vertColor;


                // Set UIEffect prameters
                if (0 <= normalizedIndex)
                {
                    vt.uv0 = new Vector2(
                        vt.uv0.x,
                        normalizedIndex
                    );
                }

                verts[i] = vt;
            }

            // Update next shadow offset.
            start = end;
            end = verts.Count;
        }

        /// <summary>
        /// Mark the UIEffect as dirty.
        /// </summary>
        void _SetDirty()
        {
            if (graphic)
                graphic.SetVerticesDirty();
        }

#if UNITY_EDITOR
        public void OnBeforeSerialize()
        {
        }

        public void OnAfterDeserialize()
        {
            EditorApplication.delayCall += UpgradeIfNeeded;
        }


#pragma warning disable 0612
        void UpgradeIfNeeded()
        {
            if (0 < m_AdditionalShadows.Count)
            {
                foreach (var s in m_AdditionalShadows)
                {
                    if (s.style == ShadowStyle.None)
                    {
                        continue;
                    }

                    var shadow = gameObject.AddComponent<UIShadow>();
                    shadow.style = s.style;
                    shadow.effectDistance = s.effectDistance;
                    shadow.effectColor = s.effectColor;
                    shadow.useGraphicAlpha = s.useGraphicAlpha;
                    shadow.blurFactor = s.blur;
                }
                m_AdditionalShadows = null;
            }
        }
#pragma warning restore 0612
#endif
    }
}