using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Shapes;

using miew.Enumerable;

namespace agree.Wpf.Util
{
	using Math = System.Math;
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class TreeLayoutPanel : Panel
	{

		static TreeLayoutPanel()
		{
			Control.VerticalContentAlignmentProperty.AddOwner(typeof(TreeLayoutPanel),
					new FrameworkPropertyMetadata(
						VerticalAlignment.Top,
						FrameworkPropertyMetadataOptions.AffectsMeasure |
						FrameworkPropertyMetadataOptions.AffectsArrange |
						FrameworkPropertyMetadataOptions.AffectsParentMeasure |
						FrameworkPropertyMetadataOptions.AffectsParentArrange |
						FrameworkPropertyMetadataOptions.AffectsRender |
						0,
						null,
						(e, o) =>
						{
							VerticalAlignment va = (VerticalAlignment)o;
							return va == VerticalAlignment.Stretch ? VerticalAlignment.Center : va;
						},
						true
					));

			Shape.StrokeProperty.AddOwner(typeof(TreeLayoutPanel),
					new FrameworkPropertyMetadata(
						Brushes.Black,
						0,
						(e, o) =>
						{
							TreeLayoutPanel p = (TreeLayoutPanel)e;
							p.InvalidateVisual();

						},
						null,
						false));

			Shape.StrokeThicknessProperty.AddOwner(typeof(TreeLayoutPanel),
					new FrameworkPropertyMetadata(
						1.0,
						0,
						(e, o) =>
						{
							TreeLayoutPanel p = (TreeLayoutPanel)e;
							p.InvalidateVisual();

						},
						null,
						false));

			//TextBlock.FontSizeProperty.AddOwner(typeof(TreeLayoutPanel),
			//        new FrameworkPropertyMetadata(
			//            15.0,
			//            0,
			//            (e, o) =>
			//            {
			//                TreeLayoutPanel p = (TreeLayoutPanel)e;
			//                p.InvalidateVisual();

			//            },
			//            null,
			//            false));

			//DefaultStyleKeyProperty.OverrideMetadata(typeof(TreeLayoutPanel), new FrameworkPropertyMetadata(typeof(TreeLayoutPanel)));
		}

		public VerticalAlignment VerticalContentAlignment
		{
			get { return (VerticalAlignment)GetValue(Control.VerticalContentAlignmentProperty); }
			set { SetValue(Control.VerticalContentAlignmentProperty, value); }
		}

		public Brush Stroke
		{
			get { return (Brush)GetValue(Shape.StrokeProperty); }
			set { SetValue(Shape.StrokeProperty, value); }
		}

		public Double StrokeThickness
		{
			get { return (Double)GetValue(Shape.StrokeThicknessProperty); }
			set { SetValue(Shape.StrokeThicknessProperty, value); }
		}

		//public Double FontSize
		//{
		//    get { return (Double)GetValue(TextBlock.FontSizeProperty); }
		//    set { SetValue(TextBlock.FontSizeProperty, value); }
		//}


		List<Double> m_layer_heights = new List<Double>();

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		class NodeLayoutInfo
		{
			public List<FrameworkElement> m_children = new List<FrameworkElement>();
			public Point top_handle;
			public Point bot_handle;
			public Double SubTreeWidth;
			public Double pxLeftPosRelativeToParent;
			public Double pxLeftPosRelativeToBoundingBox;
			public Double pxToLeftSibling;
			public Double pxFromTop;
			public Double pxFromLeft;
			public List<Double> lstPosLeftBoundaryRelativeToRoot = new List<Double>();
			public List<Double> lstPosRightBoundaryRelativeToRoot = new List<Double>();
		}

		Dictionary<FrameworkElement, NodeLayoutInfo> nli_dict = new Dictionary<FrameworkElement, NodeLayoutInfo>();

		public static readonly DependencyProperty TreeParentProperty =
			DependencyProperty.RegisterAttached("TreeParent", typeof(FrameworkElement), typeof(TreeLayoutPanel),
			new FrameworkPropertyMetadata(
				default(FrameworkElement),
					FrameworkPropertyMetadataOptions.AffectsMeasure |
					FrameworkPropertyMetadataOptions.AffectsArrange |
					FrameworkPropertyMetadataOptions.AffectsParentMeasure |
					FrameworkPropertyMetadataOptions.AffectsParentArrange |
					FrameworkPropertyMetadataOptions.AffectsRender,
					null,
					null,
					true));

		public static FrameworkElement GetTreeParent(FrameworkElement e)
		{
			return (FrameworkElement)e.GetValue(TreeParentProperty);
		}
		public static void SetTreeParent(FrameworkElement e, FrameworkElement par)
		{
			e.SetValue(TreeParentProperty, par);
		}

		public static readonly DependencyProperty VerticalBufferProperty =
			DependencyProperty.Register(
				"VerticalBuffer",
				typeof(double),
				typeof(TreeLayoutPanel),
				new FrameworkPropertyMetadata(
					10.0,
					FrameworkPropertyMetadataOptions.AffectsMeasure |
					FrameworkPropertyMetadataOptions.AffectsArrange |
					FrameworkPropertyMetadataOptions.AffectsParentMeasure |
					FrameworkPropertyMetadataOptions.AffectsParentArrange |
					FrameworkPropertyMetadataOptions.AffectsRender |
					0,
					null,
					null,
					false
				),
				null
			);

		public double VerticalBuffer
		{
			get { return (double)GetValue(VerticalBufferProperty); }
			set { SetValue(VerticalBufferProperty, value); }
		}

		public readonly static DependencyProperty HorizontalBufferSubtreeProperty =
			DependencyProperty.Register(
				"HorizontalBufferSubtree",
				typeof(double),
				typeof(TreeLayoutPanel),
				new FrameworkPropertyMetadata(
					10.0,
					FrameworkPropertyMetadataOptions.AffectsMeasure |
					FrameworkPropertyMetadataOptions.AffectsArrange |
					FrameworkPropertyMetadataOptions.AffectsParentMeasure |
					FrameworkPropertyMetadataOptions.AffectsParentArrange |
					FrameworkPropertyMetadataOptions.AffectsRender |
					0,
					null,
					null,
					false
				),
				null
			);

		public double HorizontalBufferSubtree
		{
			get { return (double)GetValue(HorizontalBufferSubtreeProperty); }
			set { SetValue(HorizontalBufferSubtreeProperty, value); }
		}

		public readonly static DependencyProperty HorizontalBufferProperty =
			DependencyProperty.Register(
				"HorizontalBuffer",
				typeof(double),
				typeof(TreeLayoutPanel),
				new FrameworkPropertyMetadata(
					10.0,
					FrameworkPropertyMetadataOptions.AffectsMeasure |
					FrameworkPropertyMetadataOptions.AffectsArrange |
					FrameworkPropertyMetadataOptions.AffectsParentMeasure |
					FrameworkPropertyMetadataOptions.AffectsParentArrange |
					FrameworkPropertyMetadataOptions.AffectsRender |
					0,
					null,
					null,
					false
				),
				null
			);

		public double HorizontalBuffer
		{
			get { return (double)GetValue(HorizontalBufferProperty); }
			set { SetValue(HorizontalBufferProperty, value); }
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		IList<FrameworkElement> GetVisibleNodeChildren(FrameworkElement tn)
		{
			NodeLayoutInfo nli;
			if (tn.Visibility != System.Windows.Visibility.Collapsed && nli_dict.TryGetValue(tn, out nli))
				return nli.m_children;

			return new FrameworkElement[0];
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void CalculateLayout(FrameworkElement node, int iLayer)
		{
			NodeLayoutInfo ni = nli_dict[node];
			IList<FrameworkElement> tng = GetVisibleNodeChildren(node);
			Double width = node.DesiredSize.Width;

			if (tng.Count == 0)
			{
				ni.SubTreeWidth = width;
				ni.lstPosLeftBoundaryRelativeToRoot.Add(0);
				ni.lstPosRightBoundaryRelativeToRoot.Add(width);
			}
			else
			{
				List<Double> lstLeftToBB = new List<Double>();
				List<int> lstResponsible = new List<int>();
				for (int i = 0; i < tng.Count; i++)
				{
					FrameworkElement tn = tng[i];
					CalculateLayout(tn, iLayer + 1);
					RepositionSubtree(i, tng, lstLeftToBB, lstResponsible);
				}

				// If a subtree extends deeper than it's left neighbors then at that lower level it could potentially extend beyond those neighbors
				// on the left.  We have to check for this and make adjustements after the loop if it occurred.
				Double pxWidth = 0.0;
				Double pxUndercut = 0.0;
				Double pxWidthCur = Double.NaN;
				foreach (FrameworkElement tn in ni.m_children)
				{
					NodeLayoutInfo nlic = nli_dict[tn];
					if (Double.IsNaN(pxWidthCur))
						pxWidthCur = nlic.pxLeftPosRelativeToBoundingBox;
					pxWidthCur += nlic.pxToLeftSibling;

					if (nlic.pxLeftPosRelativeToBoundingBox > pxWidthCur)
						pxUndercut = Math.Max(pxUndercut, nlic.pxLeftPosRelativeToBoundingBox - pxWidthCur);

					// pxWidth might already be wider than the current node's subtree if earlier nodes "undercut" on the
					// right hand side so we have to take the Max here...
					pxWidth = Math.Max(pxWidth, pxWidthCur + nlic.SubTreeWidth - nlic.pxLeftPosRelativeToBoundingBox);

					// After this next statement, the BoundingBox we're relative to is the one of our parent's subtree rather than
					// our own subtree (with the exception of undercut considerations)
					nlic.pxLeftPosRelativeToBoundingBox = pxWidthCur;
				}

				if (pxUndercut > 0.0)
				{
					foreach (FrameworkElement tn in ni.m_children)
						nli_dict[tn].pxLeftPosRelativeToBoundingBox += pxUndercut;

					pxWidth += pxUndercut;
				}

				// We are never narrower than our root node's width which we haven't taken into account yet so
				// we do that here.
				ni.SubTreeWidth = Math.Max(width, pxWidth);

				// ...so that this centering may place the parent node negatively while the "width" is the width of
				// all the child nodes.

				// We should be centered between  the connection points of our children...
				FrameworkElement tnLeftMost = ni.m_children[0];
				Double pxLeftChild = nli_dict[tnLeftMost].pxLeftPosRelativeToBoundingBox + tnLeftMost.DesiredSize.Width / 2;

				FrameworkElement tnRightMost = ni.m_children[ni.m_children.Count - 1];
				Double pxRightChild = nli_dict[tnRightMost].pxLeftPosRelativeToBoundingBox + tnRightMost.DesiredSize.Width / 2;

				ni.pxLeftPosRelativeToBoundingBox = (pxLeftChild + pxRightChild - width) / 2;

				// If the root node was wider than the subtree, then we'll have a negative position for it.  We need
				// to readjust things so that the left of the root node represents the left of the bounding box and
				// the child distances to the Bounding box need to be adjusted accordingly.
				if (ni.pxLeftPosRelativeToBoundingBox < 0)
				{
					foreach (FrameworkElement tnChildCur in ni.m_children)
						nli_dict[tnChildCur].pxLeftPosRelativeToBoundingBox -= ni.pxLeftPosRelativeToBoundingBox;

					ni.pxLeftPosRelativeToBoundingBox = 0;
				}

				foreach (FrameworkElement tn in tng)
				{
					NodeLayoutInfo ltiCur = nli_dict[tn];
					ltiCur.pxLeftPosRelativeToParent = ltiCur.pxLeftPosRelativeToBoundingBox - ni.pxLeftPosRelativeToBoundingBox;
				}

				ni.lstPosLeftBoundaryRelativeToRoot.Add(0.0);
				ni.lstPosRightBoundaryRelativeToRoot.Add(width);

				DetermineBoundary(ni.m_children, true, ni.lstPosLeftBoundaryRelativeToRoot);
				DetermineBoundary(ni.m_children.AsEnumerable().Reverse(), false, ni.lstPosRightBoundaryRelativeToRoot);
			}

			while (m_layer_heights.Count <= iLayer)
				m_layer_heights.Add(0.0);

			m_layer_heights[iLayer] = Math.Max(node.DesiredSize.Height, m_layer_heights[iLayer]);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void DetermineBoundary(IEnumerable<FrameworkElement> entn, bool fLeft, List<Double> lstPos)
		{
			int cLayersDeep = 1;
			foreach (FrameworkElement tnChild in entn)
			{
				NodeLayoutInfo ltiChild = nli_dict[tnChild];

				List<Double> lstPosCur = fLeft ?
											ltiChild.lstPosLeftBoundaryRelativeToRoot :
											ltiChild.lstPosRightBoundaryRelativeToRoot;

				if (lstPosCur.Count >= lstPos.Count)
				{
					foreach (var e in lstPosCur.Skip(cLayersDeep - 1))
					{
						lstPos.Add(e + ltiChild.pxLeftPosRelativeToParent);
						cLayersDeep++;
					}
				}
			}
		}

		struct boundary_calc
		{
			public int i_cur;
			public Double delta;
			public int i_resp;
		};

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		IEnumerable<boundary_calc> MergeBoundaryEnums(IEnumerable<Double> eLeft, IEnumerable<Double> eRight, IEnumerable<int> eResponsible)
		{
			IEnumerator<Double> enLeft = eLeft.GetEnumerator();
			IEnumerator<Double> enRight = eRight.GetEnumerator();
			IEnumerator<int> enResponsible = eResponsible.GetEnumerator();

			int i = 0;
			while (enLeft.MoveNext() && enRight.MoveNext() && enResponsible.MoveNext())
			{
				yield return new boundary_calc
				{
					i_cur = i++,
					delta = enLeft.Current - enRight.Current,
					i_resp = enResponsible.Current
				};
			}
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void RepositionSubtree(int ix, IList<FrameworkElement> tngSiblings, List<Double> lstLeftToBB, List<int> lsttnResponsible)
		{
			NodeLayoutInfo lti = nli_dict[tngSiblings[ix]];

			if (ix == 0)
			{
				// No shifting but we still have to prepare the initial version of the
				// left hand skeleton list
				foreach (Double pxRelativeToRoot in lti.lstPosRightBoundaryRelativeToRoot)
				{
					lstLeftToBB.Add(pxRelativeToRoot + lti.pxLeftPosRelativeToBoundingBox);
					lsttnResponsible.Add(0);
				}
				return;
			}

			boundary_calc bc_max = new boundary_calc { delta = 0, i_resp = -1, i_cur = 0 };
			foreach (boundary_calc bc in MergeBoundaryEnums(
											lstLeftToBB, 
											lti.lstPosLeftBoundaryRelativeToRoot, 
											lsttnResponsible))
			{
				if (bc.delta > bc_max.delta)
					bc_max = bc;
			}

			Double horz_node_pad = HorizontalBuffer;
			Double horz_tree_pad = HorizontalBufferSubtree;
			Double pxHorizontalBuffer = bc_max.i_cur == 0 ? horz_node_pad : horz_tree_pad;

			FrameworkElement tnLeft = tngSiblings[ix - 1];

			lti.pxToLeftSibling = bc_max.delta - lstLeftToBB[0] + tnLeft.DesiredSize.Width + pxHorizontalBuffer;

			int cLevels = Math.Min(lti.lstPosRightBoundaryRelativeToRoot.Count, lstLeftToBB.Count);
			for (int i = 0; i < cLevels; i++)
			{
				lstLeftToBB[i] = lti.lstPosRightBoundaryRelativeToRoot[i] + bc_max.delta + pxHorizontalBuffer;
				lsttnResponsible[i] = ix;
			}
			for (int i = lstLeftToBB.Count; i < lti.lstPosRightBoundaryRelativeToRoot.Count; i++)
			{
				lstLeftToBB.Add(lti.lstPosRightBoundaryRelativeToRoot[i] + bc_max.delta + pxHorizontalBuffer);
				lsttnResponsible.Add(ix);
			}

			Double pxSlop = lti.pxToLeftSibling - tnLeft.DesiredSize.Width - horz_node_pad;
			if (pxSlop > 0)
			{
				for (int i = bc_max.i_resp + 1; i < ix; i++)
					nli_dict[tngSiblings[i]].pxToLeftSibling += pxSlop * (i - bc_max.i_resp) / (ix - bc_max.i_resp);

				lti.pxToLeftSibling -= (ix - bc_max.i_resp - 1) * pxSlop / (ix - bc_max.i_resp);
			}
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		Double CalcJustify(Double height, Double pxRowHeight)
		{
			switch (VerticalContentAlignment)
			{
				case VerticalAlignment.Top:
					return 0;

				case VerticalAlignment.Center:
					return (pxRowHeight - height) / 2;

				case VerticalAlignment.Bottom:
					return pxRowHeight - height;
			}
			throw new InvalidOperationException();
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		Size DetermineFinalPositions(FrameworkElement tn, int iLayer, Double pxFromTop, Double pxParentFromLeft)
		{
			NodeLayoutInfo nli = nli_dict[tn];

			Double pxRowHeight = m_layer_heights[iLayer++];

			nli.pxFromTop = pxFromTop + CalcJustify(tn.DesiredSize.Height, pxRowHeight);
			nli.pxFromLeft = nli.pxLeftPosRelativeToParent + pxParentFromLeft;

			nli.bot_handle = new Point(nli.pxFromLeft + tn.DesiredSize.Width / 2, nli.pxFromTop + tn.DesiredSize.Height);
			nli.top_handle = new Point(nli.pxFromLeft + tn.DesiredSize.Width / 2, nli.pxFromTop);

			Double pxBottom = nli.pxFromTop + tn.DesiredSize.Height;

			Double vert_node_pad = VerticalBuffer;

			foreach (FrameworkElement tnCur in GetVisibleNodeChildren(tn))
			{
				Double b = DetermineFinalPositions(tnCur, iLayer, pxFromTop + pxRowHeight + vert_node_pad, nli.pxFromLeft).Height;
				pxBottom = Math.Max(pxBottom, b);
			}
			return new Size(nli.SubTreeWidth, pxBottom);
		}

		
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected override Size MeasureOverride(Size availableSize)
		{
			FrameworkElement[] children = InternalChildren.OfType<FrameworkElement>().ToArray();
			if (children.Length == 0)
				return new Size(100, 100);

			nli_dict = new Dictionary<FrameworkElement, NodeLayoutInfo>();

			Size szFinal = new Size(0, 0);
			foreach (FrameworkElement child in children)
			{
				child.Measure(availableSize);
				Size szThis = child.DesiredSize;

				if (szThis.Width > szFinal.Width || szThis.Height > szFinal.Height)
				{
					szFinal = new Size(Math.Max(szThis.Width, szFinal.Width),
										Math.Max(szThis.Height, szFinal.Height));
				}

				nli_dict.Add(child, new NodeLayoutInfo());
			}

			FrameworkElement root = children.FirstOrDefault(fe => fe.GetValue(TreeParentProperty) == null);
			if (root == null)
				return szFinal;

			foreach (FrameworkElement child in children)
			{
				FrameworkElement tpar = child.GetValue(TreeParentProperty) as FrameworkElement;
				if (tpar == null)
					continue;

				nli_dict[tpar].m_children.Add(child);
			}

			CalculateLayout(root, 0);

			return DetermineFinalPositions(root, 0, 0, nli_dict[root].pxLeftPosRelativeToBoundingBox);
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected override Size ArrangeOverride(Size finalSize)
		{
			foreach (FrameworkElement tn in InternalChildren.OfType<FrameworkElement>())
			{
				Point ptLocation = new Point(0, 0);

				NodeLayoutInfo nli;
				if (nli_dict.TryGetValue(tn, out nli))
					ptLocation = new Point(nli.pxFromLeft, nli.pxFromTop);

				tn.Arrange(new Rect(ptLocation, tn.DesiredSize));
			}
			return finalSize;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		///
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		protected override void OnRender(DrawingContext dc)
		{
			base.OnRender(dc);

			Pen pen = new Pen(Stroke, StrokeThickness);
			foreach (FrameworkElement tn in InternalChildren.OfType<FrameworkElement>())
			{
				NodeLayoutInfo nli = nli_dict[tn];
				Point b = nli.bot_handle;
				foreach (FrameworkElement child in nli.m_children)
				{
					dc.DrawLine(pen, b, nli_dict[child].top_handle);
				}
			}
		}
	};
}