#define INTERNAL_MARKS
using System;
using System.IO;
using System.Windows;
using System.Windows.Media;
using System.Diagnostics;
using System.Globalization;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

using agree;

namespace agree.Wpf.Util
{
	using Math = System.Math;

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class TfsDrawingVisual : DrawingVisual
	{
		protected TfsDrawingVisual(TfsVisualHost host)
		{
			this.host = host;
		}

		readonly public TfsVisualHost host;
		public Size m_size;
	}

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Visual element of a coreference box
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	class TdeCoreference : TfsDrawingVisual
	{
		static Brush tag_brush = new SolidColorBrush(Colors.ForestGreen);
		static Pen tag_pen = new Pen(Brushes.ForestGreen, 1);

		int i_tag;

		public TdeCoreference(TfsVisualHost host, int i_tag)
			: base(host)
		{
			this.i_tag = i_tag;

			using (DrawingContext dc = RenderOpen())
				Draw(dc);
		}

		void Draw(DrawingContext dc)
		{
			Double x = 0, y = 0;

			Typeface font = host.owner.TypeFace;
			Double font_size = host.owner.FontSize;

			Double tag_height = font_size * .85;

			Rect r = new Rect(x, y, tag_height, tag_height);
			FormattedText ft = i_tag.ToString().FormattedText(font, tag_height * .9, tag_brush);
			r.Width = Math.Max(r.Width, ft.Width + 2);
			r.Width++;
			r.Height++;
			if (dc != null)
			{
				dc.DrawText(ft, x + ((r.Width / 2) - (ft.Width / 2)), y + ((r.Height / 2) - (ft.Height / 2)));
				dc.DrawRectangle(null, tag_pen, r);
			}
			m_size = new Size(r.Width, r.Height);
		}

		public int TagNumber { get { return i_tag; } }
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Visual element of a Typed Feature Structure
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class TdeConstraint : TfsDrawingVisual
	{
		const Double pen_width = 1.5;
		const Double half_pen = pen_width / 2;

		static Pen black_pen = new Pen(Brushes.Black, pen_width);
		static Brush type_brush = new SolidColorBrush(Color.FromRgb(0x4e, 0x4e, 0xea));
		static Brush mark_brush = new SolidColorBrush(Colors.Crimson);
		static Brush info_brush = new SolidColorBrush(Colors.Gray);
		static Brush hilt_brush = new SolidColorBrush(Color.FromArgb(0x80, 0xFF, 0xFE, 0x00));

		static TdeConstraint()
		{
			black_pen.StartLineCap = PenLineCap.Square;
			black_pen.EndLineCap = PenLineCap.Square;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		public TdeConstraint(TfsVisualHost host, Edge e)
			: base(host)
		{
			this.m_edge = e;
			if (host.tfs_highlight != null && host.tfs_highlight.Edge.Equals(e))
				m_flags |= DrawState.Highlight;

			using (DrawingContext dc = RenderOpen())
				Draw(dc);
		}

		Edge m_edge;
		public Edge Edge { get { return m_edge; } }

		[Flags]
		enum DrawState
		{
			Highlight = 0x01,
			OwnerBackground = 0x02,
		};

		DrawState m_flags = 0;

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		void Draw(DrawingContext dc)
		{
			Tfs tfs = host.owner._TfsEdge;
			Typeface font = host.owner.TypeFace;
			Double font_size = host.owner.FontSize;
			Double line_height = font_size * 1.15;

			Double x = 0;
			Double y = 0;
			Double x_max = 0;

			if (host.owner.f_brackets)
			{
				x += 6;
				y += 2;
			}

			{
				Double x0 = x;
				FormattedText ft = tfs.tm.GetEdgeType(m_edge.FlagsId).Name.FormattedText(font, font_size, type_brush);
				if (dc != null)
					dc.DrawText(ft, x0, y);
				x0 += ft.Width + 4;

				if (host.viz == this)
				{
					String info = null;
					agree.Type ht = host.owner._TfsEdge.Type;
					if (ht.Definition.Edge.Equals(m_edge))
						info = "definition";
					else if (ht.Expanded.Edge.Equals(m_edge))
						info = "expanded";
					else
						info = "copy";

					Typeface f2 = new Typeface(font.FontFamily, FontStyles.Italic, FontWeights.Normal, FontStretches.Normal);
					info = "(" + info + ")";
					ft = info.FormattedText(f2, font_size, info_brush);
					if (dc != null)
						dc.DrawText(ft, x0, y);
					x0 += ft.Width + 2;
				}
				miew.Math.Extensions.Maximize(ref x_max, x0);
				y += line_height;
			}

			// get maximum width among feature strings
			Double feature_end_x = FeatureStringMax() + 3;

			// feature/type tuples
			foreach (ConstraintRef cref in tfs.AllConstraintRefs(m_edge).OrderBy(e => e.FeatureInfo.maximal_type.m_level))
			{
				Size sz = DrawFeature(dc, cref, feature_end_x, x, y);
				if (x + sz.Width > x_max)
					x_max = x + sz.Width;
				y += sz.Height;
			}
			x = x_max;

			// feature structure outer brackets
			if (host.owner.f_brackets)
			{
				x += 4;
				y += 2;
				if (dc != null)
				{
					Double xhp = Math.Round(x) + half_pen;
					Double yhp = Math.Round(y) + half_pen;

					dc.DrawLine(black_pen, new Point(half_pen, half_pen), new Point(5 + half_pen, half_pen));
					dc.DrawLine(black_pen, new Point(half_pen, half_pen), new Point(half_pen, yhp));
					dc.DrawLine(black_pen, new Point(half_pen, yhp), new Point(5 + half_pen, yhp));

					dc.DrawLine(black_pen, new Point(xhp - 5, half_pen), new Point(xhp, half_pen));
					dc.DrawLine(black_pen, new Point(xhp, half_pen), new Point(xhp, y));
					dc.DrawLine(black_pen, new Point(xhp - 5, yhp), new Point(xhp, yhp));
				}

				x += pen_width + half_pen;
				y += pen_width + half_pen;
			}

			Size tsize = new Size(x, y);

			if (dc != null)
			{
				if (m_flags.HasFlag(DrawState.Highlight))
				{
					//Rect rr = new Rect(VisualOffset.X, VisualOffset.Y, tsize.Width, tsize.Height);
					Rect rr = new Rect(0, 0, tsize.Width, tsize.Height);
					dc.DrawRectangle(hilt_brush, null, rr);
				}

				//else 
				//    if (m_flags.HasFlag(DrawState.Highlight))
				//    dc.DrawRectangle(host.owner.Background, null, new Rect(VisualOffset.X, VisualOffset.Y, m_size.Width, m_size.Height));
			}
			m_size = tsize;
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		Double FeatureStringMax()
		{
			Tfs tfs = host.owner._TfsEdge;
			if (!tfs.tm.GetEdgeType(m_edge.FlagsId).HasAnyFeatures)
				return 0;

			Typeface font = host.owner.TypeFace;
			Double font_size = host.owner.FontSize;

			return tfs.AllConstraintRefs(m_edge).Max(cref =>
				{
					Double w = cref.Feature.ToUpper().FormattedText(font, font_size).Width;
					if (host.owner.f_marks)
						w += (" " + MarkText(cref.Constraint)).FormattedText(font, font_size * .85).Width;
					return w;
				});
		}

		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		Size DrawFeature(DrawingContext dc, ConstraintRef cref, Double feature_end_x, Double x_base, Double y_base)
		{
			TypeMgr tm = host.owner._TfsEdge.tm;
			Typeface font = host.owner.TypeFace;
			Double font_size = host.owner.FontSize;
			Double line_height = font_size * 1.15;

			FormattedText ft;
			Edge constraint = cref.Constraint;

			Double x = x_base;
			Double y = y_base;

			Brush b = tm.GetMaximalTypeForFeature(cref.i_feat) == tm.GetEdgeType(m_edge.FlagsId) ? Brushes.Black : Brushes.Gray;
			ft = cref.Feature.ToUpper().FormattedText(font, font_size, b);
			if (dc != null)
				dc.DrawText(ft, x, y);
			x += ft.Width;

			if (host.owner.f_marks
#if INTERNAL_MARKS
				&& !constraint.IsCoreferenced
#endif
				)
			{
				ft = (" " + MarkText(constraint)).FormattedText(font, font_size * .85, mark_brush);
				if (dc != null)
					dc.DrawText(ft, x, y + (font_size * .15));
				x += ft.Width;
			}

			x = x_base + feature_end_x;

			bool f_coref_nodisplay = false;
			if (constraint.IsCoreferenced)
			{
				ReentrancyFinder.Entry ce;
				if (host.dict_corefs.TryGetValue(constraint,out ce) && (!cref.Equals(ce.cref_display) || cref.ConstraintTypeIsTop))
					f_coref_nodisplay = true;
			}

			//if (cref.Pool.i_feat == tm.f_ix_dlist_list && !f_coref_nodisplay)
			//{
			//    y += 3;
			//    TdeDiffList tv = new TdeDiffList(host, host.owner._TfsEdge.WrapEdgeInSameTfs(cref.Host));
			//    this.Children.Add(tv);
			//    tv.Offset = new Vector(x, y);

			//    x += tv.m_size.Width;
			//    y += tv.m_size.Height;
			//}
			//else
			{
				ReentrancyFinder.Entry coref = null;
				if (constraint.IsCoreferenced)
				{
					Debug.Assert(host.dict_corefs != null);

					if (host.dict_corefs.TryGetValue(constraint, out coref))
					{
						TdeCoreference tcr = 
#if INTERNAL_MARKS
							 new TdeCoreference(host, constraint.Mark);
#else
							new TdeCoreference(host, coref.i_tag);
#endif
						this.Children.Add(tcr);

						Double yc = y + font_size * .20;
						tcr.Offset = new Vector(x, yc);

						x += tcr.m_size.Width + 5;
					}
				}

#if true
				if (f_coref_nodisplay)
				{
					// don't render the type for coreference nodes when either:
					// a. it's been rendered elsewhere, or
					// b. it's *top*
					y += line_height;
				}
				else
#endif
				{
					String val = tm.GetStringValue(cref.Constraint.FlagsId);
					if (val != null)
					{
						val = "“" + val + "”";
						ft = val.FormattedText(font, font_size, type_brush);
						if (dc != null)
							dc.DrawText(ft, x, y);
						x += ft.Width + 2;
						y += line_height;
					}
					else if ((constraint.FlagsId & agree.Edge.Flag.PrunedDuringParsing) > 0)
					{
						ft = "(pruned during parsing)".FormattedText(font, font_size, mark_brush);
						if (dc != null)
							dc.DrawText(ft, x, y);
						x += ft.Width + 2;
						y += line_height;
					}
					else
					{
						Type t = tm.GetEdgeType(constraint.FlagsId);
						if (t.IsBare)
						{
							val = cref.ConstraintType.Name;
							ft = val.FormattedText(font, font_size, type_brush);
							if (dc != null)
								dc.DrawText(ft, x, y);
							x += ft.Width + 2;
							y += line_height;
						}
						else
						{
							y += 3;
							TdeConstraint tv = new TdeConstraint(host, constraint);
							this.Children.Add(tv);
							tv.Offset = new Vector(x, y);

							x += tv.m_size.Width;
							y += tv.m_size.Height;
						}
					}
				}
			}
			return new Size(x - x_base, y - y_base);
		}


		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		/// <summary>
		/// 
		/// </summary>
		///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		String MarkText(Edge e)
		{
			if (e.Mark == 0)
				return String.Empty;
			String s = e.IsCoreferenced ? "⇌" : String.Empty;
			return s + String.Format("{0}", e.Mark);
		}
#if false
		protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters)
		{
			Debug.WriteLine("viz.htc {0} {1}", this.Edge, hitTestParameters.HitPoint);
			return base.HitTestCore(hitTestParameters);
		}
#endif
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class TdeDiffList : TfsDrawingVisual
	{
		static Brush type_brush = new SolidColorBrush(Color.FromRgb(0x4e, 0x4e, 0xea));
		static Pen thin_black_pen = new Pen(Brushes.Black, 1);

		Tfs tfs;

		public TdeDiffList(TfsVisualHost host, Tfs te)
			: base(host)
		{
			this.tfs = te;

			using (DrawingContext dcx = RenderOpen())
				Draw(dcx);
		}

		const double OPlusLRPad = 4;
		const double OPlusRadius = 6;

		void Draw(DrawingContext dc)
		{
			TypeMgr tm = host.owner._TfsEdge.tm;
			Typeface font = host.owner.TypeFace;
			Double font_size = host.owner.FontSize;
			Double line_height = font_size * 1.15;

			Double x = 0;
			Double y_max = line_height;

			var rgrgcr = PartitionDiffList(tfs).ToArray();

			int j;
			for (j = 0; j < rgrgcr.Length; j++)
			{
				ConstraintRef[] rgcr = rgrgcr[j];

				Edge he = rgcr[0].Host;
				if (he.IsCoreferenced)
				{
#if INTERNAL_MARKS
					TdeCoreference tcr = new TdeCoreference(host, he.Mark);
#else
					ReentrancyFinder.Entry coref = host.dict_corefs[he];
					TdeCoreference tcr = new TdeCoreference(host, coref.i_tag);
#endif
					this.Children.Add(tcr);
					x += tcr.m_size.Width + 3;
				}

				TdeList tv = new TdeList(host, rgcr);
				this.Children.Add(tv);
				x += tv.m_size.Width + 2;

				/// OPlus goes here
				x += OPlusLRPad + (OPlusRadius * 2) + OPlusLRPad;

				y_max = Math.Max(y_max, tv.m_size.Height);
			}

			double y_center = y_max / 2;

			//////////////////////////////////////////////////////
			/// second pass
			//////////////////////////////////////////////////////

			x = 0;
			j = 0;
			foreach (TfsDrawingVisual viz in Children)
			{
				ConstraintRef[] rgcr = rgrgcr[j];

				viz.Offset = new Vector(x, y_center - viz.m_size.Height / 2);
				x += viz.m_size.Width;

				if (viz is TdeCoreference)
					x += 3;

				if (viz is TdeList)
				{
					x += 2;
					if (j < rgrgcr.Length)
					{
						x += OPlusLRPad + OPlusRadius;
						dc.DrawEllipse(null, thin_black_pen, new Point(x, y_center), OPlusRadius, OPlusRadius);
						dc.DrawLine(thin_black_pen, new Point(x, y_center - OPlusRadius), new Point(x, y_center + OPlusRadius));
						dc.DrawLine(thin_black_pen, new Point(x - OPlusRadius, y_center), new Point(x + OPlusRadius, y_center));
						x += OPlusRadius + OPlusLRPad;
						j++;
					}
				}
			}

			Edge e = tfs.GetEdge(tfs.tm.f_ix_dlist_last, tfs.Edge.Mark);
			if (e.IsCoreferenced)
			{
#if INTERNAL_MARKS
				TdeCoreference tcr = new TdeCoreference(host, e.Mark);
#else
				ReentrancyFinder.Entry coref = host.dict_corefs[e];
				TdeCoreference tcr = new TdeCoreference(host, coref.i_tag);
#endif
				this.Children.Add(tcr);
				tcr.Offset = new Vector(x, y_center - tcr.m_size.Height / 2);
				x += tcr.m_size.Width + 3;
			}
			String val = tfs.tm.GetEdgeType(e.FlagsId).Name;
			FormattedText ft = val.FormattedText(font, font_size, type_brush);
			dc.DrawText(ft, x, y_center - ft.Height /2);
			x += ft.Width + 2;

			m_size = new Size(x, y_max);
		}

		static IEnumerable<ConstraintRef[]> PartitionDiffList(Tfs te)
		{
			var rgcr = te.GetDiffListLists(te.Edge).ToArray();
			ConstraintRef[] sect;
			for (int i = 0; (sect = rgcr.Skip(i).TakeWhile((cr, ix) => ix == 0 || !cr.Host.IsCoreferenced).ToArray()).Length > 0; i += sect.Length)
				yield return sect;
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Retained graphics instructions for an AVM "list"
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class TdeList : TfsDrawingVisual
	{
		const Double pen_width = 1.5;
		const Double half_pen = pen_width / 2;
		static Pen black_pen = new Pen(Brushes.Black, pen_width);
		static Brush type_brush = new SolidColorBrush(Color.FromRgb(0x4e, 0x4e, 0xea));

		IEnumerable<ConstraintRef> rgcr;

		public TdeList(TfsVisualHost host, IEnumerable<ConstraintRef> rgcr)
			: base(host)
		{
			this.rgcr = rgcr;

			using (DrawingContext dcx = RenderOpen())
				Draw(dcx);
		}

		const Double AngleWidth = 7.0;
		const Double AngleYMargin = 10;

		void Draw(DrawingContext dc)
		{
			TypeMgr tm = host.owner._TfsEdge.tm;
			Typeface font = host.owner.TypeFace;
			Double font_size = host.owner.FontSize;
			Double line_height = font_size * 1.15;
			FormattedText comma = ",".FormattedText(font, 20.0);

			//////////////////////////////////////////////////////
			/// first pass: create items and find item with the 
			/// largest height
			//////////////////////////////////////////////////////

			Double x = AngleWidth + 5;
			Double y_max = 0;
			bool f_first = true;

			foreach (ConstraintRef cr in rgcr)
			{
				if (f_first)
					f_first = false;
				else
					x += comma.Width + 5;

				TdeConstraint tv = new TdeConstraint(host, cr.Constraint);
				this.Children.Add(tv);
				y_max = Math.Max(y_max, tv.m_size.Height);

				x += tv.m_size.Width + 5;
			}

			//////////////////////////////////////////////////////
			/// calculations based on tallest item
			//////////////////////////////////////////////////////

			double y_center = y_max / 2;
			double angle_height = Math.Max(Math.Min(y_max - (AngleYMargin * 2), 180), 35);
			double y_margin = (y_max - angle_height) / 2;

			//////////////////////////////////////////////////////
			/// second pass
			//////////////////////////////////////////////////////

			/// Front angle '<'
			Point pt_apex = new Point(0, y_max / 2);
			dc.DrawLine(black_pen, pt_apex, new Point(AngleWidth, y_margin));
			dc.DrawLine(black_pen, pt_apex, new Point(AngleWidth, y_max - y_margin));

			/// center each item vertically
			x = AngleWidth + 5;
			f_first = true;
			foreach (TdeConstraint tv in Children.OfType<TdeConstraint>())
			{
				if (f_first)
					f_first = false;
				else
				{
					dc.DrawText(comma, new Point(x, y_center - comma.Height / 2));
					x += comma.Width + 5;
				}

				tv.Offset = new Vector(x, y_center - tv.m_size.Height / 2);
				x += tv.m_size.Width + 5;
			}

			/// Back angle '>'
			x += AngleWidth;
			pt_apex = new Point(x, y_max / 2);
			dc.DrawLine(black_pen, pt_apex, new Point(x - AngleWidth, y_margin));
			dc.DrawLine(black_pen, pt_apex, new Point(x - AngleWidth, y_max - y_margin));

			m_size = new Size(x, y_max);
		}
	};
}