using System;
using System.Collections.Generic;
using UnityEngine;

namespace XCharts.Runtime
{
    /// <summary>
    /// The axis in rectangular coordinate.
    /// |直角坐标系的坐标轴组件。
    /// </summary>
    [System.Serializable]
    public class Axis : MainComponent
    {
        /// <summary>
        /// the type of axis.
        /// |坐标轴类型。
        /// </summary>
        public enum AxisType
        {
            /// <summary>
            /// Numerical axis, suitable for continuous data.
            /// ||数值轴。适用于连续数据。
            /// </summary>
            Value,
            /// <summary>
            /// Category axis, suitable for discrete category data. Data should only be set via data for this type.
            /// ||类目轴。适用于离散的类目数据，为该类型时必须通过 data 设置类目数据。
            /// </summary>
            Category,
            /// <summary>
            /// Log axis, suitable for log data.
            /// |对数轴。适用于对数数据。
            /// </summary>
            Log,
            /// <summary>
            /// Time axis, suitable for continuous time series data.
            /// |时间轴。适用于连续的时序数据。
            /// </summary>
            Time
        }

        /// <summary>
        /// the type of axis min and max value.
        /// |坐标轴最大最小刻度显示类型。
        /// </summary>
        public enum AxisMinMaxType
        {
            /// <summary>
            /// 0 - maximum.
            /// |0-最大值。
            /// </summary>
            Default,
            /// <summary>
            /// minimum - maximum.
            /// |最小值-最大值。
            /// </summary>
            MinMax,
            /// <summary>
            /// Customize the minimum and maximum.
            /// |自定义最小值最大值。
            /// </summary>
            Custom
        }
        /// <summary>
        /// the position of axis in grid.
        /// |坐标轴在Grid中的位置
        /// </summary>
        public enum AxisPosition
        {
            Left,
            Right,
            Bottom,
            Top
        }

        [SerializeField] protected bool m_Show = true;
        [SerializeField] protected AxisType m_Type;
        [SerializeField] protected AxisMinMaxType m_MinMaxType;
        [SerializeField] protected int m_GridIndex;
        [SerializeField] protected int m_PolarIndex;
        [SerializeField] protected int m_ParallelIndex;
        [SerializeField] protected AxisPosition m_Position;
        [SerializeField] protected float m_Offset;
        [SerializeField] protected double m_Min;
        [SerializeField] protected double m_Max;
        [SerializeField] protected int m_SplitNumber = 0;
        [SerializeField] protected double m_Interval = 0;
        [SerializeField] protected bool m_BoundaryGap = true;
        [SerializeField] protected int m_MaxCache = 0;
        [SerializeField] protected float m_LogBase = 10;
        [SerializeField] protected bool m_LogBaseE = false;
        [SerializeField] protected int m_CeilRate = 0;
        [SerializeField] protected bool m_Inverse = false;
        [SerializeField] private bool m_Clockwise = true;
        [SerializeField] private bool m_InsertDataToHead;
        [SerializeField] protected List<Sprite> m_Icons = new List<Sprite>();
        [SerializeField] protected List<string> m_Data = new List<string>();
        [SerializeField] protected AxisLine m_AxisLine = AxisLine.defaultAxisLine;
        [SerializeField] protected AxisName m_AxisName = AxisName.defaultAxisName;
        [SerializeField] protected AxisTick m_AxisTick = AxisTick.defaultTick;
        [SerializeField] protected AxisLabel m_AxisLabel = AxisLabel.defaultAxisLabel;
        [SerializeField] protected AxisSplitLine m_SplitLine = AxisSplitLine.defaultSplitLine;
        [SerializeField] protected AxisSplitArea m_SplitArea = AxisSplitArea.defaultSplitArea;

        public AxisContext context = new AxisContext();

