/**************************************************** 
 文件：UnoEditorLayout.cs
 作者：张铮
 邮箱：834207172@qq.com
 日期：2022/3/3 17:53:52
 功能：
*****************************************************/
using UnityEditor;
using UnityEngine;
using System;
namespace Stary.Evo.Editor  
{
    public class UnoEditorLayout
    {
        public float LineSpacing = 2;
        public int IndentCount { get; set; }
        public float IndentSize = 15;

        private float _originX;

        private Rect _rootRect;

        public Rect RootRect
        {
            get
            {
                var rect = new Rect(_rootRect);
                rect.x = _originX + IndentSize * IndentCount;
                return rect;
            }
        }

        public void Begin(Rect rect)
        {
            IndentCount = 0;
            _rootRect = rect;
            _originX = _rootRect.x;
        }

        /// <summary>
        /// 移至新的一行
        /// </summary>
        /// <param name="height"></param>
        /// <returns>返回前一行的Rect</returns>
        public Rect NextLine(float height = 0, float lineSpacing = -1)
        {
            if (height <= 0)
                height = EditorGUIUtility.singleLineHeight;
            if (lineSpacing < 0)
                lineSpacing = LineSpacing;
            var rect = new Rect(RootRect);
            rect.height = height;
            var delta = height + lineSpacing;
            _rootRect.y += delta;
            _rootRect.height -= delta;
            return rect;
        }

        public Rect SingleLine(float height = 0f)
        {
            return CreateRow(new[] { LayoutDef.Any() }, height)[0];
        }

        public Rect[] CreateRow(LayoutDef[] lDefs, float height = 0f, float gap = 4f, Rect? rect = null, Rect[] result = null)
        {
            if (height <= 0)
                height = EditorGUIUtility.singleLineHeight;
            return CreateRects(lDefs, false, height, gap, rect, result);
        }

        public static Rect[] CreateRow(LayoutDef[] lDefs, Rect rect, float height = 0f, float gap = 4f, Rect[] result = null)
        {
            return CreateRects(lDefs, rect, false, height, gap, result);
        }

        public Rect[] CreateColum(LayoutDef[] lDefs, float width = 0f, float gap = 4f, Rect? rect = null, Rect[] result = null)
        {
            return CreateRects(lDefs, true, width, gap, rect, result);
        }

        public static Rect[] CreateColum(LayoutDef[] lDefs, Rect rect, float width = 0f, float gap = 4f, Rect[] result = null)
        {
            return CreateRects(lDefs, rect, true, width, gap, result);
        }

        private Rect[] CreateRects(LayoutDef[] lDefs, bool isVertical, float otherEdgeLength = 0f, float gap = 4, Rect? rect = null, Rect[] result = null)
        {
            rect = rect ?? RootRect;
            return CreateRects(lDefs, rect.Value, isVertical, otherEdgeLength, gap, result);
        }

