namespace SRF.UI.Layout
{
    using System.Collections.Generic;
    using Internal;
    using UnityEngine;
    using UnityEngine.UI;
    /// 
    /// Layout Group controller that arranges children in rows, fitting as many on a line until total width exceeds parent
    /// bounds
    /// 
    [AddComponentMenu(ComponentMenuPaths.FlowLayoutGroup)]
    public class FlowLayoutGroup : LayoutGroup
    {
        /// 
        /// Holds the rects that will make up the current row being processed
        /// 
        private readonly IList _rowList = new List();
        private float _layoutHeight;
        public bool ChildForceExpandHeight = false;
        public bool ChildForceExpandWidth = false;
        public float Spacing = 0f;
        protected bool IsCenterAlign
        {
            get
            {
                return childAlignment == TextAnchor.LowerCenter || childAlignment == TextAnchor.MiddleCenter ||
                       childAlignment == TextAnchor.UpperCenter;
            }
        }
        protected bool IsRightAlign
        {
            get
            {
                return childAlignment == TextAnchor.LowerRight || childAlignment == TextAnchor.MiddleRight ||
                       childAlignment == TextAnchor.UpperRight;
            }
        }
        protected bool IsMiddleAlign
        {
            get
            {
                return childAlignment == TextAnchor.MiddleLeft || childAlignment == TextAnchor.MiddleRight ||
                       childAlignment == TextAnchor.MiddleCenter;
            }
        }
        protected bool IsLowerAlign
        {
            get
            {
                return childAlignment == TextAnchor.LowerLeft || childAlignment == TextAnchor.LowerRight ||
                       childAlignment == TextAnchor.LowerCenter;
            }
        }
        public override void CalculateLayoutInputHorizontal()
        {
            base.CalculateLayoutInputHorizontal();
            var minWidth = GetGreatestMinimumChildWidth() + padding.left + padding.right;
            SetLayoutInputForAxis(minWidth, -1, -1, 0);
        }
        public override void SetLayoutHorizontal()
        {
            SetLayout(rectTransform.rect.width, 0, false);
        }
        public override void SetLayoutVertical()
        {
            SetLayout(rectTransform.rect.width, 1, false);
        }
        public override void CalculateLayoutInputVertical()
        {
            _layoutHeight = SetLayout(rectTransform.rect.width, 1, true);
        }
        /// 
        /// Main layout method
        /// 
        /// Width to calculate the layout with
        /// 0 for horizontal axis, 1 for vertical
        /// If true, sets the layout input for the axis. If false, sets child position for axis
        public float SetLayout(float width, int axis, bool layoutInput)
        {
            var groupHeight = rectTransform.rect.height;
            // Width that is available after padding is subtracted
            var workingWidth = rectTransform.rect.width - padding.left - padding.right;
            // Accumulates the total height of the rows, including spacing and padding.
            var yOffset = IsLowerAlign ? padding.bottom : (float)padding.top;
            var currentRowWidth = 0f;
            var currentRowHeight = 0f;
            for (var i = 0; i < rectChildren.Count; i++)
            {
                // LowerAlign works from back to front
                var index = IsLowerAlign ? rectChildren.Count - 1 - i : i;
                var child = rectChildren[index];
                var childWidth = LayoutUtility.GetPreferredSize(child, 0);
                var childHeight = LayoutUtility.GetPreferredSize(child, 1);
                // Max child width is layout group with - padding
                childWidth = Mathf.Min(childWidth, workingWidth);
                // Apply spacing if not the first element in a row
                if (_rowList.Count > 0)
                {
                    currentRowWidth += Spacing;
                }
                // If adding this element would exceed the bounds of the row,
                // go to a new line after processing the current row
                if (currentRowWidth + childWidth > workingWidth)
                {
                    // Undo spacing addition if we're moving to a new line (Spacing is not applied on edges)
                    currentRowWidth -= Spacing;
                    // Process current row elements positioning
                    if (!layoutInput)
                    {
                        var h = CalculateRowVerticalOffset(groupHeight, yOffset, currentRowHeight);
                        LayoutRow(_rowList, currentRowWidth, currentRowHeight, workingWidth, padding.left, h, axis);
                    }
                    // Clear existing row
                    _rowList.Clear();
                    // Add the current row height to total height accumulator, and reset to 0 for the next row
                    yOffset += currentRowHeight;
                    yOffset += Spacing;
                    currentRowHeight = 0;
                    currentRowWidth = 0;
                }
                currentRowWidth += childWidth;
                _rowList.Add(child);
                // We need the largest element height to determine the starting position of the next line
                if (childHeight > currentRowHeight)
                {
                    currentRowHeight = childHeight;
                }
            }
            if (!layoutInput)
            {
                var h = CalculateRowVerticalOffset(groupHeight, yOffset, currentRowHeight);
                // Layout the final row
                LayoutRow(_rowList, currentRowWidth, currentRowHeight, workingWidth, padding.left, h, axis);
            }
            _rowList.Clear();
            // Add the last rows height to the height accumulator
            yOffset += currentRowHeight;
            yOffset += IsLowerAlign ? padding.top : padding.bottom;
            if (layoutInput)
            {
                if (axis == 1)
                {
                    SetLayoutInputForAxis(yOffset, yOffset, -1, axis);
                }
            }
            return yOffset;
        }
        private float CalculateRowVerticalOffset(float groupHeight, float yOffset, float currentRowHeight)
        {
            float h;
            if (IsLowerAlign)
            {
                h = groupHeight - yOffset - currentRowHeight;
            }
            else if (IsMiddleAlign)
            {
                h = groupHeight * 0.5f - _layoutHeight * 0.5f + yOffset;
            }
            else
            {
                h = yOffset;
            }
            return h;
        }
        protected void LayoutRow(IList contents, float rowWidth, float rowHeight, float maxWidth,
            float xOffset, float yOffset, int axis)
        {
            var xPos = xOffset;
            if (!ChildForceExpandWidth && IsCenterAlign)
            {
                xPos += (maxWidth - rowWidth) * 0.5f;
            }
            else if (!ChildForceExpandWidth && IsRightAlign)
            {
                xPos += (maxWidth - rowWidth);
            }
            var extraWidth = 0f;
            if (ChildForceExpandWidth)
            {
                var flexibleChildCount = 0;
                for (var i = 0; i < _rowList.Count; i++)
                {
                    if (LayoutUtility.GetFlexibleWidth(_rowList[i]) > 0f)
                    {
                        flexibleChildCount++;
                    }
                }
                if (flexibleChildCount > 0)
                {
                    extraWidth = (maxWidth - rowWidth) / flexibleChildCount;
                }
            }
            for (var j = 0; j < _rowList.Count; j++)
            {
                var index = IsLowerAlign ? _rowList.Count - 1 - j : j;
                var rowChild = _rowList[index];
                var rowChildWidth = LayoutUtility.GetPreferredSize(rowChild, 0);
                if (LayoutUtility.GetFlexibleWidth(rowChild) > 0f)
                {
                    rowChildWidth += extraWidth;
                }
                var rowChildHeight = LayoutUtility.GetPreferredSize(rowChild, 1);
                if (ChildForceExpandHeight)
                {
                    rowChildHeight = rowHeight;
                }
                rowChildWidth = Mathf.Min(rowChildWidth, maxWidth);
                var yPos = yOffset;
                if (IsMiddleAlign)
                {
                    yPos += (rowHeight - rowChildHeight) * 0.5f;
                }
                else if (IsLowerAlign)
                {
                    yPos += (rowHeight - rowChildHeight);
                }
                if (axis == 0)
                {
#if UNITY_2019_1
                    SetChildAlongAxis(rowChild, 0, 1f, xPos, rowChildWidth);
#else
                    SetChildAlongAxis(rowChild, 0, xPos, rowChildWidth);
#endif
                }
                else
                {
#if UNITY_2019_1
                    SetChildAlongAxis(rowChild, 1, 1f, yPos, rowChildHeight);
#else
                    SetChildAlongAxis(rowChild, 1, yPos, rowChildHeight);
#endif
                }
                xPos += rowChildWidth + Spacing;
            }
        }
        public float GetGreatestMinimumChildWidth()
        {
            var max = 0f;
            for (var i = 0; i < rectChildren.Count; i++)
            {
                var w = LayoutUtility.GetMinWidth(rectChildren[i]);
                max = Mathf.Max(w, max);
            }
            return max;
        }
    }
}