        /// <summary>
        /// Whether to show axis.
        /// |是否显示坐标轴。
        /// </summary>
        public bool show
        {
            get { return m_Show; }
            set { if (PropertyUtil.SetStruct(ref m_Show, value)) SetAllDirty(); }
        }
        /// <summary>
        /// the type of axis.
        /// |坐标轴类型。
        /// </summary>
        public AxisType type
        {
            get { return m_Type; }
            set { if (PropertyUtil.SetStruct(ref m_Type, value)) SetAllDirty(); }
        }
        /// <summary>
        /// the type of axis minmax.
        /// |坐标轴刻度最大最小值显示类型。
        /// </summary>
        public AxisMinMaxType minMaxType
        {
            get { return m_MinMaxType; }
            set { if (PropertyUtil.SetStruct(ref m_MinMaxType, value)) SetAllDirty(); }
        }
        /// <summary>
        /// The index of the grid on which the axis are located, by default, is in the first grid.
        /// |坐标轴所在的 grid 的索引，默认位于第一个 grid。
        /// </summary>
        public int gridIndex
        {
            get { return m_GridIndex; }
            set { if (PropertyUtil.SetStruct(ref m_GridIndex, value)) SetAllDirty(); }
        }
        /// <summary>
        /// The index of the polar on which the axis are located, by default, is in the first polar.
        /// |坐标轴所在的 ploar 的索引，默认位于第一个 polar。
        /// </summary>
        public int polarIndex
        {
            get { return m_PolarIndex; }
            set { if (PropertyUtil.SetStruct(ref m_PolarIndex, value)) SetAllDirty(); }
        }
        /// <summary>
        /// The index of the parallel on which the axis are located, by default, is in the first parallel.
        /// |坐标轴所在的 parallel 的索引，默认位于第一个 parallel。
        /// </summary>
        public int parallelIndex
        {
            get { return m_ParallelIndex; }
            set { if (PropertyUtil.SetStruct(ref m_ParallelIndex, value)) SetAllDirty(); }
        }
        /// <summary>
        /// the position of axis in grid.
        /// |坐标轴在Grid中的位置。
        /// </summary>
        public AxisPosition position
        {
            get { return m_Position; }
            set { if (PropertyUtil.SetStruct(ref m_Position, value)) SetAllDirty(); }
        }
        /// <summary>
        /// the offset of axis from the default position. Useful when the same position has multiple axes.
        /// |坐标轴相对默认位置的偏移。在相同position有多个坐标轴时有用。
        /// </summary>
        public float offset
        {
            get { return m_Offset; }
            set { if (PropertyUtil.SetStruct(ref m_Offset, value)) SetAllDirty(); }
        }
        /// <summary>
        /// The minimun value of axis.Valid when `minMaxType` is `Custom`
        /// |设定的坐标轴刻度最小值，当minMaxType为Custom时有效。
        /// </summary>
        public double min
        {
            get { return m_Min; }
            set { if (PropertyUtil.SetStruct(ref m_Min, value)) SetAllDirty(); }
        }
        /// <summary>
        /// The maximum value of axis.Valid when `minMaxType` is `Custom`
        /// |设定的坐标轴刻度最大值，当minMaxType为Custom时有效。
        /// </summary>
        public double max
        {
            get { return m_Max; }
            set { if (PropertyUtil.SetStruct(ref m_Max, value)) SetAllDirty(); }
        }
        /// <summary>
        /// Number of segments that the axis is split into.
        /// |坐标轴的期望的分割段数。默认为0表示自动分割。 
        /// </summary>
        public int splitNumber
        {
            get { return m_SplitNumber; }
            set { if (PropertyUtil.SetStruct(ref m_SplitNumber, value)) SetAllDirty(); }
        }
        /// <summary>
        /// Compulsively set segmentation interval for axis.This is unavailable for category axis.
        /// |强制设置坐标轴分割间隔。无法在类目轴中使用。
        /// </summary>
        public double interval
        {
            get { return m_Interval; }
            set { if (PropertyUtil.SetStruct(ref m_Interval, value)) SetAllDirty(); }
        }
        /// <summary>
        /// The boundary gap on both sides of a coordinate axis, which is valid only for category axis with type: 'Category'.
        /// |坐标轴两边是否留白。只对类目轴有效。
        /// </summary>
        public bool boundaryGap
        {
            get { return IsCategory() ? m_BoundaryGap : false; }
            set { if (PropertyUtil.SetStruct(ref m_BoundaryGap, value)) SetAllDirty(); }
        }
        /// <summary>
        /// Base of logarithm, which is valid only for numeric axes with type: 'Log'.
        /// |对数轴的底数，只在对数轴（type:'Log'）中有效。
        /// </summary>
        public float logBase
        {
            get { return m_LogBase; }
            set
            {
                if (value <= 0 || value == 1) value = 10;
                if (PropertyUtil.SetStruct(ref m_LogBase, value)) SetAllDirty();
            }
        }
        /// <summary>
        /// On the log axis, if base e is the natural number, and is true, logBase fails.
        /// |对数轴是否以自然数 e 为底数，为 true 时 logBase 失效。
        /// </summary>
        public bool logBaseE
        {
            get { return m_LogBaseE; }
            set { if (PropertyUtil.SetStruct(ref m_LogBaseE, value)) SetAllDirty(); }
        }
        /// <summary>
        /// The max number of axis data cache.
        /// |The first data will be remove when the size of axis data is larger then maxCache.
        /// |可缓存的最大数据量。默认为0没有限制，大于0时超过指定值会移除旧数据再插入新数据。
        /// </summary>
        public int maxCache
        {
            get { return m_MaxCache; }
            set { if (PropertyUtil.SetStruct(ref m_MaxCache, value < 0 ? 0 : value)) SetAllDirty(); }
        }
        /// <summary>
        /// The ratio of maximum and minimum values rounded upward. The default is 0, which is automatically calculated.
        /// |最大最小值向上取整的倍率。默认为0时自动计算。
        /// </summary>
        public int ceilRate
        {
            get { return m_CeilRate; }
            set { if (PropertyUtil.SetStruct(ref m_CeilRate, value < 0 ? 0 : value)) SetAllDirty(); }
        }
        /// <summary>
        /// Whether the axis are reversed or not. Invalid in `Category` axis.
        /// |是否反向坐标轴。在类目轴中无效。
        /// </summary>
        public bool inverse
        {
            get { return m_Inverse; }
            set { if (m_Type == AxisType.Value && PropertyUtil.SetStruct(ref m_Inverse, value)) SetAllDirty(); }
        }
        /// <summary>
        /// Whether the positive position of axis is in clockwise. True for clockwise by default.
        /// |刻度增长是否按顺时针，默认顺时针。
        /// </summary>
        public bool clockwise
        {
            get { return m_Clockwise; }
            set { if (PropertyUtil.SetStruct(ref m_Clockwise, value)) SetAllDirty(); }
        }
        /// <summary>
        /// Category data, available in type: 'Category' axis.
        /// |类目数据，在类目轴（type: 'category'）中有效。
        /// </summary>
        public List<string> data
        {
            get { return m_Data; }
            set { if (value != null) { m_Data = value; SetAllDirty(); } }
        }
        /// <summary>
        /// 类目数据对应的图标。
        /// </summary>
        public List<Sprite> icons
        {
            get { return m_Icons; }
            set { if (value != null) { m_Icons = value; SetAllDirty(); } }
        }
        /// <summary>
        /// axis Line.
        /// |坐标轴轴线。
        /// </summary>
        public AxisLine axisLine
        {
            get { return m_AxisLine; }
            set { if (value != null) { m_AxisLine = value; SetVerticesDirty(); } }
        }
        /// <summary>
        /// axis name.
        /// |坐标轴名称。
        /// </summary>
        public AxisName axisName
        {
            get { return m_AxisName; }
            set { if (value != null) { m_AxisName = value; SetComponentDirty(); } }
        }
        /// <summary>
        /// axis tick.
        /// |坐标轴刻度。
        /// </summary>
        public AxisTick axisTick
        {
            get { return m_AxisTick; }
            set { if (value != null) { m_AxisTick = value; SetVerticesDirty(); } }
        }
        /// <summary>
        /// axis label.
        /// |坐标轴刻度标签。
        /// </summary>
        public AxisLabel axisLabel
        {
            get { return m_AxisLabel; }
            set { if (value != null) { m_AxisLabel = value; SetComponentDirty(); } }
        }
        /// <summary>
        /// axis split line.
        /// |坐标轴分割线。
        /// </summary>
        public AxisSplitLine splitLine
        {
            get { return m_SplitLine; }
            set { if (value != null) { m_SplitLine = value; SetVerticesDirty(); } }
        }
        /// <summary>
        /// axis split area.
        /// |坐标轴分割区域。
        /// </summary>
        public AxisSplitArea splitArea
        {
            get { return m_SplitArea; }
            set { if (value != null) { m_SplitArea = value; SetVerticesDirty(); } }
        }
        /// <summary>
        /// Whether to add new data at the head or at the end of the list.
        /// |添加新数据时是在列表的头部还是尾部加入。
        /// </summary>
        public bool insertDataToHead
        {
            get { return m_InsertDataToHead; }
            set { if (PropertyUtil.SetStruct(ref m_InsertDataToHead, value)) SetAllDirty(); }
        }

