using System.Collections.Generic;
using UnityEngine;

namespace XCharts.Runtime
{
    [System.Serializable]
    public class VisualMapRange : ChildComponent
    {
        [SerializeField] private double m_Min;
        [SerializeField] private double m_Max;
        [SerializeField] private string m_Label;
        [SerializeField] private Color32 m_Color;

        /// <summary>
        /// 范围最小值
        /// </summary>
        public double min { get { return m_Min; } set { m_Min = value; } }
        /// <summary>
        /// 范围最大值
        /// </summary>
        public double max { get { return m_Max; } set { m_Max = value; } }
        /// <summary>
        /// 文字描述
        /// </summary>
        public string label { get { return m_Label; } set { m_Label = value; } }
        /// <summary>
        /// 颜色
        /// </summary>
        public Color32 color { get { return m_Color; } set { m_Color = value; } }

        public bool Contains(double value, double minMaxRange)
        {
            if (m_Min == 0 && m_Max == 0) return false;
            var cmin = System.Math.Abs(m_Min) < 1 ? minMaxRange * m_Min : m_Min;
            var cmax = System.Math.Abs(m_Max) < 1 ? minMaxRange * m_Max : m_Max;
            return value >= cmin && value < cmax;
        }
    }

    /// <summary>
    /// VisualMap component. Mapping data to visual elements such as colors.
    /// |视觉映射组件。用于进行『视觉编码』,也就是将数据映射到视觉元素(视觉通道)。
    /// </summary>
    [System.Serializable]
    [ComponentHandler(typeof(VisualMapHandler), true)]
    public class VisualMap : MainComponent
    {
        /// <summary>
        /// 类型。分为连续型和分段型。
        /// </summary>
        public enum Type
        {
            /// <summary>
            /// 连续型。
            /// </summary>
            Continuous,
            /// <summary>
            /// 分段型。
            /// </summary>
            Piecewise
        }

        /// <summary>
        /// 选择模式
        /// </summary>
        public enum SelectedMode
        {
            /// <summary>
            /// 多选。
            /// </summary>
            Multiple,
            /// <summary>
            /// 单选。
            /// </summary>
            Single
        }

        [SerializeField] private bool m_Show = true;
        [SerializeField] private bool m_ShowUI = false;
        [SerializeField] private Type m_Type = Type.Continuous;
        [SerializeField] private SelectedMode m_SelectedMode = SelectedMode.Multiple;
        [SerializeField] private int m_SerieIndex = 0;
        [SerializeField] private double m_Min = 0;
        [SerializeField] private double m_Max = 100;

        [SerializeField] private double[] m_Range = new double[2] { 0, 100 };
        [SerializeField] private string[] m_Text = new string[2] { "", "" };
        [SerializeField] private float[] m_TextGap = new float[2] { 10f, 10f };
        [SerializeField] private int m_SplitNumber = 5;
        [SerializeField] private bool m_Calculable = false;
        [SerializeField] private bool m_Realtime = true;
        [SerializeField] private float m_ItemWidth = 20f;
        [SerializeField] private float m_ItemHeight = 140f;
        [SerializeField] private float m_ItemGap = 10f;
        [SerializeField] private float m_BorderWidth = 0;
        [SerializeField] private int m_Dimension = -1;
        [SerializeField] private bool m_HoverLink = true;
        [SerializeField] private bool m_AutoMinMax = true;
        [SerializeField] private Orient m_Orient = Orient.Horizonal;
        [SerializeField] private Location m_Location = Location.defaultLeft;
        [SerializeField] private bool m_WorkOnLine = true;
        [SerializeField] private bool m_WorkOnArea = false;

        [SerializeField] private List<VisualMapRange> m_OutOfRange = new List<VisualMapRange>() { new VisualMapRange() { color = Color.gray } };
        [SerializeField] private List<VisualMapRange> m_InRange = new List<VisualMapRange>();

        public VisualMapContext context = new VisualMapContext();