        public static Rect[] CreateRects(LayoutDef[] lDefs, Rect r, bool isVertical, float otherEdgeLength = 0f, float gap = 4, Rect[] result = null)
        {
            if (result != null && result.Length != lDefs.Length)
                throw new ArgumentException("the length of result does not match to length of lDefs");
            if (otherEdgeLength <= 0)
            {
                if (isVertical)
                    otherEdgeLength = r.width;
                else
                    otherEdgeLength = r.height;
            }

            var totalSpace = isVertical ? r.height : r.width;
            var spaceLeft = totalSpace;
            var currentPos = isVertical ? r.y : r.x;
            var anyCount = lDefs.Length;
            result = result ?? new Rect[lDefs.Length];
            for (var i = 0; i < lDefs.Length; i++)
            {
                var ld = lDefs[i];
                float val = 0;
                if (!ld.IsAny)
                {
                    val = ld.RatioWise ? GetPercentageVal(ld, totalSpace, lDefs.Length, gap) : ld.Value;
                    val = ClampValueFromVector2(val, ld.MinSize, ld.MaxSize, isVertical);
                    spaceLeft -= val;
                    anyCount--;
                }
                else if (ld.Value > 0)
                {
                    val = ld.Value + gap;
                    spaceLeft -= val;
                    anyCount--;
                }

                if (isVertical)
                {
                    result[i].height = val;
                    result[i].width = otherEdgeLength;
                    result[i].x = r.x;
                }
                else
                {
                    result[i].width = val;
                    result[i].height = otherEdgeLength;
                    result[i].y = r.y;
                }

                spaceLeft -= gap;
            }

            spaceLeft += gap;

            var anySize = spaceLeft / anyCount;
            for (var i = 0; i < lDefs.Length; i++)
            {
                var ld = lDefs[i];
                if (!ld.IsAny || !ld.MinSize.HasValue)
                    continue;
                var size = 0f;
                var minSize = isVertical ? ld.MinSize.Value.y : ld.MinSize.Value.x;
                if (anySize < minSize)
                    size = minSize;
                if (size <= 0)
                    continue;

                anyCount--;
                spaceLeft -= size;
                lDefs[i].Value = size;
                if (isVertical)
                    result[i].height = size;
                else
                    result[i].width = size;
            }

            anySize = spaceLeft / anyCount;

            for (var i = 0; i < lDefs.Length; i++)
            {
                var ld = lDefs[i];
                if (!ld.IsAny || !ld.MaxSize.HasValue)
                    continue;
                var size = 0f;

                var maxSize = isVertical ? ld.MaxSize.Value.y : ld.MaxSize.Value.x;
                if (anySize > maxSize)
                    size = maxSize;

                if (size <= 0)
                    continue;

                anyCount--;
                spaceLeft -= size;
                lDefs[i].Value = size;
                if (isVertical)
                    result[i].height = size;
                else
                    result[i].width = size;
            }

            anySize = spaceLeft / anyCount;
            for (var i = 0; i < lDefs.Length; i++)
            {
                var ld = lDefs[i];
                float val;
                if (isVertical)
                {
                    if (ld.IsAny)
                    {
                        result[i].height = anySize;
                        val = anySize;
                    }
                    else
                        val = result[i].height;

                    result[i].y = currentPos;
                }
                else
                {
                    if (ld.IsAny)
                    {
                        result[i].width = anySize;
                        val = anySize;
                    }
                    else
                        val = result[i].width;

                    result[i].x = currentPos;
                }

                currentPos += val + gap;
                //Debug.Log(result[i].x + ", " + result[i].width);
            }

            return result;
        }

        public Rect[][] CreateGrid(LayoutDef[] hlDefs, LayoutDef[] vlDefs, float gap = 4, Rect? rect = null)
        {
            rect = rect ?? RootRect;
            return CreateGrid(hlDefs, vlDefs, rect.Value, gap);
        }