        public override bool vertsDirty
        {
            get
            {
                return m_VertsDirty ||
                    axisLine.anyDirty ||
                    axisTick.anyDirty ||
                    splitLine.anyDirty ||
                    splitArea.anyDirty;
            }
        }

        public override bool componentDirty
        {
            get
            {
                return m_ComponentDirty ||
                    axisName.anyDirty ||
                    axisLabel.anyDirty;
            }
        }

        public override void ClearComponentDirty()
        {
            base.ClearComponentDirty();
            axisName.ClearComponentDirty();
            axisLabel.ClearComponentDirty();
        }

        public override void ClearVerticesDirty()
        {
            base.ClearVerticesDirty();
            axisLine.ClearVerticesDirty();
            axisTick.ClearVerticesDirty();
            splitLine.ClearVerticesDirty();
            splitArea.ClearVerticesDirty();
        }

        public override void SetComponentDirty()
        {
            context.isNeedUpdateFilterData = true;
            base.SetComponentDirty();
        }

        public Axis Clone()
        {
            var axis = new Axis();
            axis.show = show;
            axis.type = type;
            axis.gridIndex = 0;
            axis.minMaxType = minMaxType;
            axis.min = min;
            axis.max = max;
            axis.splitNumber = splitNumber;
            axis.interval = interval;
            axis.boundaryGap = boundaryGap;
            axis.maxCache = maxCache;
            axis.logBase = logBase;
            axis.logBaseE = logBaseE;
            axis.ceilRate = ceilRate;
            axis.insertDataToHead = insertDataToHead;
            axis.axisLine = axisLine.Clone();
            axis.axisName = axisName.Clone();
            axis.axisTick = axisTick.Clone();
            axis.axisLabel = axisLabel.Clone();
            axis.splitLine = splitLine.Clone();
            axis.splitArea = splitArea.Clone();
            axis.icons = new List<Sprite>();
            axis.data = new List<string>();
            ChartHelper.CopyList(axis.data, data);
            return axis;
        }

