﻿using NUnit.Framework;
using System;
using System.Globalization;
using System.Threading;

namespace Xamarin.Forms.Core.UnitTests
{
	[TestFixture]
	public class FlexLayoutTests : BaseTestFixture
	{
		[Test]
		public void TestBasicLayout()
		{
			var label1 = new Label { IsPlatformEnabled = true };
			var label2 = new Label { IsPlatformEnabled = true };

			var layout = new FlexLayout {
				IsPlatformEnabled = true,
				Children = {
					label1,
					label2,
				}
			};

			layout.Layout(new Rectangle(0, 0, 912, 912));

			Assert.That(layout.Bounds, Is.EqualTo(new Rectangle(0, 0, 912, 912)));
			Assert.That(label1.Bounds, Is.EqualTo(new Rectangle(0, 0, 100, 912)));
			Assert.That(label2.Bounds, Is.EqualTo(new Rectangle(100, 0, 100, 912)));
		}

		[Test]
		public void TestBasicLayoutWithElementsWidth()
		{
			var label1 = new Label { IsPlatformEnabled = true, WidthRequest = 120 };
			var label2 = new Label { IsPlatformEnabled = true, WidthRequest = 120 };

			var layout = new FlexLayout {
				IsPlatformEnabled = true,
				Children = {
					label1,
					label2,
				}
			};

			layout.Layout(new Rectangle(0, 0, 912, 912));

			Assert.That(layout.Bounds, Is.EqualTo(new Rectangle(0, 0, 912, 912)));
			Assert.That(label1.Bounds, Is.EqualTo(new Rectangle(0, 0, 120, 912)));
			Assert.That(label2.Bounds, Is.EqualTo(new Rectangle(120, 0, 120, 912)));

		}

		[Test]
		public void TestBasicLayoutWithElementsWidthAndMargin()
		{
			var label1 = new Label { IsPlatformEnabled = true, WidthRequest = 100, Margin = new Thickness(5, 0, 0, 0) };
			var label2 = new Label { IsPlatformEnabled = true, WidthRequest = 100, Margin = new Thickness(5, 0, 0, 0) };

			var layout = new FlexLayout {
				IsPlatformEnabled = true,
				Children = {
					label1,
					label2,
				}
			};

			layout.Layout(new Rectangle(0, 0, 912, 912));

			Assert.AreEqual(912, layout.Width);
			Assert.AreEqual(912, layout.Height);

			Assert.AreEqual(new Rectangle(5, 0, 100, 912), label1.Bounds);
			Assert.AreEqual(new Rectangle(110, 0, 100, 912), label2.Bounds);
		}

		[Test]
		public void TestSetBounds()
		{
			var layoutSize = new Size(320, 50);

			var layout = new FlexLayout {
				Direction = FlexDirection.Row,
				AlignItems = FlexAlignItems.Start,
				IsPlatformEnabled = true
			};

			var label1 = new Label { IsPlatformEnabled = true };
			FlexLayout.SetGrow(label1, 1);
			layout.Children.Add(label1);

			var label2 = new Label { IsPlatformEnabled = true };
			FlexLayout.SetGrow(label2, 1);
			layout.Children.Add(label2);

			var label3 = new Label { IsPlatformEnabled = true };
			FlexLayout.SetGrow(label3, 1);
			layout.Children.Add(label3);

			layout.Layout(new Rectangle(0, 0, layoutSize.Width, layoutSize.Height));

			Assert.AreEqual(label2.Bounds.Left, Math.Max(label1.Bounds.Left, label1.Bounds.Right), 1);
			Assert.AreEqual(label3.Bounds.Left, Math.Max(label2.Bounds.Left, label2.Bounds.Right), 1);

			double totalWidth = 0;
			foreach (var view in layout.Children)
			{
				totalWidth += view.Bounds.Width;
			}

			Assert.AreEqual(layoutSize.Width, totalWidth, 2);
		}

