using System;
using System.Collections;
using System.Collections.Generic;
using Xamarin.Forms;

namespace Billing.UI
{
    public class WrapLayout : Layout<View>
    {
        public static readonly BindableProperty ItemsSourceProperty = Helper.Create<IList, WrapLayout>(nameof(ItemsSource), propertyChanged: OnItemsSourcePropertyChanged);
        public static readonly BindableProperty ColumnSpacingProperty = Helper.Create<double, WrapLayout>(nameof(ColumnSpacing), defaultValue: 4d, propertyChanged: (layout, _, _) => layout.InvalidateLayout());
        public static readonly BindableProperty RowSpacingProperty = Helper.Create<double, WrapLayout>(nameof(RowSpacing), defaultValue: 4d, propertyChanged: (layout, _, _) => layout.InvalidateLayout());

        private static void OnItemsSourcePropertyChanged(WrapLayout layout, IList old, IList list)
        {
            var itemTemplate = BindableLayout.GetItemTemplate(layout);
            if (itemTemplate == null)
            {
                return;
            }
            if (list == null)
            {
                layout.Children.Clear();
                layout.InvalidateLayout();
            }
            else
            {
                layout.freezed = true;
                layout.Children.Clear();
                foreach (var item in list)
                {
                    var child = itemTemplate.CreateContent();
                    if (child is View view)
                    {
                        view.BindingContext = item;
                        layout.Children.Add(view);
                    }
                }
                layout.freezed = false;
                layout.InvalidateLayout();
            }
        }

        public IList ItemsSource
        {
            get => (IList)GetValue(ItemsSourceProperty);
            set => SetValue(ItemsSourceProperty, value);
        }
        public double ColumnSpacing
        {
            get => (double)GetValue(ColumnSpacingProperty);
            set => SetValue(ColumnSpacingProperty, value);
        }
        public double RowSpacing
        {
            get => (double)GetValue(RowSpacingProperty);
            set => SetValue(RowSpacingProperty, value);
        }

        private readonly Dictionary<Size, LayoutData> layoutDataCache = new();

        private bool freezed;

        protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
        {
            var data = GetLayoutData(widthConstraint, heightConstraint);
            if (data.VisibleChildrenCount == 0)
            {
                return new SizeRequest();
            }
            var totalSize = new Size(
                data.CellSize.Width * data.Columns + ColumnSpacing * (data.Columns - 1),
                data.CellSize.Height * data.Rows + RowSpacing * (data.Rows - 1));
            return new SizeRequest(totalSize);
        }

        protected override void LayoutChildren(double x, double y, double width, double height)
        {
            if (freezed)
            {
                return;
            }
            var data = GetLayoutData(width, height);
            if (data.VisibleChildrenCount == 0)
            {
                return;
            }

            double xChild = x;
            double yChild = y;
            int row = 0;
            int column = 0;

            var columnSpacing = ColumnSpacing;
            var rowSpacing = RowSpacing;

            foreach (View child in Children)
            {
                if (!child.IsVisible)
                {
                    continue;
                }
                LayoutChildIntoBoundingRegion(child, new Rectangle(new Point(xChild, yChild), data.CellSize));
                if (++column == data.Columns)
                {
                    column = 0;
                    row++;
                    xChild = x;
                    yChild += rowSpacing + data.CellSize.Height;
                }
                else
                {
                    xChild += columnSpacing + data.CellSize.Width;
                }
            }
        }

        protected override void InvalidateLayout()
        {
            base.InvalidateLayout();
            layoutDataCache.Clear();
        }

        protected override void OnChildMeasureInvalidated()
        {
            base.OnChildMeasureInvalidated();
            layoutDataCache.Clear();
        }

        private LayoutData GetLayoutData(double width, double height)
        {
            Size size = new(width, height);
            if (layoutDataCache.ContainsKey(size))
            {
                return layoutDataCache[size];
            }

            int visibleChildrenCount = 0;
            Size maxChildSize = new();
            LayoutData layoutData;

            foreach (View child in Children)
            {
                if (!child.IsVisible)
                {
                    continue;
                }
                visibleChildrenCount++;
                SizeRequest childSizeRequest = child.Measure(double.PositiveInfinity, double.PositiveInfinity);
                maxChildSize.Width = Math.Max(maxChildSize.Width, childSizeRequest.Request.Width);
                maxChildSize.Height = Math.Max(maxChildSize.Height, childSizeRequest.Request.Height);
            }

            if (visibleChildrenCount > 0)
            {
                var columnSpacing = ColumnSpacing;
                int rows;
                int columns;
                if (double.IsPositiveInfinity(width))
                {
                    columns = visibleChildrenCount;
                    rows = 1;
                }
                else
                {
                    columns = (int)((width + columnSpacing) / (maxChildSize.Width + columnSpacing));
                    columns = Math.Max(1, columns);
                    rows = (visibleChildrenCount + columns - 1) / columns;
                }
                Size cellSize = new();
                if (double.IsPositiveInfinity(width))
                {
                    cellSize.Width = maxChildSize.Width;
                }
                else
                {
                    cellSize.Width = (width - columnSpacing * (columns - 1)) / columns;
                }
                //if (double.IsPositiveInfinity(height))
                {
                    cellSize.Height = maxChildSize.Height;
                }
                //else
                //{
                //    cellSize.Height = (height - RowSpacing * (rows - 1)) / rows;
                //}
                layoutData = new LayoutData(visibleChildrenCount, cellSize, rows, columns);
            }
            else
            {
                layoutData = new LayoutData();
            }

            layoutDataCache.Add(size, layoutData);
            return layoutData;
        }
    }

    public class LayoutData
    {
        public int VisibleChildrenCount { get; set; }
        public Size CellSize { get; set; }
        public int Rows { get; set; }
        public int Columns { get; set; }

        public LayoutData() { }
        public LayoutData(int count, Size size, int rows, int columns)
        {
            VisibleChildrenCount = count;
            CellSize = size;
            Rows = rows;
            Columns = columns;
        }
    }
}
