using System;
using System.Collections.Generic;
using Robust.Shared.Maths;

namespace Robust.Client.UserInterface.Controls
{
    /// <summary>
    ///     A container that lays out its children sequentially.
    ///     Use <see cref="VBoxContainer"/> or <see cref="HBoxContainer"/> for an implementation.
    /// </summary>
    public abstract class BoxContainer : Container
    {
        public const string StylePropertySeparation = "separation";

        private const int DefaultSeparation = 0;
        private protected abstract bool Vertical { get; }

        /// <summary>
        ///     Specifies "where" the controls should be laid out.
        /// </summary>
        public AlignMode Align { get; set; }

        private int ActualSeparation
        {
            get
            {
                if (TryGetStyleProperty(StylePropertySeparation, out int separation))
                {
                    return separation;
                }

                return SeparationOverride ?? DefaultSeparation;
            }
        }

        public int? SeparationOverride { get; set; }

        protected override Vector2 MeasureOverride(Vector2 availableSize)
        {
            var separation = ActualSeparation;

            var minSize = Vector2.Zero;
            var first = true;

            foreach (var child in Children)
            {
                if (!child.Visible)
                {
                    continue;
                }

                child.Measure(availableSize);

                var childSize = child.DesiredSize;
                if (Vertical)
                {
                    var taken = childSize.Y;
                    if (!first)
                    {
                        taken += separation;
                    }

                    minSize.Y += taken;
                    availableSize.Y = Math.Max(0, availableSize.Y - taken);

                    first = false;
                    minSize.X = Math.Max(minSize.X, childSize.X);
                }
                else
                {
                    var taken = childSize.X;
                    if (!first)
                    {
                        taken += separation;
                    }

                    minSize.X += taken;
                    availableSize.X = Math.Max(0, availableSize.X - taken);

                    first = false;

                    minSize.Y = Math.Max(minSize.Y, childSize.Y);
                }
            }

            return minSize;
        }

        protected override Vector2 ArrangeOverride(Vector2 finalSize)
        {
            var finalPixel = (Vector2i) (finalSize * UIScale);
            var separation = (int) (ActualSeparation * UIScale);

            // Step one: figure out the sizes of all our children and whether they want to stretch.
            var sizeList = new List<(Control control, int minSize, int finalSize, bool stretch)>(ChildCount);
            var totalStretchRatio = 0f;
            // Amount of space not available for stretching.
            var stretchMin = 0;

            foreach (var child in Children)
            {
                if (!child.Visible)
                {
                    continue;
                }

                var (minX, minY) = child.DesiredPixelSize;
                int minSize;
                bool stretch;

                if (Vertical)
                {
                    minSize = minY;
                    stretch = child.VerticalExpand;
                }
                else
                {
                    minSize = minX;
                    stretch = child.HorizontalExpand;
                }

                if (!stretch)
                {
                    stretchMin += minSize;
                }
                else
                {
                    totalStretchRatio += child.SizeFlagsStretchRatio;
                }

                sizeList.Add((child, minSize, minSize, stretch));
            }

            var stretchMax = Vertical ? finalPixel.Y : finalPixel.X;

            stretchMax -= separation * (ChildCount - 1);
            // This is the amount of space allocated for stretchable children.
            var stretchAvail = Math.Max(0, stretchMax - stretchMin);

            // Step two: figure out which that want to stretch need to suck it,
            // because due to their stretch ratio they would be smaller than minSize.
            // Treat those as non-stretching.
            for (var i = 0; i < sizeList.Count; i++)
            {
                var (control, minSize, _, stretch) = sizeList[i];
                if (!stretch)
                {
                    continue;
                }

                var share = (int) (stretchAvail * (control.SizeFlagsStretchRatio / totalStretchRatio));
                if (share < minSize)
                {
                    sizeList[i] = (control, minSize, minSize, false);
                    stretchAvail -= minSize;
                    totalStretchRatio -= control.SizeFlagsStretchRatio;
                }
            }

            // Step three: allocate space for all the stretchable children.
            var stretchingAtAll = false;
            for (var i = 0; i < sizeList.Count; i++)
            {
                var (control, minSize, _, stretch) = sizeList[i];
                if (!stretch)
                {
                    continue;
                }

                stretchingAtAll = true;

                var share = (int) (stretchAvail * (control.SizeFlagsStretchRatio / totalStretchRatio));
                sizeList[i] = (control, minSize, share, false);
            }

            // Step four: actually lay them out one by one.
            var offset = 0;
            if (!stretchingAtAll)
            {
                switch (Align)
                {
                    case AlignMode.Begin:
                        break;
                    case AlignMode.Center:
                        offset = stretchAvail / 2;
                        break;
                    case AlignMode.End:
                        offset = stretchAvail;
                        break;
                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }

            var first = true;
            foreach (var (control, _, size, _) in sizeList)
            {
                if (!first)
                {
                    offset += separation;
                }

                first = false;

                UIBox2i targetBox;
                if (Vertical)
                {
                    targetBox = new UIBox2i(0, offset, finalPixel.X, offset + size);
                }
                else
                {
                    targetBox = new UIBox2i(offset, 0, offset + size, finalPixel.Y);
                }

                control.ArrangePixel(targetBox);

                offset += size;
            }

            return finalSize;
        }

        public enum AlignMode : byte
        {
            /// <summary>
            ///     Controls are laid out from the begin of the box container.
            /// </summary>
            Begin = 0,

            /// <summary>
            ///     Controls are laid out from the center of the box container.
            /// </summary>
            Center = 1,

            /// <summary>
            ///     Controls are laid out from the end of the box container.
            /// </summary>
            End = 2
        }
    }
}