        /// <summary>
        /// Whether to enable components. 
        /// |组件是否生效。
        /// </summary>
        public bool show
        {
            get { return m_Show; }
            set { if (PropertyUtil.SetStruct(ref m_Show, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// Whether to display components. If set to false, it will not show up, but the data mapping function still exists.
        /// |是否显示组件。如果设置为 false,不会显示,但是数据映射的功能还存在。
        /// </summary>
        public bool showUI
        {
            get { return m_ShowUI; }
            set { if (PropertyUtil.SetStruct(ref m_ShowUI, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// the type of visualmap component.
        /// |组件类型。
        /// </summary>
        public Type type
        {
            get { return m_Type; }
            set { if (PropertyUtil.SetStruct(ref m_Type, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// the selected mode for Piecewise visualMap.
        /// |选择模式。
        /// </summary>
        public SelectedMode selectedMode
        {
            get { return m_SelectedMode; }
            set { if (PropertyUtil.SetStruct(ref m_SelectedMode, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// the serie index of visualMap.
        /// |影响的serie索引。
        /// </summary>
        public int serieIndex
        {
            get { return m_SerieIndex; }
            set { if (PropertyUtil.SetStruct(ref m_SerieIndex, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// The minimum allowed. 'min' must be user specified. [visualmap.min, visualmap.max] forms the "domain" of the visualMap.
        /// |
        /// 允许的最小值。`autoMinMax`为`false`时必须指定。[visualMap.min, visualMap.max] 形成了视觉映射的『定义域』。
        /// </summary>
        public double min
        {
            get { return m_Min; }
            set { if (PropertyUtil.SetStruct(ref m_Min, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// The maximum allowed. 'max' must be user specified. [visualmap.min, visualmap.max] forms the "domain" of the visualMap.
        /// |
        /// 允许的最大值。`autoMinMax`为`false`时必须指定。[visualMap.min, visualMax.max] 形成了视觉映射的『定义域』。
        /// </summary>
        public double max
        {
            get { return m_Max; }
            set { m_Max = (value < min ? min + 1 : value); SetVerticesDirty(); }
        }
        /// <summary>
        /// Specifies the position of the numeric value corresponding to the handle. Range should be within the range of [min,max].
        /// |
        /// 指定手柄对应数值的位置。range 应在[min,max]范围内。
        /// </summary>
        public double[] range { get { return m_Range; } }
        /// <summary>
        /// Text on both ends.
        /// |两端的文本,如 ['High', 'Low']。
        /// </summary>
        public string[] text { get { return m_Text; } }
        /// <summary>
        /// The distance between the two text bodies.
        /// |两端文字主体之间的距离,单位为px。
        /// </summary>
        public float[] textGap { get { return m_TextGap; } }
        /// <summary>
        /// For continuous data, it is automatically evenly divided into several segments 
        /// and automatically matches the size of inRange color list when the default is 0.
        /// |
        /// 对于连续型数据,自动平均切分成几段,默认为0时自动匹配inRange颜色列表大小。
        /// </summary>
        /// <value></value>
        public int splitNumber
        {
            get { return m_SplitNumber; }
            set { if (PropertyUtil.SetStruct(ref m_SplitNumber, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// Whether the handle used for dragging is displayed (the handle can be dragged to adjust the selected range).
        /// |
        /// 是否显示拖拽用的手柄(手柄能拖拽调整选中范围)。
        /// </summary>
        public bool calculable
        {
            get { return m_Calculable; }
            set { if (PropertyUtil.SetStruct(ref m_Calculable, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// Whether to update in real time while dragging.
        /// |
        /// 拖拽时,是否实时更新。
        /// </summary>
        public bool realtime
        {
            get { return m_Realtime; }
            set { if (PropertyUtil.SetStruct(ref m_Realtime, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// The width of the figure, that is, the width of the color bar.
        /// |
        /// 图形的宽度,即颜色条的宽度。
        /// </summary>
        public float itemWidth
        {
            get { return m_ItemWidth; }
            set { if (PropertyUtil.SetStruct(ref m_ItemWidth, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// The height of the figure, that is, the height of the color bar.
        /// |
        /// 图形的高度,即颜色条的高度。
        /// </summary>
        public float itemHeight
        {
            get { return m_ItemHeight; }
            set { if (PropertyUtil.SetStruct(ref m_ItemHeight, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// 每个图元之间的间隔距离。
        /// </summary>
        public float itemGap
        {
            get { return m_ItemGap; }
            set { if (PropertyUtil.SetStruct(ref m_ItemGap, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// Border line width.
        /// |
        /// 边框线宽,单位px。
        /// </summary>
        public float borderWidth
        {
            get { return m_BorderWidth; }
            set { if (PropertyUtil.SetStruct(ref m_BorderWidth, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// Specifies "which dimension" of the data to map to the visual element. "Data" is series.data.
        /// |Starting at 1, the default is 0 to take the last dimension in data.
        /// |
        /// 指定用数据的『哪个维度』,映射到视觉元素上。『数据』即 series.data。从1开始,默认为0取 data 中最后一个维度。
        /// </summary>
        public int dimension
        {
            get { return m_Dimension; }
            set { if (PropertyUtil.SetStruct(ref m_Dimension, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// When the hoverLink function is turned on, when the mouse hovers over the visualMap component, 
        /// the corresponding value of the mouse position is highlighted in the corresponding graphic element in the diagram.
        /// |Conversely, when the mouse hovers over a graphic element in a diagram, 
        /// the corresponding value of the visualMap component is triangulated in the corresponding position.
        /// |
        /// 打开 hoverLink 功能时,鼠标悬浮到 visualMap 组件上时,鼠标位置对应的数值 在 图表中对应的图形元素,会高亮。
        /// 反之,鼠标悬浮到图表中的图形元素上时,在 visualMap 组件的相应位置会有三角提示其所对应的数值。
        /// </summary>
        /// <value></value>
        public bool hoverLink
        {
            get { return m_HoverLink; }
            set { if (PropertyUtil.SetStruct(ref m_HoverLink, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// Automatically set min, Max value
        /// 自动设置min,max的值
        /// </summary>
        public bool autoMinMax
        {
            get { return m_AutoMinMax; }
            set { if (PropertyUtil.SetStruct(ref m_AutoMinMax, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// Specify whether the layout of component is horizontal or vertical.
        /// |
        /// 布局方式是横还是竖。
        /// </summary>
        public Orient orient
        {
            get { return m_Orient; }
            set { if (PropertyUtil.SetStruct(ref m_Orient, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// The location of component.
        /// |组件显示的位置。
        /// </summary>
        public Location location
        {
            get { return m_Location; }
            set { if (PropertyUtil.SetClass(ref m_Location, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// Whether the visualmap is work on linestyle of linechart.
        /// |组件是否对LineChart的LineStyle有效。
        /// </summary>
        public bool workOnLine
        {
            get { return m_WorkOnLine; }
            set { if (PropertyUtil.SetStruct(ref m_WorkOnLine, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// Whether the visualmap is work on areaStyle of linechart.
        /// |组件是否对LineChart的AreaStyle有效。
        /// </summary>
        public bool workOnArea
        {
            get { return m_WorkOnArea; }
            set { if (PropertyUtil.SetStruct(ref m_WorkOnArea, value)) SetVerticesDirty(); }
        }
        /// <summary>
        /// Defines a visual color outside of the selected range.
        /// |定义 在选中范围外 的视觉颜色。
        /// </summary>
        public List<VisualMapRange> outOfRange
        {
            get { return m_OutOfRange; }
            set { if (value != null) { m_OutOfRange = value; SetVerticesDirty(); } }
        }
        /// <summary>
        /// 分段式每一段的相关配置。
        /// </summary>
        public List<VisualMapRange> inRange
        {
            get { return m_InRange; }
            set { if (value != null) { m_InRange = value; SetVerticesDirty(); } }
        }

        public override bool vertsDirty { get { return m_VertsDirty || location.anyDirty; } }
        public override void ClearVerticesDirty()
        {
            base.ClearVerticesDirty();
            location.ClearVerticesDirty();
        }

        public override void ClearComponentDirty()
        {
            base.ClearComponentDirty();
            location.ClearComponentDirty();
        }

        public double rangeMin
        {
            get
            {
                if (m_Range[0] < min || m_Range[0] > max) return min;
                else return m_Range[0];
            }
            set
            {
                if (value >= min && value <= m_Range[1]) m_Range[0] = value;
            }
        }

        public double rangeMax
        {
            get
            {
                if (m_Range[1] >= m_Range[0] && m_Range[1] < max) return m_Range[1];
                else return max;
            }
            set
            {
                if (value >= m_Range[0] && value <= max) m_Range[1] = value;
            }
        }

        public float runtimeRangeMinHeight { get { return (float) ((rangeMin - min) / (max - min) * itemHeight); } }
        public float runtimeRangeMaxHeight { get { return (float) ((rangeMax - min) / (max - min) * itemHeight); } }

        public void AddColors(List<Color32> colors)
        {
            m_InRange.Clear();
            foreach (var color in colors)
            {
                m_InRange.Add(new VisualMapRange()
                {
                    color = color
                });
            }
        }

        public void AddColors(List<string> colors)
        {
            m_InRange.Clear();
            foreach (var str in colors)
            {
                m_InRange.Add(new VisualMapRange()
                {
                    color = ThemeStyle.GetColor(str)
                });
            }
        }

        public Color32 GetColor(double value)
        {
            int index = GetIndex(value);
            if (index == -1)
            {
                if (m_OutOfRange.Count > 0)
                    return m_OutOfRange[0].color;
                else
                    return ChartConst.clearColor32;
            }

            if (m_Type == VisualMap.Type.Piecewise)
            {
                return m_InRange[index].color;
            }
            else
            {
                int splitNumber = m_InRange.Count;
                var diff = (m_Max - m_Min) / (splitNumber - 1);
                var nowMin = m_Min + index * diff;
                var rate = (value - nowMin) / diff;
                if (index == splitNumber - 1)
                    return m_InRange[index].color;
                else
                    return Color32.Lerp(m_InRange[index].color, m_InRange[index + 1].color, (float) rate);
            }
        }

        private bool IsNeedPieceColor(double value, out int index)
        {
            bool flag = false;
            index = -1;
            for (int i = 0; i < m_InRange.Count; i++)
            {
                var range = m_InRange[i];
                if (range.min != 0 || range.max != 0)
                {
                    flag = true;
                    if (range.Contains(value, max - min))
                    {
                        index = i;
                        return true;
                    }
                }
            }
            return flag;
        }

        private Color32 GetPiecesColor(double value)
        {
            foreach (var piece in m_InRange)
            {
                if (piece.Contains(value, max - min))
                {
                    return piece.color;
                }
            }
            if (m_OutOfRange.Count > 0)
                return m_OutOfRange[0].color;
            else
                return ChartConst.clearColor32;
        }

        public int GetIndex(double value)
        {
            int splitNumber = m_InRange.Count;
            if (splitNumber <= 0)
                return -1;
            var index = -1;
            if (IsNeedPieceColor(value, out index))
            {
                return index;
            }
            value = MathUtil.Clamp(value, m_Min, m_Max);

            var diff = (m_Max - m_Min) / (splitNumber - 1);

            for (int i = 0; i < splitNumber; i++)
            {
                if (value <= m_Min + (i + 1) * diff)
                {
                    index = i;
                    break;
                }
            }
            return index;
        }

        public bool IsPiecewise()
        {
            return m_Type == VisualMap.Type.Piecewise;
        }

        public bool IsInSelectedValue(double value)
        {
            if (context.pointerIndex < 0)
                return true;
            else
                return context.pointerIndex == GetIndex(value);
        }

        public double GetValue(Vector3 pos, Rect chartRect)
        {
            var vertical = orient == Orient.Vertical;
            var centerPos = new Vector3(chartRect.x, chartRect.y) + location.GetPosition(chartRect.width, chartRect.height);
            var pos1 = centerPos + (vertical ? Vector3.down : Vector3.left) * itemHeight / 2;
            var pos2 = centerPos + (vertical ? Vector3.up : Vector3.right) * itemHeight / 2;

            if (vertical)
            {
                if (pos.y < pos1.y)
                    return min;
                else if (pos.y > pos2.y)
                    return max;
                else
                    return min + (pos.y - pos1.y) / (pos2.y - pos1.y) * (max - min);
            }
            else
            {
                if (pos.x < pos1.x)
                    return min;
                else if (pos.x > pos2.x)
                    return max;
                else
                    return min + (pos.x - pos1.x) / (pos2.x - pos1.x) * (max - min);
            }
        }

        public bool IsInRect(Vector3 local, Rect chartRect, float triangleLen = 20)
        {
            var centerPos = new Vector3(chartRect.x, chartRect.y) + location.GetPosition(chartRect.width, chartRect.height);
            var diff = calculable ? triangleLen : 0;

            if (local.x >= centerPos.x - itemWidth / 2 - diff &&
                local.x <= centerPos.x + itemWidth / 2 + diff &&
                local.y >= centerPos.y - itemHeight / 2 - diff &&
                local.y <= centerPos.y + itemHeight / 2 + diff)
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        public bool IsInRangeRect(Vector3 local, Rect chartRect)
        {
            var centerPos = new Vector3(chartRect.x, chartRect.y) + location.GetPosition(chartRect.width, chartRect.height);

            if (orient == Orient.Vertical)
            {
                var pos1 = centerPos + Vector3.down * itemHeight / 2;

                return local.x >= centerPos.x - itemWidth / 2 &&
                    local.x <= centerPos.x + itemWidth / 2 &&
                    local.y >= pos1.y + runtimeRangeMinHeight &&
                    local.y <= pos1.y + runtimeRangeMaxHeight;
            }
            else
            {
                var pos1 = centerPos + Vector3.left * itemHeight / 2;
                return local.x >= pos1.x + runtimeRangeMinHeight &&
                    local.x <= pos1.x + runtimeRangeMaxHeight &&
                    local.y >= centerPos.y - itemWidth / 2 &&
                    local.y <= centerPos.y + itemWidth / 2;
            }
        }

        public bool IsInRangeMinRect(Vector3 local, Rect chartRect, float triangleLen)
        {
            var centerPos = new Vector3(chartRect.x, chartRect.y) + location.GetPosition(chartRect.width, chartRect.height);

            if (orient == Orient.Vertical)
            {
                var radius = triangleLen / 2;
                var pos1 = centerPos + Vector3.down * itemHeight / 2;
                var cpos = new Vector3(pos1.x + itemWidth / 2 + radius, pos1.y + runtimeRangeMinHeight - radius);

                return local.x >= cpos.x - radius &&
                    local.x <= cpos.x + radius &&
                    local.y >= cpos.y - radius &&
                    local.y <= cpos.y + radius;
            }
            else
            {
                var radius = triangleLen / 2;
                var pos1 = centerPos + Vector3.left * itemHeight / 2;
                var cpos = new Vector3(pos1.x + runtimeRangeMinHeight, pos1.y + itemWidth / 2 + radius);

                return local.x >= cpos.x - radius &&
                    local.x <= cpos.x + radius &&
                    local.y >= cpos.y - radius &&
                    local.y <= cpos.y + radius;
            }
        }

        public bool IsInRangeMaxRect(Vector3 local, Rect chartRect, float triangleLen)
        {
            var centerPos = new Vector3(chartRect.x, chartRect.y) + location.GetPosition(chartRect.width, chartRect.height);

            if (orient == Orient.Vertical)
            {
                var radius = triangleLen / 2;
                var pos1 = centerPos + Vector3.down * itemHeight / 2;
                var cpos = new Vector3(pos1.x + itemWidth / 2 + radius, pos1.y + runtimeRangeMaxHeight + radius);

                return local.x >= cpos.x - radius &&
                    local.x <= cpos.x + radius &&
                    local.y >= cpos.y - radius &&
                    local.y <= cpos.y + radius;
            }
            else
            {
                var radius = triangleLen / 2;
                var pos1 = centerPos + Vector3.left * itemHeight / 2;
                var cpos = new Vector3(pos1.x + runtimeRangeMaxHeight + radius, pos1.y + itemWidth / 2 + radius);

                return local.x >= cpos.x - radius &&
                    local.x <= cpos.x + radius &&
                    local.y >= cpos.y - radius &&
                    local.y <= cpos.y + radius;
            }
        }
    }
}