        public void Copy(Axis axis)
        {
            show = axis.show;
            type = axis.type;
            minMaxType = axis.minMaxType;
            gridIndex = axis.gridIndex;
            min = axis.min;
            max = axis.max;
            splitNumber = axis.splitNumber;
            interval = axis.interval;
            boundaryGap = axis.boundaryGap;
            maxCache = axis.maxCache;
            logBase = axis.logBase;
            logBaseE = axis.logBaseE;
            ceilRate = axis.ceilRate;
            insertDataToHead = axis.insertDataToHead;
            axisLine.Copy(axis.axisLine);
            axisName.Copy(axis.axisName);
            axisTick.Copy(axis.axisTick);
            axisLabel.Copy(axis.axisLabel);
            splitLine.Copy(axis.splitLine);
            splitArea.Copy(axis.splitArea);
            ChartHelper.CopyList(data, axis.data);
            ChartHelper.CopyList<Sprite>(icons, axis.icons);
        }

        /// <summary>
        /// 清空类目数据
        /// </summary>
        public override void ClearData()
        {
            m_Data.Clear();
            m_Icons.Clear();
            context.Clear();
            SetAllDirty();
        }

        /// <summary>
        /// 是否为类目轴。
        /// </summary>
        /// <returns></returns>
        public bool IsCategory()
        {
            return m_Type == AxisType.Category;
        }

