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

namespace InABox.Mobile
{

	struct LayoutData
	{
		public int VisibleChildCount { get; private set; }

		public Size CellSize { get; private set; }

		public int Rows { get; private set; }

		public int Columns { get; private set; }

		public LayoutData(int visibleChildCount, Size cellSize, int rows, int columns)
		{
			VisibleChildCount = visibleChildCount;
			CellSize = cellSize;
			Rows = rows;
			Columns = columns;
		}
	}

	public enum ContentAlignment
    {
		Start = 0,
		Center,
		End
    }

	public class WrapLayout : Layout<View>
	{
		Dictionary<Size, LayoutData> layoutDataCache = new Dictionary<Size, LayoutData>();

		public static readonly BindableProperty CellPaddingProperty = BindableProperty.Create(
					"CellPadding",
					typeof(double),
					typeof(WrapLayout),
					10.0,
					propertyChanged: (bindable, oldvalue, newvalue) =>
					{
						((WrapLayout)bindable).InvalidateLayout();
					});

		public double CellPadding
		{
			set { SetValue(CellPaddingProperty, value); }
			get { return (double)GetValue(CellPaddingProperty); }
		}

		public static readonly BindableProperty HorizontalContentAlignmentProperty = BindableProperty.Create(
					"HorizontalContentAlignment",
					typeof(ContentAlignment),
					typeof(WrapLayout),
					ContentAlignment.Center,
					propertyChanged: (bindable, oldvalue, newvalue) =>
					{
						((WrapLayout)bindable).InvalidateLayout();
					});

		public ContentAlignment HorizontalContentAlignment
		{
			set { SetValue(HorizontalContentAlignmentProperty, value); }
			get { return (ContentAlignment)GetValue(HorizontalContentAlignmentProperty); }
		}

		public static readonly BindableProperty VerticalContentAlignmentProperty = BindableProperty.Create(
			"VerticalContentAlignment",
			typeof(ContentAlignment),
			typeof(WrapLayout),
			ContentAlignment.Center,
			propertyChanged: (bindable, oldvalue, newvalue) =>
			{
				((WrapLayout)bindable).InvalidateLayout();
			});

		public ContentAlignment VerticalContentAlignment
		{
			set { SetValue(VerticalContentAlignmentProperty, value); }
			get { return (ContentAlignment)GetValue(VerticalContentAlignmentProperty); }
		}


		protected override SizeRequest OnMeasure(double widthConstraint, double heightConstraint)
		{
			Size totalSize = CalculateSize(widthConstraint, heightConstraint);
			return new SizeRequest(totalSize);
		}

		public Size CalculateSize(double widthConstraint, double heightConstraint)
        {
			LayoutData layoutData = GetLayoutData(widthConstraint, heightConstraint);
			if (layoutData.VisibleChildCount == 0)
			{
				return new Size();
			}

			Size totalSize = new Size(layoutData.CellSize.Width * layoutData.Columns, layoutData.CellSize.Height * layoutData.Rows);

			return totalSize;
		}

		protected override void LayoutChildren(double x, double y, double width, double height)
		{
			LayoutData layoutData = GetLayoutData(width, height);

			if (layoutData.VisibleChildCount == 0)
			{
				return;
			}

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

			foreach (View child in Children)
			{
				if (!child.IsVisible)
				{
					continue;
				}

				SizeRequest csr = child.Measure(Double.PositiveInfinity, Double.PositiveInfinity);

				double xOffSet = HorizontalContentAlignment.Equals(ContentAlignment.Start) ? CellPadding / 2.0F : HorizontalContentAlignment.Equals(ContentAlignment.Center) ? (layoutData.CellSize.Width - csr.Request.Width) / 2.0F : layoutData.CellSize.Width - (csr.Request.Width + (CellPadding / 2.0F));

				double yOffSet = VerticalContentAlignment.Equals(ContentAlignment.Start) ? CellPadding / 2.0F : VerticalContentAlignment.Equals(ContentAlignment.Center) ? (layoutData.CellSize.Height - csr.Request.Height) / 2.0F : layoutData.CellSize.Height - (csr.Request.Height + (CellPadding / 2.0F));

				LayoutChildIntoBoundingRegion(
					child,
					new Rectangle(
						new Point(xChild + xOffSet, yChild + yOffSet),
						new Size( layoutData.CellSize.Width - (xOffSet * 2.0F), layoutData.CellSize.Height - (yOffSet * 2.0F))
					)
				);

				if (++column == layoutData.Columns)
				{
					column = 0;
					row++;
					xChild = x;
					yChild += layoutData.CellSize.Height;
				}
				else
				{
					xChild += layoutData.CellSize.Width;
				}
			}
		}

		LayoutData GetLayoutData(double width, double height)
		{
			Size size = new Size(width, height);

			// Check if cached information is available.
			if (layoutDataCache.ContainsKey(size))
			{
				return layoutDataCache[size];
			}

			int visibleChildCount = 0;
			Size maxChildSize = new Size();
			int rows = 0;
			int columns = 0;
			LayoutData layoutData = new LayoutData();

			// Enumerate through all the children.
			foreach (View child in Children)
			{
				// Skip invisible children.
				if (!child.IsVisible)
					continue;

				// Count the visible children.
				visibleChildCount++;

				// Get the child's requested size.
				SizeRequest childSizeRequest = child.Measure(Double.PositiveInfinity, Double.PositiveInfinity);

				// Accumulate the maximum child size.
				maxChildSize.Width = Math.Max(maxChildSize.Width, childSizeRequest.Request.Width + CellPadding);
				maxChildSize.Height = Math.Max(maxChildSize.Height, childSizeRequest.Request.Height + CellPadding);
			}

			if (visibleChildCount != 0)
			{
				// Calculate the number of rows and columns.
				if (Double.IsPositiveInfinity(width))
				{
					columns = visibleChildCount;
					rows = 1;
				}
				else
				{
					columns = (int)(width / maxChildSize.Width);
					columns = Math.Max(1, columns);
					rows = (visibleChildCount + columns - 1) / columns;
				}

				// Now maximize the cell size based on the layout size.
				Size cellSize = new Size();

				if (Double.IsPositiveInfinity(width))
				{
					cellSize.Width = maxChildSize.Width;
				}
				else
				{
					cellSize.Width = width / columns;
				}

                if (Double.IsPositiveInfinity(height) || VerticalOptions.Alignment.Equals(LayoutAlignment.Start))
                {
                    cellSize.Height = maxChildSize.Height;
                }
                else
                {
					
                    cellSize.Height = height / rows;
                }

                layoutData = new LayoutData(visibleChildCount, cellSize, rows, columns);
			}

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

		protected override void InvalidateLayout()
		{
			base.InvalidateLayout();

			// Discard all layout information for children added or removed.
			layoutDataCache.Clear();
		}

		protected override void OnChildMeasureInvalidated()
		{
			base.OnChildMeasureInvalidated();

			// Discard all layout information for child size changed.
			layoutDataCache.Clear();
		}
	}
}