using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Threading;
using Microsoft.Research.DynamicDataDisplay.Common;

namespace Microsoft.Research.DynamicDataDisplay
{
	/// <summary>ViewportElement2D is intended to be a child of Viewport2D. Specifics
	/// of ViewportElement2D is Viewport2D attached property</summary>
	public abstract class ViewportElement2D : PlotterElement, INotifyPropertyChanged
	{
		protected ViewportElement2D() { }

		protected virtual Panel GetHostPanel(Plotter plotter)
		{
			return plotter.CentralGrid;
		}

		protected override void OnPlotterAttached(Plotter plotter)
		{
			base.OnPlotterAttached(plotter);

			plotter2D = (Plotter2D)plotter;
			GetHostPanel(plotter).Children.Add(this);
			viewport = plotter2D.Viewport;
			viewport.PropertyChanged += OnViewportPropertyChanged;
		}

		private void OnViewportPropertyChanged(object sender, ExtendedPropertyChangedEventArgs e)
		{
			if (e.PropertyName == "Visible")
			{
				OnVisibleChanged((DataRect)e.NewValue, (DataRect)e.OldValue);
			}
			else if (e.PropertyName == "Output")
			{
				OnOutputChanged((Rect)e.NewValue, (Rect)e.OldValue);
			}
			else if (e.PropertyName == "Transform")
			{
				Update();
			}
			else
			{
				// other properties changed are now not interesting for us
			}
		}

		protected override void OnPlotterDetaching(Plotter plotter)
		{
			base.OnPlotterDetaching(plotter);

			viewport.PropertyChanged -= OnViewportPropertyChanged;
			viewport = null;
			GetHostPanel(plotter).Children.Remove(this);
			plotter2D = null;
		}

		private Plotter2D plotter2D;
		protected Plotter2D Plotter2D
		{
			get { return plotter2D; }
		}

		public int ZIndex
		{
			get { return Panel.GetZIndex(this); }
			set { Panel.SetZIndex(this, value); }
		}

		#region Viewport

		private Viewport2D viewport;
		protected Viewport2D Viewport
		{
			get { return viewport; }
		}

		#endregion

		#region Description

		/// <summary>
		/// Creates the default description.
		/// </summary>
		/// <returns></returns>
		protected virtual Description CreateDefaultDescription()
		{
			return new StandardDescription();
		}

		private Description description;
		/// <summary>
		/// Gets or sets the description.
		/// </summary>
		/// <value>The description.</value>
		public Description Description
		{
			get
			{
				if (description == null)
				{
					description = CreateDefaultDescription();
					description.Attach(this);
				}
				return description;
			}
			set
			{
				if (description != null)
				{
					description.Detach();
				}
				description = value;
				if (description != null)
				{
					description.Attach(this);
				}
				RaisePropertyChanged("Description");
			}
		}

		public override string ToString()
		{
			return GetType().Name + ": " + Description.Brief;
		}

		#endregion

		private Vector offset = new Vector();
		protected internal Vector Offset
		{
			get { return offset; }
			set { offset = value; }
		}

        //bool SizeEqual(Size s1, Size s2, double eps)
        //{
        //    double width = Math.Min(s1.Width, s2.Width);
        //    double height = Math.Min(s1.Height, s2.Height);
        //    return Math.Abs(s1.Width - s2.Width) < width * eps &&
        //           Math.Abs(s1.Height - s2.Height) < height * eps;
        //}

		protected virtual void OnVisibleChanged(DataRect newRect, DataRect oldRect)
		{
			if (newRect.Size == oldRect.Size)
			{
				var transform = viewport.Transform;
				offset += oldRect.Location.DataToScreen(transform) - newRect.Location.DataToScreen(transform);
				if (ManualTranslate)
				{
					Update();
				}
			}
			else
			{
				offset = new Vector();
				Update();
			}
		}

		protected virtual void OnOutputChanged(Rect newRect, Rect oldRect)
		{
			offset = new Vector();
			Update();
		}

		/// <summary>
		/// Gets a value indicating whether this instance is translated.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is translated; otherwise, <c>false</c>.
		/// </value>
		protected bool IsTranslated
		{
			get { return offset.X != 0 || offset.Y != 0; }
		}

		#region IsLevel

		public bool IsLayer
		{
			get { return (bool)GetValue(IsLayerProperty); }
			set { SetValue(IsLayerProperty, value); }
		}

		public static readonly DependencyProperty IsLayerProperty =
			DependencyProperty.Register(
			"IsLayer",
			typeof(bool),
			typeof(ViewportElement2D),
			new FrameworkPropertyMetadata(
				false
				));

		#endregion

		#region Rendering & caching options

		protected object GetValueSync(DependencyProperty property)
		{
			return Dispatcher.Invoke(
						  DispatcherPriority.Send,
						   (DispatcherOperationCallback)delegate { return GetValue(property); },
							property);
		}

		protected void SetValueAsync(DependencyProperty property, object value)
		{
			Dispatcher.BeginInvoke(DispatcherPriority.Send,
				(SendOrPostCallback)delegate { SetValue(property, value); },
				value);
		}

		private bool manualClip;
		/// <summary>
		/// Gets or sets a value indicating whether descendant graph class 
		/// relies on autotic clipping by Viewport.Output or
		/// does its own clipping.
		/// </summary>
		public bool ManualClip
		{
			get { return manualClip; }
			set { manualClip = value; }
		}