		[Test]
		public void TestRelayoutOnChildrenRemoved()
		{
			var layoutSize = new Size(300, 50);

			var layout = new FlexLayout {
				IsPlatformEnabled = true,
				Direction = FlexDirection.Row
			};

			var label1 = new Label { IsPlatformEnabled = true };
			FlexLayout.SetGrow(label1, 1);
			layout.Children.Add(label1);

			var label2 = new Label { IsPlatformEnabled = true };
			FlexLayout.SetGrow(label2, 1);
			layout.Children.Add(label2);

			var label3 = new Label { IsPlatformEnabled = true };
			FlexLayout.SetGrow(label3, 1);
			layout.Children.Add(label3);

			layout.Layout(new Rectangle(0, 0, layoutSize.Width, layoutSize.Height));

			foreach (var view in layout.Children)
				Assert.That(view.Bounds.Width, Is.EqualTo(100));

			layout.Children.Remove(label3);

			Assert.That(label1.Bounds.Width, Is.EqualTo(150));
			Assert.That(label2.Bounds.Width, Is.EqualTo(150));
			Assert.That(label3.Bounds.Width, Is.EqualTo(100));
		}

		[Test]
		public void TestFlexLayoutIsIncludeChangeWorksOnSecondPass()
		{
			var layoutSize = new Size(300, 50);
			var layout = new FlexLayout {
				Direction = FlexDirection.Row,
				IsPlatformEnabled = true,
			};

			var label1 = new Label { IsPlatformEnabled = true };
			FlexLayout.SetGrow(label1, 1);
			layout.Children.Add(label1);

			var label2 = new Label { IsPlatformEnabled = true };
			FlexLayout.SetGrow(label2, 1);
			layout.Children.Add(label2);

			var label3 = new Label { IsPlatformEnabled = true };
			FlexLayout.SetGrow(label3, 1);

			layout.Layout(new Rectangle(0, 0, layoutSize.Width, layoutSize.Height));

			Assert.AreEqual(150, label1.Bounds.Width);
			Assert.AreEqual(150, label2.Bounds.Width);
			Assert.AreEqual(-1, label3.Bounds.Width);

			layout.Children.Add(label3);

			layout.Layout(new Rectangle(0, 0, layoutSize.Width, layoutSize.Height));
			Assert.AreEqual(100, label1.Bounds.Width);
			Assert.AreEqual(100, label2.Bounds.Width);
			Assert.AreEqual(100, label3.Bounds.Width);
		}

		[Test]
		// fixed at https://github.com/xamarin/flex/commit/0ccb9f1625abdc5400def29651373937bf6610cd
		public void TestSwapChildrenOrder()
		{
			var layoutSize = new Size(300, 50);

			var layout = new FlexLayout { 
				Direction = FlexDirection.Row,
				IsPlatformEnabled = true,
			};

			var label0 = new Label { IsPlatformEnabled = true, Text="Label0" };
			FlexLayout.SetGrow(label0, 1);
			layout.Children.Add(label0);

			var label1 = new Label { IsPlatformEnabled = true, Text = "Label1" };
			FlexLayout.SetGrow(label1, 1);
			layout.Children.Add(label1);

			var label2 = new Label { IsPlatformEnabled = true, Text = "Label2" };
			FlexLayout.SetGrow(label2, 1);
			layout.Children.Add(label2);

			layout.Layout(new Rectangle(0, 0, layoutSize.Width, layoutSize.Height));

			Assert.AreEqual(new Rectangle(0, 0, 100, 50), label0.Bounds);
			Assert.AreEqual(new Rectangle(100, 0, 100, 50), label1.Bounds);
			Assert.AreEqual(new Rectangle(200, 0, 100, 50), label2.Bounds);

			var lastItem = layout.Children[2];
			Assert.That(lastItem, Is.SameAs(label2));

			layout.Children.Remove(lastItem);
			Assert.AreEqual(new Rectangle(0, 0, 150, 50), label0.Bounds);
			Assert.AreEqual(new Rectangle(150, 0, 150, 50), label1.Bounds);

			layout.Children.Insert(0, lastItem);

			Assert.AreEqual(new Rectangle(0, 0, 100, 50), label2.Bounds);
			Assert.AreEqual(new Rectangle(100, 0, 100, 50), label0.Bounds);
			Assert.AreEqual(new Rectangle(200, 0, 100, 50), label1.Bounds);
		}

		[Test]
		public void TestSizeThatFits()
		{
			Device.PlatformServices = new MockPlatformServices(useRealisticLabelMeasure:true);

			var layout = new FlexLayout {
				Direction = FlexDirection.Row,
				AlignItems = FlexAlignItems.Start,
				IsPlatformEnabled = true
			};

			var label1 = new Label {
				IsPlatformEnabled = true,
				LineBreakMode = LineBreakMode.TailTruncation,
				Text = @"This is a very very very very very very very very long piece of text."
			};
			FlexLayout.SetShrink(label1, 1);
			layout.Children.Add(label1);

			var label2 = new Label {
				Text = "",
				IsPlatformEnabled = true,
				WidthRequest = 10,
				HeightRequest = 10
			};
			layout.Children.Add(label2);
			layout.Layout(new Rectangle(0, 0, 320, 50));

			var label2Size = label2.Measure(double.PositiveInfinity, double.PositiveInfinity);
			Assert.AreEqual(10, label2Size.Request.Height);
			Assert.AreEqual(10, label2Size.Request.Width);

			var label1Size = label1.Measure(double.PositiveInfinity, double.PositiveInfinity);
			//	var layoutSize = layout.Measure(-1, -1);
		}