        /// <summary>
        /// 是否为数值轴。
        /// </summary>
        /// <returns></returns>
        public bool IsValue()
        {
            return m_Type == AxisType.Value;
        }

        /// <summary>
        /// 是否为对数轴。
        /// </summary>
        /// <returns></returns>
        public bool IsLog()
        {
            return m_Type == AxisType.Log;
        }

        /// <summary>
        /// 是否为时间轴。
        /// </summary>
        public bool IsTime()
        {
            return m_Type == AxisType.Time;
        }

        public bool IsLeft()
        {
            return m_Position == AxisPosition.Left;
        }

        public bool IsRight()
        {
            return m_Position == AxisPosition.Right;
        }

        public bool IsTop()
        {
            return m_Position == AxisPosition.Top;
        }

        public bool IsBottom()
        {
            return m_Position == AxisPosition.Bottom;
        }

        public void SetNeedUpdateFilterData()
        {
            context.isNeedUpdateFilterData = true;
        }

        /// <summary>
        /// 添加一个类目到类目数据列表
        /// </summary>
        /// <param name="category"></param>
        public void AddData(string category)
        {
            if (maxCache > 0)
            {
                while (m_Data.Count >= maxCache)
                {
                    RemoveData(m_InsertDataToHead ? m_Data.Count - 1 : 0);
                }
            }

            if (m_InsertDataToHead)
                m_Data.Insert(0, category);
            else
                m_Data.Add(category);

            SetAllDirty();
        }

        public void RemoveData(int dataIndex)
        {
            context.isNeedUpdateFilterData = true;
            m_Data.RemoveAt(dataIndex);
        }

        /// <summary>
        /// 更新类目数据
        /// </summary>
        /// <param name="index"></param>
        /// <param name="category"></param>
        public void UpdateData(int index, string category)
        {
            if (index >= 0 && index < m_Data.Count)
            {
                m_Data[index] = category;
                SetComponentDirty();
            }
        }

        /// <summary>
        /// 添加图标
        /// </summary>
        /// <param name="icon"></param>
        public void AddIcon(Sprite icon)
        {
            if (maxCache > 0)
            {
                while (m_Icons.Count > maxCache)
                {
                    m_Icons.RemoveAt(m_InsertDataToHead ? m_Icons.Count - 1 : 0);
                }
            }
            if (m_InsertDataToHead) m_Icons.Insert(0, icon);
            else m_Icons.Add(icon);
            SetAllDirty();
        }

        /// <summary>
        /// 更新图标
        /// </summary>
        /// <param name="index"></param>
        /// <param name="icon"></param>
        public void UpdateIcon(int index, Sprite icon)
        {
            if (index >= 0 && index < m_Icons.Count)
            {
                m_Icons[index] = icon;
                SetComponentDirty();
            }
        }

        /// <summary>
        /// 获得指定索引的类目数据
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public string GetData(int index)
        {
            if (index >= 0 && index < m_Data.Count)
                return m_Data[index];
            else
                return null;
        }

        /// <summary>
        /// 获得在dataZoom范围内指定索引的类目数据
        /// </summary>
        /// <param name="index">类目数据索引</param>
        /// <param name="dataZoom">区域缩放</param>
        /// <returns></returns>
        public string GetData(int index, DataZoom dataZoom)
        {
            var showData = GetDataList(dataZoom);
            if (index >= 0 && index < showData.Count)
                return showData[index];
            else
                return "";
        }

        public Sprite GetIcon(int index)
        {
            if (index >= 0 && index < m_Icons.Count)
                return m_Icons[index];
            else
                return null;
        }

