﻿/* 
MIT License

Copyright (c) 2016-2017 Florian Cäsar, Michael Plainer

For full license see LICENSE in the root directory of this project. 
*/

using System.Windows;
using System.Windows.Controls;

//SOURCE: (with adaptations)
//https://daniel-albuschat.blogspot.co.at/2011/07/gridlayout-for-wpf-escape-margin-hell.html

namespace Sigma.Core.Monitors.WPF.ViewModel.CustomControls
{
	// The GridLayout is a special Panel that can be used exactly like the Grid Panel, except that it
	// defines a new property ChildMargin. ChildMargin's left, top, right and bottom margins will be applied
	// to all children in a way that the children will have a vertical space of ChildMargin.Top+ChildMargin.Bottom
	// and a horizontal space of ChildMargin.Left+ChildMargin.Right between them.
	// However, there is no margin for the borders of the internal widget, so that the GridLayout itself can be
	// aligned to another element without a margin.
	// It's best to have a look at TestWindow, which effectively tests all possible alignments of children.

	public class GridLayout : Grid
	{
		public static readonly DependencyProperty ChildMarginProperty = DependencyProperty.Register(
			"ChildMargin",
			typeof(Thickness),
			typeof(GridLayout),
			new FrameworkPropertyMetadata(new Thickness(5))
			{
				AffectsArrange = true,
				AffectsMeasure = true
			});

		public new Thickness Margin
		{
			get { return ChildMargin; }
			set { ChildMargin = value; }
		}

		// The child margin defines a margin that will be automatically applied to all children of this Grid.
		// However, the children at the edges will have the respective margins remove. E.g. the leftmost children will have
		// a Margin.Left of 0 and the children in the first row will have a Margin.Top of 0.
		// The margins that are not set to 0 are set to half the ChildMargin's value, since it's neighbour will also apply it,
		// effectively doubling it.

		public Thickness ChildMargin
		{
			get { return (Thickness) GetValue(ChildMarginProperty); }
			set
			{
				SetValue(ChildMarginProperty, value);
				UpdateChildMargins();
			}
		}

		// UpdateChildMargin first finds out what's the rightmost column and bottom row and then applies
		// the correct margins to all children.

		public void UpdateChildMargins()
		{
			int maxColumn = 0;
			int maxRow = 0;
			foreach (UIElement element in InternalChildren)
			{
				int row = GetRow(element);
				int rowSpan = GetRowSpan(element);
				int column = GetColumn(element);
				int columnSpan = GetColumnSpan(element);

				if (row + rowSpan > maxRow)
				{
					maxRow = row + rowSpan;
				}
				if (column + columnSpan > maxColumn)
				{
					maxColumn = column + columnSpan;
				}
			}
			foreach (UIElement element in InternalChildren)
			{
				FrameworkElement fe = element as FrameworkElement;
				if (null != fe)
				{
					int row = GetRow(fe);
					int rowSpan = GetRowSpan(fe);
					int column = GetColumn(fe);
					int columnSpan = GetColumnSpan(fe);
					double factorLeft = 0.5;
					double factorTop = 0.5;
					double factorRight = 0.5;
					double factorBottom = 0.5;
					// Top row - no top margin
					if (row == 0)
					{
						factorTop = 1;
					}
					// Bottom row - no bottom margin
					if (row + rowSpan >= maxRow)
					{
						factorBottom = 1;
					}
					// Leftmost column = no left margin
					if (column == 0)
					{
						factorLeft = 1;
					}
					// Rightmost column - no right margin
					if (column + columnSpan >= maxColumn)
					{
						factorRight = 1;
					}
					fe.Margin = new Thickness(ChildMargin.Left*factorLeft,
						ChildMargin.Top*factorTop,
						ChildMargin.Right*factorRight,
						ChildMargin.Bottom*factorBottom);
				}
			}
		}

		// We change all children's margins in MeasureOverride, since this is called right before
		// the layouting takes place. I was first skeptical to do this here, because I thought changing
		// the margin will trigger a LayoutUpdate, which in turn would lead to an endless recursion,
		// but apparantly WPF takes care of this.

		protected override Size MeasureOverride(Size availableSize)
		{
			UpdateChildMargins();
			return base.MeasureOverride(availableSize);
		}
	}
}