using UnityEngine;
using UnityEngine.UI;

namespace Coffee.UIExtensions
{
    /// <summary>
    /// UIGradient.
    /// </summary>
    [DisallowMultipleComponent]
    [AddComponentMenu("UI/MeshEffectForTextMeshPro/UIGradient", 101)]
    public class UIGradient : BaseMeshEffect
    {
        //################################
        // Constant or Static Members.
        //################################

        /// <summary>
        /// Gradient direction.
        /// </summary>
        public enum Direction
        {
            Horizontal,
            Vertical,
            Angle,
            Diagonal,
        }

        /// <summary>
        /// Gradient space for Text.
        /// </summary>
        public enum GradientStyle
        {
            Rect,
            Fit,
            Split,
        }


        //################################
        // Serialize Members.
        //################################

        [Tooltip("Gradient Direction.")]
        [SerializeField] Direction m_Direction;

        [Tooltip("Color1: Top or Left.")]
        [SerializeField] Color m_Color1 = Color.white;

        [Tooltip("Color2: Bottom or Right.")]
        [SerializeField] Color m_Color2 = Color.white;

        [Tooltip("Color3: For diagonal.")]
        [SerializeField] Color m_Color3 = Color.white;

        [Tooltip("Color4: For diagonal.")]
        [SerializeField] Color m_Color4 = Color.white;

        [Tooltip("Gradient rotation.")]
        [SerializeField][Range(-180, 180)] float m_Rotation;

        [Tooltip("Gradient offset for Horizontal, Vertical or Angle.")]
        [SerializeField][Range(-1, 1)] float m_Offset1;

        [Tooltip("Gradient offset for Diagonal.")]
        [SerializeField][Range(-1, 1)] float m_Offset2;

        [Tooltip("Gradient style for Text.")]
        [SerializeField] GradientStyle m_GradientStyle;

        [Tooltip("Color space to correct color.")]
        [SerializeField] ColorSpace m_ColorSpace = ColorSpace.Uninitialized;

        [Tooltip("Ignore aspect ratio.")]
        [SerializeField] bool m_IgnoreAspectRatio = true;


        //################################
        // Public Members.
        //################################
        public Graphic targetGraphic { get { return base.graphic; } }

        /// <summary>
        /// Gradient Direction.
        /// </summary>
        public Direction direction
        {
            get { return m_Direction; }
            set
            {
                if (m_Direction != value)
                {
                    m_Direction = value;
                    SetVerticesDirty();
                }
            }
        }

        /// <summary>
        /// Color1: Top or Left.
        /// </summary>
        public Color color1
        {
            get { return m_Color1; }
            set
            {
                if (m_Color1 != value)
                {
                    m_Color1 = value;
                    SetVerticesDirty();
                }
            }
        }

        /// <summary>
        /// Color2: Bottom or Right.
        /// </summary>
        public Color color2
        {
            get { return m_Color2; }
            set
            {
                if (m_Color2 != value)
                {
                    m_Color2 = value;
                    SetVerticesDirty();
                }
            }
        }

        /// <summary>
        /// Color3: For diagonal.
        /// </summary>
        public Color color3
        {
            get { return m_Color3; }
            set
            {
                if (m_Color3 != value)
                {
                    m_Color3 = value;
                    SetVerticesDirty();
                }
            }
        }

        /// <summary>
        /// Color4: For diagonal.
        /// </summary>
        public Color color4
        {
            get { return m_Color4; }
            set
            {
                if (m_Color4 != value)
                {
                    m_Color4 = value;
                    SetVerticesDirty();
                }
            }
        }

        /// <summary>
        /// Gradient rotation.
        /// </summary>
        public float rotation
        {
            get
            {
                return m_Direction == Direction.Horizontal ? -90
                        : m_Direction == Direction.Vertical ? 0
                        : m_Rotation;
            }
            set
            {
                if (!Mathf.Approximately(m_Rotation, value))
                {
                    m_Rotation = value;
                    SetVerticesDirty();
                }
            }
        }

        /// <summary>
        /// Gradient offset for Horizontal, Vertical or Angle.
        /// </summary>
        public float offset
        {
            get { return m_Offset1; }
            set
            {
                if (m_Offset1 != value)
                {
                    m_Offset1 = value;
                    SetVerticesDirty();
                }
            }
        }

        /// <summary>
        /// Gradient offset for Diagonal.
        /// </summary>
        public Vector2 offset2
        {
            get { return new Vector2(m_Offset2, m_Offset1); }
            set
            {
                if (m_Offset1 != value.y || m_Offset2 != value.x)
                {
                    m_Offset1 = value.y;
                    m_Offset2 = value.x;
                    SetVerticesDirty();
                }
            }
        }

        /// <summary>
        /// Gradient style for Text.
        /// </summary>
        public GradientStyle gradientStyle
        {
            get { return m_GradientStyle; }
            set
            {
                if (m_GradientStyle != value)
                {
                    m_GradientStyle = value;
                    SetVerticesDirty();
                }
            }
        }

