using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Shapes;
using System.Windows.Media;
using System.Windows.Input;
using Microsoft.Research.DynamicDataDisplay.Common.DataSearch;
using System.Diagnostics;
using System.Windows.Markup;
using System.ComponentModel;

namespace Microsoft.Research.DynamicDataDisplay.Charts
{
	/// <summary>
	/// Represents a marker with position.X bound to mouse cursor's position and position.Y is determined by interpolation of <see cref="MarkerPointsGraph"/>'s points.
	/// </summary>
	[ContentProperty("MarkerTemplate")]
	public class DataFollowChart : ViewportHostPanel, INotifyPropertyChanged
	{
		/// <summary>
		/// Initializes a new instance of the <see cref="DataFollowChart"/> class.
		/// </summary>
		public DataFollowChart()
		{
			Marker = CreateDefaultMarker();
			SetX(marker, 0);
			SetY(marker, 0);
			Children.Add(marker);
		}

		private static Ellipse CreateDefaultMarker()
		{
			return new Ellipse
			{
				Width = 10,
				Height = 10,
				Stroke = Brushes.Green,
				StrokeThickness = 1,
				Fill = Brushes.LightGreen,
				Visibility = Visibility.Hidden
			};
		}

		/// <summary>
		/// Initializes a new instance of the <see cref="DataFollowChart"/> class, bound to specified <see cref="PointsGraphBase"/>.
		/// </summary>
		/// <param name="pointSource">The point source.</param>
		public DataFollowChart(PointsGraphBase pointSource)
			: this()
		{
			PointSource = pointSource;
		}

		#region MarkerTemplate property

		/// <summary>
		/// Gets or sets the template, used to create a marker. This is a dependency property.
		/// </summary>
		/// <value>The marker template.</value>
		public DataTemplate MarkerTemplate
		{
			get { return (DataTemplate)GetValue(MarkerTemplateProperty); }
			set { SetValue(MarkerTemplateProperty, value); }
		}

		/// <summary>
		/// Identifies the <see cref="MarkerTemplate"/> dependency property.
		/// </summary>
		public static readonly DependencyProperty MarkerTemplateProperty = DependencyProperty.Register(
		  "MarkerTemplate",
		  typeof(DataTemplate),
		  typeof(DataFollowChart),
		  new FrameworkPropertyMetadata(null, OnMarkerTemplateChanged));

		private static void OnMarkerTemplateChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			DataFollowChart chart = (DataFollowChart)d;
			DataTemplate template = (DataTemplate)e.NewValue;

			FrameworkElement marker;
			if (template != null)
			{
				marker = (FrameworkElement)template.LoadContent();
			}
			else
			{
				marker = CreateDefaultMarker();
			}

			chart.Children.Remove(chart.marker);
			chart.Marker = marker;
			chart.Children.Add(marker);
		}

		#endregion

		#region Point sources

		/// <summary>
		/// Gets or sets the source of points.
		/// Can be null.
		/// </summary>
		/// <value>The point source.</value>
		public PointsGraphBase PointSource
		{
			get { return (PointsGraphBase)GetValue(PointSourceProperty); }
			set { SetValue(PointSourceProperty, value); }
		}

		/// <summary>
		/// Identifies the <see cref="PointSource"/> dependency property.
		/// </summary>
		public static readonly DependencyProperty PointSourceProperty = DependencyProperty.Register(
		  "PointSource",
		  typeof(PointsGraphBase),
		  typeof(DataFollowChart),
		  new FrameworkPropertyMetadata(null, OnPointSourceChanged));

		private static void OnPointSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			DataFollowChart chart = (DataFollowChart)d;

			PointsGraphBase previous = e.OldValue as PointsGraphBase;
			if (previous != null)
			{
				previous.VisiblePointsChanged -= chart.Source_VisiblePointsChanged;
			}

			PointsGraphBase current = e.NewValue as PointsGraphBase;
			if (current != null)
			{
				current.ProvideVisiblePoints = true;
				current.VisiblePointsChanged += chart.Source_VisiblePointsChanged;
				if (current.VisiblePoints != null)
				{
					chart.searcher = new SortedXSearcher1d(current.VisiblePoints);
				}
			}

