using Microsoft.Xna.Framework;
using System;
using System.Linq;

namespace Barotrauma
{
    public class GUILayoutGroup : GUIComponent
    {
        private bool isHorizontal;
        public bool IsHorizontal
        {
            get { return isHorizontal; }
            set
            {
                isHorizontal = value;
                needsToRecalculate = true;
            }
        }

        private bool stretch;
        /// <summary>
        /// Note that stretching cannot be undone, because the previous child sizes are not stored.
        /// </summary>
        public bool Stretch
        {
            get { return stretch; }
            set
            {
                stretch = value;
                needsToRecalculate = true;
            }
        }

        private int absoluteSpacing;
        public int AbsoluteSpacing
        {
            get { return absoluteSpacing; }
            set
            {
                absoluteSpacing = MathHelper.Clamp(value, 0, int.MaxValue);
                needsToRecalculate = true;
            }
        }

        private float relativeSpacing;
        public float RelativeSpacing
        {
            get { return relativeSpacing; }
            set
            {
                relativeSpacing = MathHelper.Clamp(value, -1, 1);
                needsToRecalculate = true;
            }
        }

        private Anchor childAnchor;
        public Anchor ChildAnchor
        {
            get { return childAnchor; }
            set
            {
                childAnchor = value;
                needsToRecalculate = true;
            }
        }

        private bool needsToRecalculate;
        public bool NeedsToRecalculate
        {
            get { return needsToRecalculate; }
            set 
            {
                if (value) { needsToRecalculate = true; }
            }
        }

        public GUILayoutGroup(RectTransform rectT, bool isHorizontal = false, Anchor childAnchor = Anchor.TopLeft) : base(null, rectT)
        {
            CanBeFocused = false;
            this.isHorizontal = isHorizontal;
            this.childAnchor = childAnchor;
            rectT.ChildrenChanged += (child) => needsToRecalculate = true;
            rectT.ScaleChanged += () => needsToRecalculate = true;
            rectT.SizeChanged += () => needsToRecalculate = true;
        }

        public void Recalculate()
        {
            float stretchFactor = 1.0f;
            if (stretch && RectTransform.Children.Count() > 0)
            {
                foreach (RectTransform child in RectTransform.Children)
                {
                    if (child.GUIComponent.IgnoreLayoutGroups) { continue; }

                    switch (child.ScaleBasis)
                    {
                        case ScaleBasis.BothHeight:
                        case ScaleBasis.Smallest when Rect.Height <= Rect.Width:
                        case ScaleBasis.Largest when Rect.Height > Rect.Width:
                            child.MinSize = new Point((int)((child.Rect.Height * child.RelativeSize.X) / child.RelativeSize.Y), child.MinSize.Y);
                            break;
                        case ScaleBasis.BothWidth:
                        case ScaleBasis.Smallest when Rect.Width <= Rect.Height:
                        case ScaleBasis.Largest when Rect.Width > Rect.Height:
                            child.MinSize = new Point(child.MinSize.X, (int)((child.Rect.Width * child.RelativeSize.Y) / child.RelativeSize.X));
                            break;
                    }
                }

                float minSize = RectTransform.Children
                    .Where(c => !c.GUIComponent.IgnoreLayoutGroups)
                    .Sum(c => isHorizontal ? (c.IsFixedSize ? c.NonScaledSize.X : c.MinSize.X) : (c.IsFixedSize ? c.NonScaledSize.Y : c.MinSize.Y));

                float totalSize = RectTransform.Children
                    .Where(c => !c.GUIComponent.IgnoreLayoutGroups)
                    .Sum(c => isHorizontal ?
                        (c.IsFixedSize ? c.Rect.Width : MathHelper.Clamp(c.Rect.Width, c.MinSize.X, c.MaxSize.X)) :
                        (c.IsFixedSize ? c.Rect.Height : MathHelper.Clamp(c.Rect.Height, c.MinSize.Y, c.MaxSize.Y)));

                float thisSize = (isHorizontal ? Rect.Width : Rect.Height);

                totalSize +=
                    (RectTransform.Children.Count(c => !c.GUIComponent.IgnoreLayoutGroups) - 1) * 
                    (absoluteSpacing + relativeSpacing * thisSize);

                stretchFactor = totalSize <= 0.0f || minSize >= thisSize || totalSize == minSize ? 
                    1.0f : 
                    (thisSize - minSize) / (totalSize - minSize);
            }

            int absPos = 0;
            float relPos = 0;
            foreach (var child in RectTransform.Children)
            {
                if (child.GUIComponent.IgnoreLayoutGroups) { continue; }

                float currentStretchFactor = child.ScaleBasis == ScaleBasis.Normal ? stretchFactor : 1.0f;
                child.SetPosition(childAnchor);

                void advancePositionsAndCalculateChildSizes(
                    ref int childNonScaledSize,
                    ref float childRelativeSize,
                    int childMinSize,
                    int childMaxSize,
                    int childRectSize,
                    int selfRectSize)
                {
                    if (child.IsFixedSize)
                    {
                        absPos += childNonScaledSize + absoluteSpacing;
                    }
                    else
                    {
                        absPos += (int)Math.Round(MathHelper.Clamp(childRectSize * currentStretchFactor, childMinSize, childMaxSize) + (absoluteSpacing * currentStretchFactor));
                        if (stretch)
                        {
                            float relativeSize =
                                MathF.Round(childRelativeSize * currentStretchFactor * selfRectSize) / selfRectSize;
                            childRelativeSize = relativeSize;
                        }
                    }
                }

                Point childNonScaledSize = child.NonScaledSize;
                Vector2 childRelativeSize = child.RelativeSize;
                if (isHorizontal)
                {
                    child.RelativeOffset = new Vector2(relPos, child.RelativeOffset.Y);
                    child.AbsoluteOffset = new Point(absPos, child.AbsoluteOffset.Y);
                    advancePositionsAndCalculateChildSizes(
                        ref childNonScaledSize.X,
                        ref childRelativeSize.X,
                        child.MinSize.X,
                        child.MaxSize.X,
                        child.Rect.Width,
                        Rect.Width);
                }
                else
                {
                    child.RelativeOffset = new Vector2(child.RelativeOffset.X, relPos);
                    child.AbsoluteOffset = new Point(child.AbsoluteOffset.X, absPos);
                    advancePositionsAndCalculateChildSizes(
                        ref childNonScaledSize.Y,
                        ref childRelativeSize.Y,
                        child.MinSize.Y,
                        child.MaxSize.Y,
                        child.Rect.Height,
                        Rect.Height);
                }
                child.NonScaledSize = childNonScaledSize;
                child.RelativeSize = childRelativeSize;
                relPos += relativeSpacing * stretchFactor;
                if (isHorizontal) { relPos = MathF.Round(relPos * Rect.Width) / Rect.Width; }
                else { relPos = MathF.Round(relPos * Rect.Height) / Rect.Height; }
            }
            needsToRecalculate = false;
        }

        protected override void Update(float deltaTime)
        {
            base.Update(deltaTime);
            if (needsToRecalculate)
            {
                Recalculate();
            }
        }

        public override void ForceLayoutRecalculation()
        {
            Recalculate();
            base.ForceLayoutRecalculation();
        }
    }
}