        public static Rect[][] CreateGrid(LayoutDef[] hlDefs, LayoutDef[] vlDefs, Rect rect, float gap = 4)
        {
            var result = new Rect[vlDefs.Length][];
            var row = CreateRects(hlDefs, rect, false, 0, gap);
            result[0] = row;
            for (var i = 1; i < vlDefs.Length; i++)
            {
                result[i] = new Rect[row.Length];
                for (var j = 0; j < row.Length; j++)
                {
                    result[i][j] = row[j];
                }
            }

            var col = CreateRects(vlDefs, rect, true, 0, gap);
            for (var i = 0; i < vlDefs.Length; i++)
            {
                for (var j = 0; j < result[i].Length; j++)
                {
                    result[i][j].height = col[i].height;
                    result[i][j].y = col[i].y;
                }
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="hlDefs"></param>
        /// <param name="vlDefs"></param>
        /// <param name="gap"></param>
        /// <param name="rect"></param>
        /// <returns>result[i] represents a row</returns>
        public Rect[][] CreateGridUnevenColum(LayoutDef[][] hlDefs, LayoutDef[] vlDefs, float gap = 4, Rect? rect = null)
        {
            rect = rect ?? RootRect;

            return CreateGridUnevenColum(hlDefs, vlDefs, rect.Value, gap);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="hlDefs"></param>
        /// <param name="vlDefs"></param>
        /// <param name="gap"></param>
        /// <param name="rect"></param>
        /// <returns>result[i] represents a row</returns>
        public static Rect[][] CreateGridUnevenColum(LayoutDef[][] hlDefs, LayoutDef[] vlDefs, Rect rect, float gap = 4)
        {
            if (hlDefs.Length != vlDefs.Length)
                throw new ArgumentException("the length of hlDefs must match with the length of vlDefs");

            var result = new Rect[vlDefs.Length][];
            for (var i = 0; i < vlDefs.Length; i++)
            {
                result[i] = CreateRects(hlDefs[i], rect, false, 0, gap);
            }

            var col = CreateRects(vlDefs, rect, true, 0, gap);
            for (var i = 0; i < vlDefs.Length; i++)
            {
                var row = result[i];
                for (var j = 0; j < row.Length; j++)
                {
                    row[j].height = col[i].height;
                    row[j].y = col[i].y;
                }
            }

            return result;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="hlDefs"></param>
        /// <param name="vlDefs"></param>
        /// <param name="gap"></param>
        /// <param name="rect"></param>
        /// <returns>result[i] represents a colum</returns>
        public Rect[][] CreateGridUnevenRow(LayoutDef[] hlDefs, LayoutDef[][] vlDefs, float gap = 4, Rect? rect = null)
        {
            rect = rect ?? RootRect;
            return CreateGridUnevenRow(hlDefs, vlDefs, rect.Value, gap);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="hlDefs"></param>
        /// <param name="vlDefs"></param>
        /// <param name="gap"></param>
        /// <param name="rect"></param>
        /// <returns>result[i] represents a colum</returns>
        public static Rect[][] CreateGridUnevenRow(LayoutDef[] hlDefs, LayoutDef[][] vlDefs, Rect rect, float gap = 4)
        {
            if (hlDefs.Length != vlDefs.Length)
                throw new ArgumentException("the length of hlDefs must match with the length of vlDefs");
            var result = new Rect[vlDefs.Length][];
            for (var i = 0; i < vlDefs.Length; i++)
            {
                result[i] = CreateRects(vlDefs[i], rect, true, 0, gap);
            }

            var row = CreateRects(hlDefs, rect, false, 0, gap);
            for (var i = 0; i < row.Length; i++)
            {
                for (var j = 0; j < result[i].Length; j++)
                {
                    result[i][j].width = row[i].width;
                    result[i][j].x = row[i].x;
                }
            }

            return result;
        }

        public static Vector2 EvaluateTextSize(GUIContent text, GUIStyle guiStyle = null)
        {
            if (guiStyle == null)
                guiStyle = EditorStyles.label;

            return guiStyle.CalcSize(text);
        }

        public static Vector2 EvaluateTextSize(string text, GUIStyle guiStyle = null)
        {
            return EvaluateTextSize(new GUIContent(text), guiStyle);
        }

        private static float ClampValueFromVector2(float val, Vector2? min, Vector2? max, bool byY)
        {
            if (byY)
            {
                return Mathf.Clamp(val,
                    min.HasValue ? min.Value.y : float.MinValue,
                    max.HasValue ? max.Value.y : float.MaxValue);
            }
            else
            {
                return Mathf.Clamp(val,
                    min.HasValue ? min.Value.x : float.MinValue,
                    max.HasValue ? max.Value.x : float.MaxValue);
            }
        }

        private static float GetPercentageVal(LayoutDef ld, float totalSpace, int numChildren, float gap)
        {
            return (totalSpace - (numChildren - 1) * gap) * ld.Value * .01f;
        }
    }

    public struct LayoutDef
    {
        public static LayoutDef Any()
        {
            return new LayoutDef();
        }

        public static LayoutDef Val(float value)
        {
            return new LayoutDef { Value = value };
        }

        public static LayoutDef Percentage(float percentage)
        {
            return new LayoutDef { Value = percentage, RatioWise = true };
        }

        public static LayoutDef[] EquallyDivide(int num)
        {
            var arr = new LayoutDef[num];
            //default(LayoutDef) == LayoutDef.Any()
            return arr;
        }

        public float Value { get; set; }
        public bool RatioWise { get; set; }
        public Vector2? MinSize { get; private set; }
        public Vector2? MaxSize { get; private set; }

        public LayoutDef WithMinSize(Vector2 minSize)
        {
            MinSize = minSize;
            return this;
        }

        public LayoutDef WithMaxSize(Vector2 maxSize)
        {
            MaxSize = maxSize;
            return this;
        }

        public LayoutDef WithMinSize(float x, float y)
        {
            MinSize = new Vector2(x, y);
            return this;
        }

        public LayoutDef WithMaxSize(float x, float y)
        {
            MaxSize = new Vector2(x, y);
            return this;
        }

        public bool IsAny
        {
            get { return Value == 0; }
        }
    }
}