		[Test]
		public void TestNesting()
		{
			var header = new View { HeightRequest = 50, IsPlatformEnabled = true, };
			var footer = new View { HeightRequest = 50, IsPlatformEnabled = true, };
			Func<View> createItem = () => {
				var v = new View { WidthRequest = 50, Margin = 5, IsPlatformEnabled = true, };
				FlexLayout.SetGrow(v, 1);
				return v;
			};

			var layout = new FlexLayout
			{
				IsPlatformEnabled = true,
				Children = {
					header,
					new FlexLayout{
						Direction = FlexDirection.Row,
						IsPlatformEnabled = true,
						Children = {
							createItem(),
							createItem(),
							createItem(),
							createItem(),
							createItem(),
						}
					},
					footer,
				},
				Direction = FlexDirection.Column,
			};

			var inner = layout.Children[1] as FlexLayout;
			FlexLayout.SetGrow(inner, 1);

			layout.Layout(new Rectangle(0, 0, 300, 600));
			Assert.That(layout.Bounds, Is.EqualTo(new Rectangle(0, 0, 300, 600)));
			Assert.That(header.Bounds, Is.EqualTo(new Rectangle(0, 0, 300, 50)));
			Assert.That(inner.Bounds, Is.EqualTo(new Rectangle(0, 50, 300, 500)));
			Assert.That(inner.Children[0].Bounds, Is.EqualTo(new Rectangle(5, 5, 50, 490)));
			Assert.That(inner.Children[1].Bounds, Is.EqualTo(new Rectangle(65, 5, 50, 490)));
			Assert.That(inner.Children[2].Bounds, Is.EqualTo(new Rectangle(125, 5, 50, 490)));
			Assert.That(inner.Children[3].Bounds, Is.EqualTo(new Rectangle(185, 5, 50, 490)));
			Assert.That(inner.Children[4].Bounds, Is.EqualTo(new Rectangle(245, 5, 50, 490)));

			Assert.That(footer.Bounds, Is.EqualTo(new Rectangle(0, 550, 300, 50)));
		}

		[Test]
		public void TestMeasuring()
		{
			var label = new Label {
				IsPlatformEnabled = true,
			};
			var Layout = new FlexLayout {
				IsPlatformEnabled = true,
				Direction = FlexDirection.Row,
				Wrap = FlexWrap.Wrap,
				Children = { 
					label,
				}
			};

			//measure sith +inf as main-axis
			var measure = Layout.Measure(double.PositiveInfinity, 40);
			Assert.That(measure.Request, Is.EqualTo(new Size(100, 40)));

			//measure sith +inf as cross-axis
			measure = Layout.Measure(200, double.PositiveInfinity);
			Assert.That(measure.Request, Is.EqualTo(new Size(200, 20)));

			//measure with +inf as both axis
			measure = Layout.Measure(double.PositiveInfinity, double.PositiveInfinity);
			Assert.That(measure.Request, Is.EqualTo(new Size(100, 20)));

		}

		[Test]
		public void TestMarginsWithWrap()
		{
			var label0 = new Label {
				IsPlatformEnabled = true,
				Margin=6,
			};
			var label1 = new Label {
				IsPlatformEnabled = true,
				Margin=6,
			};
			var label2 = new Label {
				IsPlatformEnabled = true,
			};
			FlexLayout.SetGrow(label0, 0);
			FlexLayout.SetBasis(label0, new FlexBasis(.5f, true));
			FlexLayout.SetGrow(label1, 0);
			FlexLayout.SetBasis(label1, new FlexBasis(.5f, true));
			FlexLayout.SetGrow(label2, 0);
			FlexLayout.SetBasis(label2, new FlexBasis(1f, true));
			var layout = new FlexLayout {
				IsPlatformEnabled = true,
				Direction = FlexDirection.Row,
				Wrap = FlexWrap.Wrap,
				AlignItems = FlexAlignItems.Start,
				AlignContent = FlexAlignContent.Start,
				Children = {
					label0,
					label1,
					label2,
				}
			};

			var measure = layout.Measure(300, double.PositiveInfinity);
			Assert.That(measure.Request, Is.EqualTo(new Size(300, 52)));
			layout.Layout(new Rectangle(0, 0, 300, 300));
			Assert.That(label0.Bounds, Is.EqualTo(new Rectangle(6, 6, 138, 20)));
			Assert.That(label1.Bounds, Is.EqualTo(new Rectangle(156, 6, 138, 20)));
			Assert.That(label2.Bounds, Is.EqualTo(new Rectangle(0, 32, 300, 20)));
		}