        /// <summary>
        /// 获得值在坐标轴上的距离
        /// </summary>
        /// <param name="value"></param>
        /// <param name="axisLength"></param>
        /// <returns></returns>
        public float GetDistance(double value, float axisLength)
        {
            if (context.minMaxRange == 0)
                return 0;

            if (IsCategory() && boundaryGap)
            {
                var each = axisLength / data.Count;
                return (float) (each * (value + 0.5f));
            }
            else
            {
                return axisLength * (float) ((value - context.minValue) / context.minMaxRange);
            }
        }

        /// <summary>
        /// 获得指定区域缩放的类目数据列表
        /// </summary>
        /// <param name="dataZoom">区域缩放</param>
        /// <returns></returns>
        internal List<string> GetDataList(DataZoom dataZoom)
        {
            if (dataZoom != null && dataZoom.enable && dataZoom.IsContainsAxis(this))
            {
                UpdateFilterData(dataZoom);
                return context.filterData;
            }
            else
            {
                return m_Data.Count > 0 ? m_Data : context.runtimeData;
            }
        }

        internal List<string> GetDataList()
        {
            return m_Data.Count > 0 ? m_Data : context.runtimeData;
        }

        /// <summary>
        /// 更新dataZoom对应的类目数据列表
        /// </summary>
        /// <param name="dataZoom"></param>
        internal void UpdateFilterData(DataZoom dataZoom)
        {
            if (dataZoom != null && dataZoom.enable && dataZoom.IsContainsAxis(this))
            {
                var data = GetDataList();
                context.UpdateFilterData(data, dataZoom);
            }
        }

        /// <summary>
        /// 获得类目数据个数
        /// </summary>
        /// <param name="dataZoom"></param>
        /// <returns></returns>
        internal int GetDataCount(DataZoom dataZoom)
        {
            return IsCategory() ? GetDataList(dataZoom).Count : 0;
        }

        /// <summary>
        /// 更新刻度标签文字
        /// </summary>
        /// <param name="dataZoom"></param>
        internal void UpdateLabelText(float coordinateWidth, DataZoom dataZoom, bool forcePercent)
        {
            for (int i = 0; i < context.labelObjectList.Count; i++)
            {
                if (context.labelObjectList[i] != null)
                {
                    var text = AxisHelper.GetLabelName(this, coordinateWidth, i, context.minValue, context.maxValue, dataZoom, forcePercent);
                    context.labelObjectList[i].SetText(text);
                }
            }
        }

        internal Vector3 GetLabelObjectPosition(int index)
        {
            if (context.labelObjectList != null && index < context.labelObjectList.Count)
                return context.labelObjectList[index].GetPosition();
            else
                return Vector3.zero;
        }

        internal void UpdateMinMaxValue(double minValue, double maxValue)
        {
            context.minValue = minValue;
            context.maxValue = maxValue;
            double tempRange = maxValue - minValue;
            if (context.minMaxRange != tempRange)
            {
                context.minMaxRange = tempRange;
                if (type == Axis.AxisType.Value && interval > 0)
                {
                    SetComponentDirty();
                }
            }
        }

        public float GetLogValue(double value)
        {
            if (value <= 0 || value == 1)
                return 0;
            else
                return logBaseE ? (float) Math.Log(value) : (float) Math.Log(value, logBase);
        }

        public int GetLogMinIndex()
        {
            return logBaseE ?
                (int) Math.Log(context.minValue) :
                (int) Math.Log(context.minValue, logBase);
        }

        public int GetLogMaxIndex()
        {
            return logBaseE ?
                (int) Math.Log(context.maxValue) :
                (int) Math.Log(context.maxValue, logBase);
        }

        public double GetLabelValue(int index)
        {
            if (index < 0)
                return context.minValue;
            else if (index > context.labelValueList.Count - 1)
                return context.maxValue;
            else
                return context.labelValueList[index];
        }

        public double GetLastLabelValue()
        {
            if (context.labelValueList.Count > 0)
                return context.labelValueList[context.labelValueList.Count - 1];
            else
                return 0;
        }
    }
}