        /// <summary>
        /// Color space to correct color.
        /// </summary>
        public ColorSpace colorSpace
        {
            get { return m_ColorSpace; }
            set
            {
                if (m_ColorSpace != value)
                {
                    m_ColorSpace = value;
                    SetVerticesDirty();
                }
            }
        }

        /// <summary>
        /// Ignore aspect ratio.
        /// </summary>
        public bool ignoreAspectRatio
        {
            get { return m_IgnoreAspectRatio; }
            set
            {
                if (m_IgnoreAspectRatio != value)
                {
                    m_IgnoreAspectRatio = value;
                    SetVerticesDirty();
                }
            }
        }


        /// <summary>
        /// Call used to modify mesh.
        /// </summary>
        public override void ModifyMesh(VertexHelper vh)
        {
            if (!IsActive())
                return;

            // Gradient space.
            Rect rect = default(Rect);
            UIVertex vertex = default(UIVertex);
            if (m_GradientStyle == GradientStyle.Rect)
            {
                // RectTransform.
                rect = graphic.rectTransform.rect;
            }
            else if (m_GradientStyle == GradientStyle.Split)
            {
                // Each characters.
                rect.Set(0, 0, 1, 1);
            }
            else if (m_GradientStyle == GradientStyle.Fit)
            {
                // Fit to contents.
                rect.xMin = rect.yMin = float.MaxValue;
                rect.xMax = rect.yMax = float.MinValue;
                for (int i = 0; i < vh.currentVertCount; i++)
                {
                    vh.PopulateUIVertex(ref vertex, i);
                    rect.xMin = Mathf.Min(rect.xMin, vertex.position.x);
                    rect.yMin = Mathf.Min(rect.yMin, vertex.position.y);
                    rect.xMax = Mathf.Max(rect.xMax, vertex.position.x);
                    rect.yMax = Mathf.Max(rect.yMax, vertex.position.y);
                }
            }

            // Gradient rotation.
            float rad = rotation * Mathf.Deg2Rad;
            Vector2 dir = new Vector2(Mathf.Cos(rad), Mathf.Sin(rad));
            if (!m_IgnoreAspectRatio && Direction.Angle <= m_Direction)
            {
                dir.x *= rect.height / rect.width;
                dir = dir.normalized;
            }

            // Calculate vertex color.
            Color color;
            Vector2 nomalizedPos;
            Matrix2x3 localMatrix = new Matrix2x3(rect, dir.x, dir.y);  // Get local matrix.
            for (int i = 0; i < vh.currentVertCount; i++)
            {
                vh.PopulateUIVertex(ref vertex, i);

                // Normalize vertex position by local matrix.
                if (m_GradientStyle == GradientStyle.Split)
                {
                    // Each characters.
                    nomalizedPos = localMatrix * s_SplitedCharacterPosition[i % 4] + offset2;
                }
                else
                {
                    nomalizedPos = localMatrix * vertex.position + offset2;
                }

                // Interpolate vertex color.
                if (direction == Direction.Diagonal)
                {
                    color = Color.LerpUnclamped(
                        Color.LerpUnclamped(m_Color1, m_Color2, nomalizedPos.x),
                        Color.LerpUnclamped(m_Color3, m_Color4, nomalizedPos.x),
                        nomalizedPos.y);
                }
                else
                {
                    color = Color.LerpUnclamped(m_Color2, m_Color1, nomalizedPos.y);
                }

                // Correct color.
                vertex.color *= (m_ColorSpace == ColorSpace.Gamma) ? color.gamma
                    : (m_ColorSpace == ColorSpace.Linear) ? color.linear
                    : color;

                vh.SetUIVertex(vertex, i);
            }
        }


        //################################
        // Private Members.
        //################################
        static readonly Vector2[] s_SplitedCharacterPosition = { Vector2.up, Vector2.one, Vector2.right, Vector2.zero };

        /// <summary>
        /// Matrix2x3.
        /// </summary>
        struct Matrix2x3
        {
            public float m00, m01, m02, m10, m11, m12;

            public Matrix2x3(Rect rect, float cos, float sin)
            {
                const float center = 0.5f;
                float dx = -rect.xMin / rect.width - center;
                float dy = -rect.yMin / rect.height - center;
                m00 = cos / rect.width;
                m01 = -sin / rect.height;
                m02 = dx * cos - dy * sin + center;
                m10 = sin / rect.width;
                m11 = cos / rect.height;
                m12 = dx * sin + dy * cos + center;
            }

            public static Vector2 operator *(Matrix2x3 m, Vector2 v)
            {
                return new Vector2(
                    (m.m00 * v.x) + (m.m01 * v.y) + m.m02,
                    (m.m10 * v.x) + (m.m11 * v.y) + m.m12
                );
            }
        }
    }
}