			chart.UpdateUIRepresentation();
		}

		private SearchResult1d searchResult = SearchResult1d.Empty;
		private SortedXSearcher1d searcher;
		private FrameworkElement marker;

		[NotNull]
		public FrameworkElement Marker
		{
			get { return marker; }
			protected set
			{
				marker = value;
				marker.DataContext = followDataContext;
				PropertyChanged.Raise(this, "Marker");
			}
		}

		private FollowDataContext followDataContext = new FollowDataContext();
		public FollowDataContext FollowDataContext
		{
			get { return followDataContext; }
		}

		private void UpdateUIRepresentation()
		{
			if (Plotter == null)
				return;

			PointsGraphBase source = this.PointSource;

			if (source == null || (source != null && source.VisiblePoints == null))
			{
				SetValue(MarkerPositionPropertyKey, new Point(Double.NaN, Double.NaN));
				marker.Visibility = Visibility.Hidden;
				return;
			}
			else
			{
				Point mousePos = Mouse.GetPosition(Plotter.CentralGrid);

				var transform = Plotter.Transform;
				Point viewportPos = mousePos.ScreenToViewport(transform);

				double x = viewportPos.X;
				searchResult = searcher.SearchXBetween(x, searchResult);
				SetValue(ClosestPointIndexPropertyKey, searchResult.Index);
				if (!searchResult.IsEmpty)
				{
					marker.Visibility = Visibility.Visible;

					IList<Point> points = source.VisiblePoints;
					Point ptBefore = points[searchResult.Index];
					Point ptAfter = points[searchResult.Index + 1];

					double ratio = (x - ptBefore.X) / (ptAfter.X - ptBefore.X);
					double y = ptBefore.Y + (ptAfter.Y - ptBefore.Y) * ratio;

					Point temp = new Point(x, y);
					SetX(marker, temp.X);
					SetY(marker, temp.Y);

					Point markerPosition = temp;
					followDataContext.Position = markerPosition;
					SetValue(MarkerPositionPropertyKey, markerPosition);
				}
				else
				{
					SetValue(MarkerPositionPropertyKey, new Point(Double.NaN, Double.NaN));
					marker.Visibility = Visibility.Hidden;
				}
			}
		}

		#region ClosestPointIndex property

		private static readonly DependencyPropertyKey ClosestPointIndexPropertyKey = DependencyProperty.RegisterReadOnly(
			"ClosestPointIndex",
			typeof(int),
			typeof(DataFollowChart),
			new PropertyMetadata(-1)
			);

		public static readonly DependencyProperty ClosestPointIndexProperty = ClosestPointIndexPropertyKey.DependencyProperty;

		public int ClosestPointIndex
		{
			get { return (int)GetValue(ClosestPointIndexProperty); }
		}

		#endregion

		#region MarkerPositionProperty

		private static readonly DependencyPropertyKey MarkerPositionPropertyKey = DependencyProperty.RegisterReadOnly(
			"MarkerPosition",
			typeof(Point),
			typeof(DataFollowChart),
			new PropertyMetadata(new Point(), OnMarkerPositionChanged));

		private static void OnMarkerPositionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			DataFollowChart chart = (DataFollowChart)d;
			chart.MarkerPositionChanged.Raise(chart);
		}

		/// <summary>
		/// Identifies the <see cref="MarkerPosition"/> dependency property.
		/// </summary>
		public static readonly DependencyProperty MarkerPositionProperty = MarkerPositionPropertyKey.DependencyProperty;

		/// <summary>
		/// Gets the marker position.
		/// </summary>
		/// <value>The marker position.</value>
		public Point MarkerPosition
		{
			get { return (Point)GetValue(MarkerPositionProperty); }
		}

		/// <summary>
		/// Occurs when marker position changes.
		/// </summary>
		public event EventHandler MarkerPositionChanged;

		#endregion

		public override void OnPlotterAttached(Plotter plotter)
		{
			base.OnPlotterAttached(plotter);
			plotter.MainGrid.MouseMove += MainGrid_MouseMove;
		}

		private void MainGrid_MouseMove(object sender, MouseEventArgs e)
		{
			UpdateUIRepresentation();
		}

		public override void OnPlotterDetaching(Plotter plotter)
		{
			plotter.MainGrid.MouseMove -= MainGrid_MouseMove;
			base.OnPlotterDetaching(plotter);
		}

		protected override void Viewport_PropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
		{
			base.Viewport_PropertyChanged(sender, e);
			UpdateUIRepresentation();
		}

		private void Source_VisiblePointsChanged(object sender, EventArgs e)
		{
			PointsGraphBase source = (PointsGraphBase)sender;
			if (source.VisiblePoints != null)
			{
				searcher = new SortedXSearcher1d(source.VisiblePoints);
			}
			UpdateUIRepresentation();
		}

		#endregion

		#region INotifyPropertyChanged Members

		/// <summary>
		/// Occurs when a property value changes.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		#endregion
	}

	/// <summary>
	/// Represents a special data context, which encapsulates marker's position and custom data.
	/// Used in <see cref="DataFollowChart"/>.
	/// </summary>
	public class FollowDataContext : INotifyPropertyChanged
	{
		private Point position;
		/// <summary>
		/// Gets or sets the position of marker.
		/// </summary>
		/// <value>The position.</value>
		public Point Position
		{
			get { return position; }
			set
			{
				position = value;
				PropertyChanged.Raise(this, "Position");
			}
		}

		private object data;
		/// <summary>
		/// Gets or sets the additional custom data.
		/// </summary>
		/// <value>The data.</value>
		public object Data
		{
			get { return data; }
			set
			{
				data = value;
				PropertyChanged.Raise(this, "Data");
			}
		}

		#region INotifyPropertyChanged Members

		/// <summary>
		/// Occurs when a property value changes.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged;

		#endregion
	}
}