		[Test]
		//https://github.com/xamarin/Xamarin.Forms/issues/2551
		public void TestReverseWithGrow()
		{
			var label0 = new Label {
				IsPlatformEnabled = true,
			};
			FlexLayout.SetGrow(label0, 1);
			var layout = new FlexLayout {
				IsPlatformEnabled = true,
				Direction = FlexDirection.ColumnReverse,
				Children = {
					label0,
				}
			};

			layout.Layout(new Rectangle(0, 0, 300, 300));
			Assert.That(label0.Bounds, Is.EqualTo(new Rectangle(0, 0, 300, 300)));
		}

		[Test]
		public void TestIsVisible()
		//https://github.com/xamarin/Xamarin.Forms/issues/2593
		{
			var label0 = new Label {
				IsPlatformEnabled = true,
			};
			var label1 = new Label {
				IsPlatformEnabled = true,
			};
			var label2 = new Label {
				IsPlatformEnabled = true,
			};
			var layout = new FlexLayout {
				IsPlatformEnabled = true,
				Direction = FlexDirection.Column,
				Children = {
					label0,
					label1,
					label2,
				}
			};

			layout.Layout(new Rectangle(0, 0, 300, 300));
			Assert.That(label0.Bounds, Is.EqualTo(new Rectangle(0, 0, 300, 20)));
			Assert.That(label1.Bounds, Is.EqualTo(new Rectangle(0, 20, 300, 20)));
			Assert.That(label2.Bounds, Is.EqualTo(new Rectangle(0, 40, 300, 20)));

			label1.IsVisible = false;
			Assert.That(label0.Bounds, Is.EqualTo(new Rectangle(0, 0, 300, 20)));
			Assert.That(label2.Bounds, Is.EqualTo(new Rectangle(0, 20, 300, 20)));

			label0.IsVisible = false;
			label1.IsVisible = true;
			Assert.That(label1.Bounds, Is.EqualTo(new Rectangle(0, 0, 300, 20)));
			Assert.That(label2.Bounds, Is.EqualTo(new Rectangle(0, 20, 300, 20)));
		}

		[Test]
		public void ChangingGrowTriggersLayout()
		//https://github.com/xamarin/Xamarin.Forms/issues/2821
		{
			var layout = new FlexLayout {
				IsPlatformEnabled = true,
				Direction = FlexDirection.Column,
			};

			layout.Layout(new Rectangle(0, 0, 300, 300));
			for (var i = 0; i < 3; i++) {
				var box = new BoxView {
					IsPlatformEnabled = true,
				};
				layout.Children.Add(box);
				FlexLayout.SetGrow(box, 1f);
			}

			Assert.That(layout.Children[2].Bounds, Is.EqualTo(new Rectangle(0, 200, 300, 100)));
		}

		[Test]
		public void PaddingOnLayout()
		//https://github.com/xamarin/Xamarin.Forms/issues/2663
		{
			var label0 = new Label {
				IsPlatformEnabled = true,
			};
			var label1 = new Label {
				IsPlatformEnabled = true,
			};
			var label2 = new Label {
				IsPlatformEnabled = true,
			};
			var layout = new FlexLayout {
				IsPlatformEnabled = true,
				JustifyContent = FlexJustify.SpaceBetween,
				AlignItems = FlexAlignItems.Start,

				Padding = new Thickness(20,10,20,0),
				Children = {
					label0,
					label1,
					label2,
				}
			};

			layout.Layout(new Rectangle(0, 0, 500, 300));
			Assert.That(layout.Children[0].Bounds, Is.EqualTo(new Rectangle(20, 10, 100, 20)));
			Assert.That(layout.Children[2].Bounds, Is.EqualTo(new Rectangle(380, 10, 100, 20)));
		}
	}
}
