/// Credit Simie
/// Sourced from - http://forum.unity3d.com/threads/flowlayoutgroup.296709/
/// Example http://forum.unity3d.com/threads/flowlayoutgroup.296709/


using System.Collections.Generic;

namespace UnityEngine.UI.Extensions
{
	/// <summary>
	/// Layout Group controller that arranges children in rows, fitting as many on a line until total width exceeds parent bounds
	/// </summary>
	[AddComponentMenu("Layout/Extensions/Flow Layout Group")]
	public class FlowLayoutGroup : LayoutGroup
	{
		public float Spacing = 0f;

		public bool ChildForceExpandWidth = false;
		public bool ChildForceExpandHeight = false;

		private float _layoutHeight;

		public override void CalculateLayoutInputHorizontal()
		{

			base.CalculateLayoutInputHorizontal();

			var minWidth = GetGreatestMinimumChildWidth() + padding.left + padding.right;

			SetLayoutInputForAxis(minWidth, -1, -1, 0);

		}

		public override void SetLayoutHorizontal()
		{
			SetLayout(rectTransform.rect.width, 0, false);
		}

		public override void SetLayoutVertical()
		{
			SetLayout(rectTransform.rect.width, 1, false);
		}

		public override void CalculateLayoutInputVertical()
		{
			_layoutHeight = SetLayout(rectTransform.rect.width, 1, true);
		}

		protected bool IsCenterAlign
		{
			get
			{
				return childAlignment == TextAnchor.LowerCenter || childAlignment == TextAnchor.MiddleCenter ||
					   childAlignment == TextAnchor.UpperCenter;
			}
		}

		protected bool IsRightAlign
		{
			get
			{
				return childAlignment == TextAnchor.LowerRight || childAlignment == TextAnchor.MiddleRight ||
					   childAlignment == TextAnchor.UpperRight;
			}
		}

		protected bool IsMiddleAlign
		{
			get
			{
				return childAlignment == TextAnchor.MiddleLeft || childAlignment == TextAnchor.MiddleRight ||
					   childAlignment == TextAnchor.MiddleCenter;
			}
		}

		protected bool IsLowerAlign
		{
			get
			{
				return childAlignment == TextAnchor.LowerLeft || childAlignment == TextAnchor.LowerRight ||
					   childAlignment == TextAnchor.LowerCenter;
			}
		}

		/// <summary>
		/// Holds the rects that will make up the current row being processed
		/// </summary>
		private readonly IList<RectTransform> _rowList = new List<RectTransform>(); 

		/// <summary>
		/// Main layout method
		/// </summary>
		/// <param name="width">Width to calculate the layout with</param>
		/// <param name="axis">0 for horizontal axis, 1 for vertical</param>
		/// <param name="layoutInput">If true, sets the layout input for the axis. If false, sets child position for axis</param>
		public float SetLayout(float width, int axis, bool layoutInput)
		{
			var groupHeight = rectTransform.rect.height;

			// Width that is available after padding is subtracted
			var workingWidth = rectTransform.rect.width - padding.left - padding.right;

			// Accumulates the total height of the rows, including spacing and padding.
			var yOffset = IsLowerAlign ? (float)padding.bottom : (float)padding.top;

			var currentRowWidth = 0f;
			var currentRowHeight = 0f;

			for (var i = 0; i < rectChildren.Count; i++) {

				// LowerAlign works from back to front
				var index = IsLowerAlign ? rectChildren.Count - 1 - i : i;

				var child = rectChildren[index];

				var childWidth = LayoutUtility.GetPreferredSize(child, 0);
				var childHeight = LayoutUtility.GetPreferredSize(child, 1);

				// Max child width is layout group with - padding
				childWidth = Mathf.Min(childWidth, workingWidth);

				// If adding this element would exceed the bounds of the row,
				// go to a new line after processing the current row
				if (currentRowWidth + childWidth > workingWidth) {

					currentRowWidth -= Spacing;

					// Process current row elements positioning
					if (!layoutInput) {

						var h = CalculateRowVerticalOffset(groupHeight, yOffset, currentRowHeight);
						LayoutRow(_rowList, currentRowWidth, currentRowHeight, workingWidth, padding.left, h, axis);

					}

					// Clear existing row
					_rowList.Clear();

					// Add the current row height to total height accumulator, and reset to 0 for the next row
					yOffset += currentRowHeight;
					yOffset += Spacing;

					currentRowHeight = 0;
					currentRowWidth = 0;

				}

				currentRowWidth += childWidth;
				_rowList.Add(child);

				// We need the largest element height to determine the starting position of the next line
				if (childHeight > currentRowHeight) {
					currentRowHeight = childHeight;
				}

				currentRowWidth += Spacing;
			}

			if (!layoutInput) {
				var h = CalculateRowVerticalOffset(groupHeight, yOffset, currentRowHeight);

				// Layout the final row
				LayoutRow(_rowList, currentRowWidth, currentRowHeight, workingWidth, padding.left, h, axis);
			}

			_rowList.Clear();

			// Add the last rows height to the height accumulator
			yOffset += currentRowHeight;
			yOffset += IsLowerAlign ? padding.top : padding.bottom;

			if (layoutInput) {

				if(axis == 1)
					SetLayoutInputForAxis(yOffset, yOffset, -1, axis);

			}

			return yOffset;
		}

		private float CalculateRowVerticalOffset(float groupHeight, float yOffset, float currentRowHeight)
		{
			float h;

			if (IsLowerAlign) {
				h = groupHeight - yOffset - currentRowHeight;
			} else if (IsMiddleAlign) {
				h = groupHeight*0.5f - _layoutHeight * 0.5f + yOffset;
			} else {
				h = yOffset;
			}
			return h;
		}

		protected void LayoutRow(IList<RectTransform> contents, float rowWidth, float rowHeight, float maxWidth, float xOffset, float yOffset, int axis)
		{
			var xPos = xOffset;

			if (!ChildForceExpandWidth && IsCenterAlign)
				xPos += (maxWidth - rowWidth) * 0.5f;
			else if (!ChildForceExpandWidth && IsRightAlign)
				xPos += (maxWidth - rowWidth);

			var extraWidth = 0f;

			if (ChildForceExpandWidth) {
				extraWidth = (maxWidth - rowWidth)/_rowList.Count;
			}

			for (var j = 0; j < _rowList.Count; j++) {

				var index = IsLowerAlign ? _rowList.Count - 1 - j : j;

				var rowChild = _rowList[index];

				var rowChildWidth = LayoutUtility.GetPreferredSize(rowChild, 0) + extraWidth;
				var rowChildHeight = LayoutUtility.GetPreferredSize(rowChild, 1);

				if (ChildForceExpandHeight)
					rowChildHeight = rowHeight;

				rowChildWidth = Mathf.Min(rowChildWidth, maxWidth);

				var yPos = yOffset;

				if (IsMiddleAlign)
					yPos += (rowHeight - rowChildHeight) * 0.5f;
				else if (IsLowerAlign)
					yPos += (rowHeight - rowChildHeight);

				if (axis == 0)
					SetChildAlongAxis(rowChild, 0, xPos, rowChildWidth);
				else
					SetChildAlongAxis(rowChild, 1, yPos, rowChildHeight);

				xPos += rowChildWidth + Spacing;
			}
		}

		public float GetGreatestMinimumChildWidth()
		{
			var max = 0f;

			for (var i = 0; i < rectChildren.Count; i++) {
				var w = LayoutUtility.GetMinWidth(rectChildren[i]);

				max = Mathf.Max(w, max);
			}

			return max;
		}
	}
}