		private bool manualTranslate;
		/// <summary>
		/// Gets or sets a value indicating whether descendant graph class
		/// relies on automatic translation of it, or does its own.
		/// </summary>
		public bool ManualTranslate
		{
			get { return manualTranslate; }
			set { manualTranslate = value; }
		}

		private RenderTo renderTarget = RenderTo.Screen;
		/// <summary>
		/// Gets or sets a value indicating whether descendant graph class 
		/// uses cached rendering of its content to image, or not.
		/// </summary>
		public RenderTo RenderTarget
		{
			get { return renderTarget; }
			set { renderTarget = value; }
		}

		private enum ImageKind
		{
			Real,
			BeingRendered,
			Empty
		}

		#endregion

		private RenderState CreateRenderState(DataRect renderVisible, RenderTo renderingType)
		{
			Rect output = Viewport.Output;

			return new RenderState(renderVisible, Viewport.Visible,
				output,
				renderingType);
		}

		protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e)
		{
			base.OnPropertyChanged(e);

			if (e.Property == VisibilityProperty)
			{
				Update();
			}
		}

		private bool updateCalled;
		private bool beforeFirstUpdate = true;
		protected void Update()
		{
			if (Viewport == null) return;

			UpdateCore();

			if (!beforeFirstUpdate)
			{
				updateCalled = true;
				InvalidateVisual();
			}
			beforeFirstUpdate = false;
		}

		protected virtual void UpdateCore() { }

		protected void TranslateVisual()
		{
			if (!ManualTranslate)
			{
				shouldReRender = false;
			}
			InvalidateVisual();
		}

		#region Thumbnail

		private ImageSource thumbnail;
		public ImageSource Thumbnail
		{
			get
			{
				if (!CreateThumbnail)
				{
					CreateThumbnail = true;
				}
				return thumbnail;
			}
		}

		private bool createThumbnail;
		public bool CreateThumbnail
		{
			get { return createThumbnail; }
			set
			{
				if (createThumbnail != value)
				{
					createThumbnail = value;
					if (value)
					{
						RenderThumbnail();
					}
					else
					{
						thumbnail = null;
						RaisePropertyChanged("Thumbnail");
					}
				}
			}
		}

		private bool ShouldCreateThumbnail
		{
			get { return IsLayer && createThumbnail; }
		}

		private void RenderThumbnail()
		{
			if (Viewport == null) return;

			Rect output = Viewport.Output;
			if (output.Width == 0 || output.Height == 0)
				return;

			DataRect visible = Viewport.Visible;

			var transform = viewport.Transform;

			DrawingVisual visual = new DrawingVisual();
			using (DrawingContext dc = visual.RenderOpen())
			{
				Point outputStart = visible.Location.DataToScreen(transform);
				double x = -outputStart.X + offset.X;
				double y = -outputStart.Y + output.Bottom - output.Top + offset.Y;
				bool translate = !manualTranslate && IsTranslated;
				if (translate)
				{
					dc.PushTransform(new TranslateTransform(x, y));
				}

				const byte c = 240;
				Brush brush = new SolidColorBrush(Color.FromArgb(120, c, c, c));
				Pen pen = new Pen(Brushes.Black, 1);
				dc.DrawRectangle(brush, pen, output);
				dc.DrawDrawing(graphContents);

				if (translate)
				{
					dc.Pop();
				}
			}

			RenderTargetBitmap bmp = new RenderTargetBitmap((int)output.Width, (int)output.Height, 96, 96, PixelFormats.Pbgra32);
			bmp.Render(visual);
			thumbnail = bmp;
			RaisePropertyChanged("Thumbnail");
		}

		#endregion

		private bool shouldReRender = true;
		private DrawingGroup graphContents;
		protected sealed override void OnRender(DrawingContext drawingContext)
		{
			if (Viewport == null) return;

			Rect output = Viewport.Output;
			if (output.Width == 0 || output.Height == 0) return;
			if (output.IsEmpty) return;
			if (Visibility != Visibility.Visible) return;

			if (shouldReRender || manualTranslate || renderTarget == RenderTo.Image || beforeFirstUpdate || updateCalled)
			{
				if (graphContents == null)
				{
					graphContents = new DrawingGroup();
				}
				if (beforeFirstUpdate)
				{
					Update();
				}

				using (DrawingContext context = graphContents.Open())
				{
					if (renderTarget == RenderTo.Screen)
					{
						RenderState state = CreateRenderState(Viewport.Visible, RenderTo.Screen);
						OnRenderCore(context, state);
					}
					else
					{
						// for future use
					}
				}
				updateCalled = false;
			}

			// thumbnail is not created, if
			// 1) CreateThumbnail is false
			// 2) this graph has IsLayer property, set to false
			if (ShouldCreateThumbnail)
			{
				RenderThumbnail();
			}

			if (!manualClip)
			{
				drawingContext.PushClip(new RectangleGeometry(output));
			}
			bool translate = !manualTranslate && IsTranslated;
			if (translate)
			{
				drawingContext.PushTransform(new TranslateTransform(offset.X, offset.Y));
			}

			drawingContext.DrawDrawing(graphContents);

			if (translate)
			{
				drawingContext.Pop();
			}
			if (!manualClip)
			{
				drawingContext.Pop();
			}
			shouldReRender = true;
		}


		protected abstract void OnRenderCore(DrawingContext dc, RenderState state);

		#region INotifyPropertyChanged Members

		public event PropertyChangedEventHandler PropertyChanged;

		protected void RaisePropertyChanged(string propertyName)
		{
			if (PropertyChanged != null)
			{
				PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
			}
		}

		#endregion
	}
}