// Created with MonoDevelop
//
//    Copyright (C) 2010 Hiperscan GmbH Dresden, Germany
//
//    This program is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    This program is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with this program.  If not, see <http://www.gnu.org/licenses/>.




using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.Collections.Generic;
using System.Threading;
using Gtk;

using NPlot;
using Hiperscan.Unix;
using Hiperscan.Spectroscopy;


namespace Hiperscan.GUI.Figure
{
	
	public class NPlotEngine : IPlotEngine, IDisposable
	{
		
		private class DrawableInfo
		{
			private IDrawable drawable;
			private Guid guid;
			private string id;
			private int z_order;
			private bool is_background;
			
			public DrawableInfo(IDrawable drawable, DataSet ds)
			{
				this.drawable      = drawable;
				this.guid          = ds.Guid;
				this.id            = ds.Id;
				this.z_order       = ds.ZOrder;
				this.is_background = ds.IsBackground;
			}
			
			public IDrawable Drawable
			{
				get { return this.drawable; }
			}
			
			public Guid Guid
			{
				get { return this.guid; }
			}
			
			public string Id
			{
				get { return this.id; }
			}
			
			public int ZOrder
			{
				get { return this.z_order;  }
				set { this.z_order = value; }
			}
			
			public bool IsBackground
			{
				get { return this.is_background; }
			}
		}
		
		private class DrawableInfoList : Dictionary<Guid,DrawableInfo>
		{
			public DrawableInfoList() : base()
			{
			}
			
			public DrawableInfoList(DrawableInfoList list) : base(list)
			{
			}
			
			public List<Guid> GetSortedGuids()
			{
				List<KeyValuePair<string,int>> bg_z_orders = new List<KeyValuePair<string,int>>();
				List<KeyValuePair<string,int>> z_orders    = new List<KeyValuePair<string,int>>();
				Dictionary<string,Guid> bg_dict = new Dictionary<string,Guid>();
				Dictionary<string,Guid> dict    = new Dictionary<string,Guid>();
				
				foreach (DrawableInfo info in this.Values)
				{
					if (info.IsBackground)
					{
						bg_z_orders.Add(new KeyValuePair<string,int>(info.Id, info.ZOrder));
						bg_dict.Add(info.Id, info.Guid);
					}
					else
					{
						z_orders.Add(new KeyValuePair<string,int>(info.Id, info.ZOrder));
						dict.Add(info.Id, info.Guid);
					}
				}
				
				bg_z_orders.Sort(delegate(KeyValuePair<string,int> lhs, KeyValuePair<string,int> rhs) {
					return lhs.Value.CompareTo(rhs.Value);
				});

				z_orders.Sort(delegate(KeyValuePair<string,int> lhs, KeyValuePair<string,int> rhs) {
					return lhs.Value.CompareTo(rhs.Value);
				});
				
				List<string> bg_ids = new List<string>();
				List<string> ids    = new List<string>();
				List<Guid> guids    = new List<Guid>();
				
				foreach (KeyValuePair<string,int> kv in bg_z_orders)
				{
					if (kv.Value != int.MaxValue)
						guids.Add(bg_dict[kv.Key]);
					else
						bg_ids.Add(kv.Key);
				}
				
				bg_ids.Sort();
				foreach (string id in bg_ids)
				{
					guids.Add(bg_dict[id]);
				}

				foreach (KeyValuePair<string,int> kv in z_orders)
				{
					if (kv.Value != int.MaxValue)
						guids.Add(dict[kv.Key]);
					else
						ids.Add(kv.Key);
				}
				
				ids.Sort();
				foreach (string id in ids)
				{
					guids.Add(dict[id]);
				}
				
				return guids;
			}
		}
			
		private NPlot_Gtk.PlotSurface2D plotsurface;
		private Grid grid;
		private Legend legend;
		private LinearAxis xaxis1, yaxis1;
		private LinearAxis xaxis2, yaxis2;

		private PlotSurface2D __thumbnail_plotsurface;
		private Bitmap        __thumbnail_bitmap;
		private Gdk.Pixbuf    __thumbnail_pixbuf;
		private const int thumbnail_width  = 60;
		private const int thumbnail_height = 40;
		
		private DrawableInfoList __drawables;
		private Queue __removed_drawables;
		private Queue __added_drawables;
		private Queue __added_bg_drawables;
		private Dictionary<string,DataSet> __data_sets;
		private Dictionary<string,DataSet> __background_data_sets;
		
		private float  default_line_width = 2f;
		private double x_extend_factor    = 0.0;
		private double y_extend_factor    = 0.02;
		private double x_min_range        = 0.001;
		private double y_min_range        = 0.001;
		
		private double global_x_max, global_x_min, global_y_max, global_y_min;

		private volatile bool is_empty    = true;
		private volatile bool is_dragging = false;
		private volatile bool disable_selections = false;
		private volatile bool created = false;
		private volatile bool use_second_axes = false;
		
		private volatile PlotEngineType type = PlotEngineType.Normal;
		
		private volatile int plot_type_id;
		
		private string xlabel = "x";
		private string ylabel = "y";
		
		private PlotSurface2D.XAxisPosition x_axis_position = PlotSurface2D.XAxisPosition.Bottom;
		private PlotSurface2D.YAxisPosition y_axis_position = PlotSurface2D.YAxisPosition.Left;
		
		private Thread do_process_changes;

		private RubberBandSelection selection;
		private HorizontalSelection horizontal_selection;
		private VerticalSelection   vertical_selection;
		private List<CrossHair> markers;
		private List<Annotation> annotations;
		private List<DataSetSelection> data_set_selections;
		
		private volatile bool lock_axes = false;
		private volatile bool data_sets_changed  = false;
		private volatile bool drawable_changed  = false;
		private volatile bool refresh_is_pending = false;
		private volatile bool refresh_overlay_is_pending = false;
		private volatile bool xaxis_changed, yaxis_changed;
		private volatile bool plot_changed = false;
		private volatile bool legend_visible = true;
		private volatile bool reverse_x_axes = false;
		private volatile bool __thread_running = false;
		
		public event EventHandler<EventArgs> Refreshed;
		public event EventHandler<EventArgs> DataSetAdded;
		public event EventHandler<EventArgs> DataSetRemoved;
		public event EventHandler<EventArgs> DataSetsChanged;
		public event EventHandler<EventArgs> PlotChanged;
		public event EventHandler<EventArgs> PlotCreated;
		public event EventHandler<EventArgs> AutoRemoveLegend;
		public event EventHandler<EventArgs> XAxesReversed;
		
		
		public NPlotEngine(PlotEngineType type)
		{
			this.type = type;

			this.plotsurface = new NPlot_Gtk.PlotSurface2D();
			this.__drawables = new DrawableInfoList(); 
			this.__removed_drawables  = Queue.Synchronized(new Queue());
			this.__added_drawables    = Queue.Synchronized(new Queue());
			this.__added_bg_drawables = Queue.Synchronized(new Queue());
			this.__data_sets            = new Dictionary<string,DataSet>();
			this.__background_data_sets = new Dictionary<string,DataSet>();
			this.selection   = null;
			this.markers     = new List<CrossHair>();
			this.annotations = new List<Annotation>();
			this.data_set_selections = new List<DataSetSelection>();
			
			this.plotsurface.Events |= Gdk.EventMask.ButtonPressMask;
			this.plotsurface.Events |= Gdk.EventMask.ButtonReleaseMask;
			this.plotsurface.Events |= Gdk.EventMask.PointerMotionMask;
			
			this.global_x_min = System.Double.PositiveInfinity;
			this.global_y_min = System.Double.PositiveInfinity;
			this.global_x_max = System.Double.NegativeInfinity;
			this.global_y_max = System.Double.NegativeInfinity;
			
			this.plotsurface.AutoScaleAutoGeneratedAxes = false;
			this.plotsurface.AutoScaleTitle = false;
			this.plotsurface.Legend = null;

			this.plotsurface.XAxis1 = null;
			this.plotsurface.XAxis2 = null;
			this.plotsurface.YAxis1 = null;
			this.plotsurface.YAxis2 = null;
			
			this.plotsurface.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

			// grid
			this.grid = new Grid();
			this.grid.VerticalGridType   = Grid.GridType.Fine;
			this.grid.HorizontalGridType = Grid.GridType.Fine;
			Pen pen = new Pen(Color.Black);
			float[] pattern = { 1.0f, 2.0f };
			pen.DashPattern = pattern;
			this.grid.MajorGridPen = pen;
				
			// axes
			this.xaxis1 = new LinearAxis(this.GlobalXMin, this.GlobalXMax);
			this.xaxis1.AxisColor     = Color.Black;
			this.xaxis1.LabelColor    = Color.Black;
			this.xaxis1.TickTextColor = Color.Black;
			this.xaxis1.TicksAngle    = (float)(1.5f * Math.PI);
			//this.xaxis.LargeTickStep = 100f;
			this.xaxis1.Label         = this.xlabel;
//			this.xaxis.LabelFont     = new System.Drawing.Font("Arial", 14F,
//			                                                   System.Drawing.FontStyle.Regular,
//			                                                   System.Drawing.GraphicsUnit.Pixel);
			this.plotsurface.XAxis1 = new LinearAxis(this.xaxis1);
			
			this.xaxis2   = new LinearAxis(this.ExtendedGlobalXMin, this.ExtendedGlobalXMax);
			this.xaxis2.AxisColor     = Color.Black;
			this.xaxis2.LabelColor    = Color.Black;
			this.xaxis2.TickTextColor = Color.Black;
			this.xaxis2.Label         = this.xlabel;
			this.plotsurface.XAxis2   = new LinearAxis(this.xaxis2);
			
			this.yaxis1 = new LinearAxis(this.ExtendedGlobalYMin, this.ExtendedGlobalYMax);
			this.yaxis1.AxisColor     = Color.Black;
			this.yaxis1.LabelColor    = Color.Black;
			this.yaxis1.TickTextColor = Color.Black;
			this.yaxis1.Label         = this.ylabel;
			this.plotsurface.YAxis1 = new LinearAxis(this.yaxis1);

			this.yaxis2 = new LinearAxis(this.ExtendedGlobalYMin, this.ExtendedGlobalYMax);
			this.yaxis2.AxisColor     = Color.Black;
			this.yaxis2.LabelColor    = Color.Black;
			this.yaxis2.TickTextColor = Color.Black;
			this.yaxis2.Label         = this.ylabel;
			this.yaxis2.TicksAngle    = (float)(1.5f * Math.PI);
			this.plotsurface.YAxis2   = new LinearAxis(this.yaxis2);
			
			// legend
			this.legend = new Legend();
			this.legend.AttachTo(PlotSurface2D.XAxisPosition.Bottom, PlotSurface2D.YAxisPosition.Right);
			this.legend.VerticalEdgePlacement   = Legend.Placement.Inside;
			this.legend.HorizontalEdgePlacement = Legend.Placement.Inside;
			this.legend.XOffset = -9;
			this.legend.YOffset = -9;
			this.legend.BackgroundColor = Color.FromArgb(190, Color.White);
			
			this.plotsurface.LegendZOrder = int.MaxValue;
			
			this.plotsurface.AddLegend += delegate(object o, EventArgs e) {
				if (this.legend_visible)
					this.plotsurface.Legend = this.legend;
			};
			
			this.plotsurface.AutoRemoveLegend += delegate(object sender, EventArgs e) {
				if (this.AutoRemoveLegend != null)
				{
					Application.Invoke(delegate {
						this.AutoRemoveLegend(this, EventArgs.Empty);
					});
				}
			};
			
			this.plotsurface.LinesDrawn += this.DrawOverlays;
		
			switch (this.type)
			{
				
			case PlotEngineType.Normal:
				this.StartAutoUpdate();
				break;
				
			case PlotEngineType.Synchronous:
				break;
				
			case PlotEngineType.Preview:
				this.plotsurface.PlotBackColor = Color.Transparent;
				this.legend = null;
				break;
				
			}
			
			// thumbnail creation
			this.__thumbnail_bitmap      = new Bitmap(thumbnail_width, thumbnail_height);
			this.__thumbnail_plotsurface = new PlotSurface2D();
			this.__thumbnail_plotsurface.AutoScaleAutoGeneratedAxes = false;
			this.__thumbnail_plotsurface.AutoScaleTitle = false;
			this.__thumbnail_plotsurface.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.None;
			this.__ResetThumbnail();
		}
		
		public NPlotEngine() : this(PlotEngineType.Normal)
		{
		}
		
		public void Dispose()
		{
			if (this.__thread_running)
				this.do_process_changes.Abort();
		}
		
		~NPlotEngine()
		{
		}
		
		public NPlotEngine(NPlotEngine plot) : this(PlotEngineType.Preview)
		{
			this.type = PlotEngineType.Normal;
			
			this.DataSets = new Dictionary<string,DataSet>(plot.DataSets);
			
			this.legend = plot.legend;
			this.grid   = plot.grid;
			this.XMin   = plot.XMin;
			this.XMax   = plot.XMax;
			this.YMin   = plot.YMin;
			this.YMax   = plot.YMax;
			this.XLabel = plot.XLabel;
			this.YLabel = plot.YLabel;
			
			this.__ProcessChanges();
		}
		
		public void NotifyPlotChanges()
		{
			lock (this.plotsurface)
				this.plot_changed = true;
		}
		
		public void Clear()
		{
			if (!this.is_empty)
			{
				lock (this)
				{
					this.__data_sets.Clear();
					this.__removed_drawables.Clear();
					this.__added_drawables.Clear();
					this.__added_bg_drawables.Clear();
					this.__drawables.Clear();
					this.is_empty = true;
					this.plot_changed = true;
					this.__ResetThumbnail();
				}
				if (this.DataSetRemoved != null)
					this.DataSetRemoved(this, EventArgs.Empty);
				if (this.DataSetsChanged != null)
					this.DataSetsChanged(this, EventArgs.Empty);
			}
			
			this.plotsurface.Clear();
			this.refresh_is_pending = true;
			
			this.plotsurface.XAxis1 = null;
			this.plotsurface.YAxis1 = null;
			this.plotsurface.XAxis2 = null;
			this.plotsurface.YAxis2 = null;

			this.global_x_min = System.Double.PositiveInfinity;
			this.global_y_min = System.Double.PositiveInfinity;
			this.global_x_max = System.Double.NegativeInfinity;
			this.global_y_max = System.Double.NegativeInfinity;
			
			if (this.GtkWidget.GdkWindow != null)
				this.GtkWidget.GdkWindow.Cursor = new Gdk.Cursor(Gdk.CursorType.Arrow);
		}
		
		private void __ProcessChanges()
		{
			this.__ProcessAxesChanges();
			this.__ProcessDataSets();
			this.__ProcessDrawables();
			this.__ProcessRefreshs();
			this.__ProcessDelegates();
		}
		
		public void ProcessChanges()
		{
			if (this.do_process_changes != null)
				return;
			
			this.data_sets_changed = true;
			this.__ProcessChanges();
		}
		
		private void __DoProcessChanges()
		{
			for (;;)
			{
				this.__ProcessAxesChanges();
				this.__ProcessDataSets();
				this.__ProcessDrawables();
				this.__ProcessRefreshs();
				this.__ProcessDelegates();
				
				if (!this.__thread_running)
					break;

				Thread.Sleep(20);
			}
		}
		
		private void __ProcessAxesChanges()
		{
			if (this.xaxis_changed)
			{
				double new_min1, new_max1, new_min2, new_max2;
				string new_label1, new_label2;
				lock (this)
				{
					new_min1   = this.xaxis1.WorldMin;
					new_max1   = this.xaxis1.WorldMax;
					new_label1 = this.xaxis1.Label;
					new_min2   = this.xaxis2.WorldMin;
					new_max2   = this.xaxis2.WorldMax;
					new_label2 = this.xaxis2.Label;
					this.xaxis_changed = false;
				}
				lock (this.plotsurface)
				{
					if (this.plotsurface.XAxis1 != null)
					{
						if (plotsurface.XAxis1.WorldMin != new_min1)
						{
							plotsurface.XAxis1.WorldMin = new_min1;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
						if (plotsurface.XAxis1.WorldMax != new_max1)
						{
							plotsurface.XAxis1.WorldMax = new_max1;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
						if (plotsurface.XAxis1.Label != new_label1)
						{
							plotsurface.XAxis1.Label = new_label1;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
						if (plotsurface.XAxis1.Reversed != this.reverse_x_axes)
						{
							plotsurface.XAxis1.Reversed = this.reverse_x_axes;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
					}
					
					if (this.plotsurface.XAxis2 != null)
					{
						if (plotsurface.XAxis2.WorldMin != new_min2)
						{
							plotsurface.XAxis2.WorldMin = new_min2;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
						if (plotsurface.XAxis2.WorldMax != new_max2)
						{
							plotsurface.XAxis2.WorldMax = new_max2;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
						if (plotsurface.XAxis2.Label != new_label2)
						{
							plotsurface.XAxis2.Label = new_label2;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
						if (plotsurface.XAxis2.Reversed != this.reverse_x_axes)
						{
							plotsurface.XAxis2.Reversed = this.reverse_x_axes;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
					}
				}
			}
			
			if (this.yaxis_changed)
			{
				double new_min1, new_max1, new_min2, new_max2;
				string new_label1, new_label2;
				lock (this)
				{
					new_min1   = this.yaxis1.WorldMin;
					new_max1   = this.yaxis1.WorldMax;
					new_label1 = this.yaxis1.Label;
					new_min2   = this.yaxis2.WorldMin;
					new_max2   = this.yaxis2.WorldMax;
					new_label2 = this.yaxis2.Label;
					this.yaxis_changed = false;
				}
				lock (this.plotsurface)
				{
					if (this.plotsurface.YAxis1 != null)
					{
						if (plotsurface.YAxis1.WorldMin != new_min1)
						{
							plotsurface.YAxis1.WorldMin = new_min1;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
						if (plotsurface.YAxis1.WorldMax != new_max1)
						{
							plotsurface.YAxis1.WorldMax = new_max1;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
						if (plotsurface.YAxis1.Label != new_label1)
						{
							plotsurface.YAxis1.Label = new_label1;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
					}
					
					if (this.plotsurface.YAxis2 != null)
					{
						if (plotsurface.YAxis2.WorldMin != new_min2)
						{
							plotsurface.YAxis2.WorldMin = new_min2;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
						if (plotsurface.YAxis2.WorldMax != new_max2)
						{
							plotsurface.YAxis2.WorldMax = new_max2;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
						if (plotsurface.YAxis2.Label != new_label2)
						{
							plotsurface.YAxis2.Label = new_label2;
							this.refresh_is_pending = true;
							this.plot_changed = true;
						}
					}
				}
			}
		}
		
		private void __ProcessDataSets()
		{
			lock (this)
			{
				if (!this.data_sets_changed)
					return;

				this.data_sets_changed = false;
				
				Dictionary<Guid,DrawableInfo> current_drawables = new Dictionary<Guid,DrawableInfo>(this.__drawables);

				if (this.__data_sets.Count == 0)
				{
					Application.Invoke(delegate {
						this.DataSetSelections.Clear();
						this.Clear();
					});
					return;
				}

				List<DataSet> data_sets = new List<DataSet>(this.__background_data_sets.Values);
				data_sets.AddRange(this.__data_sets.Values);
					
				foreach (DataSet ds in data_sets)
				{
					if (!ds.IsVisible)
						continue;
					
					if (this.__drawables.ContainsKey(ds.Guid))
					{
						// data set is allready up-to-date
						current_drawables.Remove(ds.Guid);
						this.__drawables[ds.Guid].ZOrder = ds.ZOrder;
						continue;
					}
					
					// its a new or updated data set
					Guid found_guid = Guid.Empty;
					foreach (Guid guid in this.__drawables.Keys)
					{
						if (this.__drawables[guid].Id == ds.Id)
						{
							found_guid = guid;
							break;
						}
					}
						
					if (found_guid != Guid.Empty)
					{
						this.__removed_drawables.Enqueue(this.__drawables[found_guid].Drawable);
						this.__drawables.Remove(found_guid);
					}
						
					IDrawable drawable = this.__CreateDrawable(ds);
					
					if (ds.IsBackground)
						this.__added_bg_drawables.Enqueue(drawable);
					else
						this.__added_drawables.Enqueue(drawable);
					
					this.__drawables.Add(ds.Guid, new DrawableInfo(drawable, ds));
					
					this.SetGlobalMinMax(ds);
					
					this.drawable_changed = true;
				}
				
				foreach (DrawableInfo drawable in current_drawables.Values)
				{
					this.__removed_drawables.Enqueue(drawable.Drawable);
					this.__drawables.Remove(drawable.Guid);
					this.drawable_changed = true;
				}
				
				this.__CreateThumbnail();
			}
		}
		
		private void __ProcessDrawables()
		{
			lock (this)
			{
				if (!this.drawable_changed)
					return;
				
				this.drawable_changed = false;
				bool added   = false;
				bool changed = false;

				if (this.is_empty)
				{
					this.is_empty = false;
					
					this.plotsurface.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

					this.plotsurface.XAxis1 = new LinearAxis(this.xaxis1);
					this.plotsurface.YAxis1 = new LinearAxis(this.yaxis1);
					this.plotsurface.XAxis2 = new LinearAxis(this.xaxis2);
					this.plotsurface.YAxis2 = new LinearAxis(this.yaxis2);
					
					if (this.reverse_x_axes)
					{
						this.plotsurface.XAxis1.Reversed = true;
						this.plotsurface.XAxis2.Reversed = true;
					}
					
					switch (this.type)
					{
						
					case PlotEngineType.Normal:
					case PlotEngineType.Synchronous:
						this.plotsurface.Add(this.grid);
						Application.Invoke(delegate {
							if (this.GtkWidget.GdkWindow != null)
								this.GtkWidget.GdkWindow.Cursor = new Gdk.Cursor(Gdk.CursorType.Tcross);
						});
						break;
						
					case PlotEngineType.Preview:
						this.plotsurface.XAxis1.Hidden = true;
						this.plotsurface.YAxis1.Hidden = true;
						this.plotsurface.XAxis2.Hidden = true;
						this.plotsurface.YAxis2.Hidden = true;
						this.plotsurface.Legend = null;
						break;
						
					}
				}
				
				if (this.__background_data_sets.Count == 0)
				{
					this.plotsurface.XAxis2 = null;
					this.plotsurface.YAxis2 = null;
				}
				
				if (this.__removed_drawables.Count > 0)
				{
					while (this.__removed_drawables.Count > 0)
					{
						IDrawable drawable = (IDrawable)this.__removed_drawables.Dequeue();
						if (drawable == null)
							continue;
						this.plotsurface.Remove(drawable, false);
						drawable = null;
						this.refresh_is_pending = true;
					}
//					this.__removed_drawables.Clear();
//					this.__removed_drawables.TrimToSize();
					if (this.DataSetRemoved != null)
						Application.Invoke(delegate {
							this.DataSetRemoved(this, EventArgs.Empty);
						});
					changed = true;
				}
				
				if (this.__added_bg_drawables.Count > 0)
				{
					while (this.__added_bg_drawables.Count > 0)
					{
						IDrawable drawable = (IDrawable)this.__added_bg_drawables.Dequeue();
						if (drawable == null)
							continue;
						this.plotsurface.Add(drawable, 
						                     PlotSurface2D.XAxisPosition.Top,
						                     PlotSurface2D.YAxisPosition.Right);
						this.plotsurface.XAxis2.WorldMin = this.xaxis2.WorldMin;
						this.plotsurface.XAxis2.WorldMax = this.xaxis2.WorldMax;
						this.plotsurface.YAxis2.WorldMin = this.yaxis2.WorldMin;
						this.plotsurface.YAxis2.WorldMax = this.yaxis2.WorldMax;
						this.refresh_is_pending = true;
					}

					added   = true;
					changed = true;
				}

				if (this.__added_drawables.Count > 0)
				{
					while (this.__added_drawables.Count > 0)
					{
						IDrawable drawable = (IDrawable)this.__added_drawables.Dequeue();
						if (drawable == null)
							continue;
						if (this.use_second_axes)
						{
							this.plotsurface.Add(drawable, 
							                     PlotSurface2D.XAxisPosition.Bottom,
							                     PlotSurface2D.YAxisPosition.Right);
							this.plotsurface.YAxis2.WorldMin = this.yaxis2.WorldMin;
							this.plotsurface.YAxis2.WorldMax = this.yaxis2.WorldMax;
						}
						else
						{
							this.plotsurface.Add(drawable, 
							                     PlotSurface2D.XAxisPosition.Bottom,
							                     PlotSurface2D.YAxisPosition.Left);
							this.plotsurface.YAxis1.WorldMin = this.yaxis1.WorldMin;
							this.plotsurface.YAxis1.WorldMax = this.yaxis1.WorldMax;
						}
						this.plotsurface.XAxis1.WorldMin = this.xaxis1.WorldMin;
						this.plotsurface.XAxis1.WorldMax = this.xaxis1.WorldMax;
						this.refresh_is_pending = true;
					}
					
					added   = true;
					changed = true;
				}
				
				if (added && this.DataSetAdded != null)
					Application.Invoke(delegate {
						this.DataSetAdded(this, EventArgs.Empty);
					});
				
				if (changed && this.DataSetsChanged != null)
					Application.Invoke(delegate {
						this.DataSetsChanged(this, EventArgs.Empty);
					});
				
				this.plot_changed = changed;
				
				// assemble IDrawables in right order
				if (changed)
				{
					lock (this.plotsurface)
					{
						List<Guid> guids = this.__drawables.GetSortedGuids();
						
						List<object> xaxis_pos = new List<object>(this.plotsurface.XAxisPositions.ToArray());
						List<object> yaxis_pos = new List<object>(this.plotsurface.YAxisPositions.ToArray());
						List<object> drawables = new List<object>(this.plotsurface.Drawables.ToArray());
						
						int ix = 0;
						for (int jx=0; jx < drawables.Count; ++jx)
						{
							if (drawables[jx] is Grid)
							{
								this.plotsurface.XAxisPositions[ix] = xaxis_pos[jx];
								this.plotsurface.YAxisPositions[ix] = yaxis_pos[jx];
								this.plotsurface.Drawables[ix++]    = drawables[jx];
								xaxis_pos.RemoveAt(jx);
								yaxis_pos.RemoveAt(jx);
								drawables.RemoveAt(jx);
								break;
							}
						}
						
						foreach (Guid guid in guids)
						{
							DrawableInfo info = this.__drawables[guid];
//							Console.WriteLine(this.__drawables[guid].Id);
							
							if (drawables.Contains(info.Drawable))
							{
								int jx = drawables.IndexOf(info.Drawable);
								this.plotsurface.XAxisPositions[ix] = xaxis_pos[jx];
								this.plotsurface.YAxisPositions[ix] = yaxis_pos[jx];
								this.plotsurface.Drawables[ix++]    = drawables[jx];
								xaxis_pos.RemoveAt(jx);
								yaxis_pos.RemoveAt(jx);
								drawables.RemoveAt(jx);
							}
						}
						
						for (int jx=0; jx < drawables.Count; ++jx)
						{
							this.plotsurface.XAxisPositions[ix] = xaxis_pos[jx];
							this.plotsurface.YAxisPositions[ix] = yaxis_pos[jx];
							this.plotsurface.Drawables[ix++]    = drawables[jx];
						}
					}
					
					this.plotsurface.CheckLegendSize();
				}
			}
		}
		
		private void __ProcessRefreshs()
		{
			while (this.refresh_is_pending || this.refresh_overlay_is_pending)
			{
				lock (this)
				{
					if (this.refresh_is_pending)
					{
						this.refresh_is_pending = false;
						this.refresh_overlay_is_pending = false;
						this.plotsurface.UpdateCache();
					}
					else
					{
						this.refresh_overlay_is_pending = false;
						this.plotsurface.UpdateCache(true);
					}
				}
				
				if (this.refresh_is_pending || this.refresh_overlay_is_pending)
					continue;
				
				Application.Invoke(delegate {
					this.plotsurface.QueueDrawArea();
					if (this.Refreshed != null)
						this.Refreshed(this, EventArgs.Empty);
				});
			}
		}
		
		private void __ProcessDelegates()
		{
			if (!this.plot_changed)
				return;
			this.plot_changed = false;
			
			if (this.PlotChanged != null)
				Application.Invoke(delegate {
					this.PlotChanged(this, EventArgs.Empty);
				});
			
			if (!this.created && this.PlotCreated != null)
			{
				this.created = true;
				Application.Invoke(delegate {
					this.PlotCreated(this, EventArgs.Empty);
				});
			}
		}
		
		public void Refresh()
		{
			this.Refresh(false);
		}
		
		public void Refresh(bool only_overlays)
		{
			try
			{
				this.plotsurface.UpdateCache(only_overlays);
				this.plotsurface.QueueDrawArea();
			}
			catch (Exception ex)
			{
				Console.WriteLine("Exception in NPlotEngine.Refresh(): " + ex.Message);
			}
			
			if (this.Refreshed != null)
				this.Refreshed(this, EventArgs.Empty);
		}
		
		private IDrawable __CreateDrawable(DataSet ds)
		{
			if (ds.Color == Color.Empty)
				ds.Color = Color.Blue;
			
			if (ds.LineWidth < 1)
				ds.LineWidth = this.default_line_width;
			
			if (ds.IsContinuous)
			{
				LinePlot lp = new LinePlot();
				lp.AbscissaData = ds.X;
				lp.OrdinateData = ds.Y;
				lp.Gaps = ds.Gaps;
				lp.Pen = new Pen(ds.Color, ds.LineWidth);
				lp.Pen.DashStyle = (DashStyle)Enum.Parse(typeof(DashStyle), ds.LineStyle.ToString());
				
				if (!string.IsNullOrEmpty(ds.Label))
					lp.Label = ds.Label;
				
				if (!ds.IsRegion)
					return lp;
				
				DataSet limit_ds = ds * 0.0 + ds.RegionFillLimit;
				
				LinePlot limit_lp = new LinePlot();
				limit_lp.AbscissaData = limit_ds.X;
				limit_lp.OrdinateData = limit_ds.Y;
				limit_lp.Pen = lp.Pen;
				FilledRegion fr = new FilledRegion(limit_lp, lp);
				fr.Brush = new HatchBrush(HatchStyle.BackwardDiagonal, ds.Color, Color.Transparent);
				return fr;
			}
			else
			{
				PointPlot pp;
				
				if (ds.IsDropPoint)
				{
					pp = new PointPlot();
					pp.Marker.DropLine = true;
					pp.Marker.Pen = Pens.Red;
					pp.Marker.Filled = false;
					pp.Marker.Type = (Marker.MarkerType)Enum.Parse(typeof(Marker.MarkerType), ds.MarkerStyle.ToString());
					pp.AbscissaData = ds.X;
					pp.OrdinateData = ds.Y;
					
					if (!string.IsNullOrEmpty(ds.Label))
						pp.Label = ds.Label;
				}
				else
				{
					Marker.MarkerType mt = (Marker.MarkerType)Enum.Parse(typeof(Marker.MarkerType), ds.MarkerStyle.ToString());
					Marker m = new Marker(mt, 6, new Pen(ds.Color, this.default_line_width));
					pp = new PointPlot(m);
					pp.AbscissaData = ds.X;
					pp.OrdinateData = ds.Y;
					if (!string.IsNullOrEmpty(ds.Label))
						pp.Label = ds.Label;
				}
				
				return pp;
			}
		}
				
//				Application.Invoke(delegate {
//					this.plotsurface.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
//							this.plotsurface.Add(this.grid);
//							this.plotsurface.Add(lp);
//							this.SetGlobalMinMax(ds);
//							if (this.DataSetAdded != null)
//								this.DataSetAdded(this, EventArgs.Empty);
//						});
//					}
//					else
//					{
//						//								if (ds.IsDropPoint)
//						//									DropPointPlot(ds, Color.Red, 0.5f);
//						//								else
//						//									PointPlot(ds, ds.Color, this.line_width);
//					}
//						
//					this.SetGlobalMinMax(ds);
//					++ix;
//					
//					this.refresh_is_pending = true;
//				}
//			}
//		}
		
		public void AddDataSet(DataSet ds)
		{
			lock (this)
			{
				List<string> current_ids = new List<string>(this.__data_sets.Keys);
				foreach (string id in current_ids)
				{
					if (id == ds.Id)
						this.__data_sets.Remove(id);
				}
				
				this.__data_sets.Add(ds.Id, ds);

				this.data_sets_changed = true;
			}
			
			if (this.DataSetAdded != null)
				this.DataSetAdded(this, EventArgs.Empty);
		}
		
		public void FitView()
		{
			bool success = false;
			
			lock (this)
			{
				foreach (DataSet ds in this.__data_sets.Values)
				{
					if (!ds.IsVisible)
						continue;
					if (!success)
					{
						this.global_x_max = double.NegativeInfinity;
						this.global_y_max = double.NegativeInfinity;
						this.global_x_min = double.PositiveInfinity;
						this.global_y_min = double.PositiveInfinity;
						success = true;
					}
					this.SetGlobalMinMax(ds);
				}
			}
			
			if (this.__data_sets.Count > 0 && !success)
			{
				Console.WriteLine("Cannot fit plot if all datasets are set invisible.");
				return;
			}
				
			this.XMin = this.ExtendedGlobalXMin;
			this.XMax = this.ExtendedGlobalXMax;
			this.YMin = this.ExtendedGlobalYMin;
			this.YMax = this.ExtendedGlobalYMax;
		}
		
		public void SetGlobalXMinMax(double x_min, double x_max)
		{
			if (this.global_x_min > x_min)
				this.global_x_min = x_min;
			
			if (this.global_x_max < x_max)
				this.global_x_max = x_max;			
		}
		
		public void SetGlobalXMinMax(double x_min, double x_max, bool force)
		{
			this.global_x_min = x_min;
			this.global_x_max = x_max;			
		}

		public void SetGlobalYMinMax(double y_min, double y_max)
		{
			if (this.global_y_min > y_min)
				this.global_y_min = y_min;
			
			if (this.global_y_max < y_max)
				this.global_y_max = y_max;
		}

		private void SetGlobalMinMax(DataSet ds)
		{
			if (ds.IsBackground)
				return;
			
			double x_min, x_max, y_min, y_max;
			
			try
			{
				x_min = ds.XMin();
				x_max = ds.XMax();
				y_min = ds.YMin();
				y_max = ds.YMax();
			}
			catch (Exception ex)
			{
				Console.WriteLine("Cannot set global Min-Max: " + ex.Message);
				Console.WriteLine("Setting default values...");
				x_min = 1000f;
				x_max = 1900f;
				y_min = 0f;
				y_max = 1f;
			}
			
			this.SetGlobalXMinMax(x_min, x_max);
			this.SetGlobalYMinMax(y_min, y_max);
		}
		
		public void GetXMinMax(double pan, double zoom, out double min, out double max)
		{
			double global_range = this.ExtendedGlobalXMax - this.ExtendedGlobalXMin;
			double zoomed_range = global_range / zoom;
			double panned_range = global_range - zoomed_range;
				
			if (this.reverse_x_axes)
			{
				max = this.global_x_max - pan * panned_range;
				min = this.global_x_max - pan * panned_range - zoomed_range;
			}
			else
			{
				min = this.global_x_min + pan * panned_range;
				max = this.global_x_min + pan * panned_range + zoomed_range;
			}
		}
		
		public void GetYMinMax(double pan, double zoom, out double min, out double max)
		{
			double y_min = this.ExtendedGlobalYMin;
			double y_max = this.ExtendedGlobalYMax;
			double global_range = y_max - y_min;
			double zoomed_range = global_range / zoom;
			double panned_range = global_range - zoomed_range;
			
			min = y_min + (1.0 - pan) * panned_range;
			max = y_min + (1.0 - pan) * panned_range + zoomed_range;
		}
		
		public void SetXAxis(double pan, double zoom)
		{
			double new_world_min, new_world_max;
			this.GetXMinMax(pan, zoom, out new_world_min, out new_world_max);
			
			if (this.XMin != new_world_min)
				this.XMin = new_world_min;

			if (this.XMax != new_world_max)
				this.XMax = new_world_max;
		}

		public void SetYAxis(double pan, double zoom)
		{
			double new_world_min, new_world_max;
			this.GetYMinMax(pan, zoom, out new_world_min, out new_world_max);

			if (this.YMin != new_world_min)
				this.YMin = new_world_min;

			if (this.YMax != new_world_max)
				this.YMax = new_world_max;
		}
		
		public double GetWorldXCoord(int x)
		{
			return this.GetWorldXCoord(x, true);
		}
		
		public double GetWorldYCoord(int y)
		{
			return this.GetWorldYCoord(y, true);
		}

		public double GetWorldXCoord(int x, bool clip)
		{
			PointF p = new PointF((float)x, 0f);
			PointF p_min = this.plotsurface.PhysicalXAxis1Cache.PhysicalMin;
			PointF p_max = this.plotsurface.PhysicalXAxis1Cache.PhysicalMax;
			return this.plotsurface.XAxis1.PhysicalToWorld(p, p_min, p_max, clip);
		}
		
		public double GetWorldYCoord(int y, bool clip)
		{
			PointF p = new PointF(0f, (float)y);
			PointF p_min = this.plotsurface.PhysicalYAxis1Cache.PhysicalMin;
			PointF p_max = this.plotsurface.PhysicalYAxis1Cache.PhysicalMax;
			return this.plotsurface.YAxis1.PhysicalToWorld(p, p_min, p_max, clip);
		}
		
		public int GetPhysicalXCoord(double x)
		{
			return this.GetPhysicalXCoord(x, true);
		}
		
		public int GetPhysicalYCoord(double y)
		{
			return this.GetPhysicalYCoord(y, true);
		}
		
		public int GetPhysicalXCoord(double x, bool clip)
		{
			PointF p_min = this.plotsurface.PhysicalXAxis1Cache.PhysicalMin;
			PointF p_max = this.plotsurface.PhysicalXAxis1Cache.PhysicalMax;
			return (int)this.plotsurface.XAxis1.WorldToPhysical(x, p_min, p_max, clip).X;
		}

		public int GetPhysicalYCoord(double y, bool clip)
		{
			PointF p_min = this.plotsurface.PhysicalYAxis1Cache.PhysicalMin;
			PointF p_max = this.plotsurface.PhysicalYAxis1Cache.PhysicalMax;
			return (int)this.plotsurface.YAxis1.WorldToPhysical(y, p_min, p_max, clip).Y;
		}

		public Gdk.Pixbuf GetPixbuf()
		{
			return this.GetPixbuf(this.plotsurface.Width, this.plotsurface.Height);
		}
		
		public Gdk.Pixbuf GetPixbuf(int width, int height)
		{
			
			Bitmap bmp = this.GetBitmap(width, height);
			System.IO.MemoryStream stream = new System.IO.MemoryStream();
			bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
			
			return new Gdk.Pixbuf(stream.ToArray());
		}
		
		public Gdk.Pixbuf GetPixbuf(float xdpi, float ydpi)
		{
			return this.GetPixbuf(this.plotsurface.Width, this.plotsurface.Height, xdpi, ydpi);
		}
		
		public Gdk.Pixbuf GetPixbuf(int width, int height, float xdpi, float ydpi)
		{
			Bitmap bmp = this.GetBitmap(width, height, xdpi, ydpi);
			System.IO.MemoryStream stream = new System.IO.MemoryStream();
			bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
			
			return new Gdk.Pixbuf(stream.ToArray());
		}
		
		public Gdk.Pixbuf GetLineStylePixbuf(Spectrum spectrum)
		{
			Bitmap bmp = new Bitmap(50, 11);
			Graphics g = Graphics.FromImage(bmp);
			Pen p = new Pen(spectrum.Color, spectrum.LineWidth);
			p.DashStyle = (DashStyle)Enum.Parse(typeof(DashStyle), spectrum.LineStyle.ToString());
			g.DrawLine(p, 0f, 5f, 50f, 5f);
			System.IO.MemoryStream stream = new System.IO.MemoryStream();
			bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
			
			return new Gdk.Pixbuf(stream.ToArray());
		}
		
		public Gdk.Pixbuf GetLineStylePixbuf(LineStyle style)
		{
			Bitmap bmp = new Bitmap(50, 11);
			Graphics g = Graphics.FromImage(bmp);
			Pen p = new Pen(Color.Black, this.default_line_width);
			p.DashStyle = (DashStyle)Enum.Parse(typeof(DashStyle), style.ToString());
			g.DrawLine(p, 5f, 5f, 45f, 5f);
			System.IO.MemoryStream stream = new System.IO.MemoryStream();
			bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
			
			return new Gdk.Pixbuf(stream.ToArray());
		}

		public Gdk.Pixbuf GetLineStylePixbuf(CapStyle style)
		{
			Bitmap bmp = new Bitmap(50, 11);
			Graphics g = Graphics.FromImage(bmp);
			Pen p = new Pen(Color.Black, this.default_line_width);
			g.DrawLine(p, 5f, 5f, 45f, 5f);
			p = new Pen(Color.Black, this.default_line_width);
			p.CustomStartCap = LineCaps.GetLineCap(style, this.default_line_width);
			g.DrawLine(p, 5f, 5f, 6f, 5f);
			System.IO.MemoryStream stream = new System.IO.MemoryStream();
			bmp.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
			
			return new Gdk.Pixbuf(stream.ToArray());
		}
		
		private void __ResetThumbnail()
		{
			using (Graphics g = Graphics.FromImage(this.__thumbnail_bitmap))
			{
				g.Clear(Color.Transparent);
			}
			
			System.IO.MemoryStream stream = new System.IO.MemoryStream();
			this.__thumbnail_bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
			this.__thumbnail_pixbuf = new Gdk.Pixbuf(stream.ToArray());
		}
		
		private void __ErrorThumbnail()
		{
			using (Graphics g = Graphics.FromImage(this.__thumbnail_bitmap))
			{
				g.Clear(Color.LightGray);
			}
			
			System.IO.MemoryStream stream = new System.IO.MemoryStream();
			this.__thumbnail_bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
			this.__thumbnail_pixbuf = new Gdk.Pixbuf(stream.ToArray());
		}
		
		private void __CreateThumbnail()
		{
			try
			{
				if (this.__data_sets.Count == 0)
					return;
			
				this.__thumbnail_plotsurface.Clear();
				this.__thumbnail_plotsurface.PlotBackColor = Color.Transparent;
				
				double gxmin = double.MaxValue;
				double gxmax = double.MinValue;
				double gymin = double.MaxValue;
				double gymax = double.MinValue;
				
				bool empty = true;
				
				foreach (DataSet ds in this.DataSets.Values)
				{
					if (!ds.IsVisible || ds.IsPreview)
						continue;
					
					LinePlot lp = new LinePlot();
					
					double xmin = ds.XMin();
					double xmax = ds.XMax();
					
					double ymin, ymax;
					try
					{
						ymin = ds.YMin();
						ymax = ds.YMax();
					}
					catch (NotFiniteNumberException)
					{
						ymin = gymin;
						ymax = gymax;
					}
					
					if (xmin < gxmin) gxmin = xmin;
					if (xmax > gxmax) gxmax = xmax;
					if (ymin < gymin) gymin = ymin;
					if (ymax > gymax) gymax = ymax;
					
					lp.AbscissaData = ds.X;
					lp.OrdinateData = ds.Y;
					lp.Pen = new Pen(ds.Color, 1f);
					
					this.__thumbnail_plotsurface.Add(lp);
					empty = false;
				}
				
				if (empty)
				{
					this.__ResetThumbnail();
					return;
				}
				
				if (double.IsInfinity(gymin) || double.IsInfinity(gymax))
				{
					gymin = 0.0;
					gymax = 1.0;
				}
				
				this.__thumbnail_plotsurface.XAxis1.WorldMin = gxmin;
				this.__thumbnail_plotsurface.XAxis1.WorldMax = gxmax;
				this.__thumbnail_plotsurface.YAxis1.WorldMin = gymin;
				this.__thumbnail_plotsurface.YAxis1.WorldMax = gymax;
				this.__thumbnail_plotsurface.XAxis1.Hidden = true;
				this.__thumbnail_plotsurface.YAxis1.Hidden = true;
				this.__thumbnail_plotsurface.XAxis1.Reversed = this.reverse_x_axes;
				
				using (Graphics g = Graphics.FromImage(this.__thumbnail_bitmap))
				{
					g.Clear(Color.Transparent);
					this.__thumbnail_plotsurface.Draw(g, new Rectangle(-8, 0, thumbnail_width+16, thumbnail_height));
					g.DrawRectangle(new Pen(Brushes.LightGray), 0, 8, thumbnail_width-1, thumbnail_height-15);
				}
				
				System.IO.MemoryStream stream = new System.IO.MemoryStream();
				this.__thumbnail_bitmap.Save(stream, System.Drawing.Imaging.ImageFormat.Png);
				
				this.__thumbnail_pixbuf = new Gdk.Pixbuf(stream.ToArray());
			}
			catch (Exception ex)
			{
				// if something goes wrong, show error thumbnail
				Console.WriteLine("Exception in NPlotEngine.__CreateThumbnail: " + ex.ToString());
				this.__ErrorThumbnail();
			}
		}
		public Bitmap GetBitmap()
		{
			return this.GetBitmap(this.plotsurface.Width, this.plotsurface.Height);
		}
		
		public Bitmap GetBitmap(int width, int height, Color color)
		{
			Bitmap bmp = new Bitmap(width, height);
			Graphics g = Graphics.FromImage(bmp);
			g.Clear(color);
			this.Draw(g, new Rectangle(0, 0, bmp.Width-1, bmp.Height-1));
			this.DrawOverlays(g, true);
			this.DrawLegend(g);
			return bmp;
		}
		
		public Bitmap GetBitmap(int width, int height)
		{
			return this.GetBitmap(width, height, Color.White);
		}
		
		public Bitmap GetBitmap(float xdpi, float ydpi)
		{
			return this.GetBitmap(this.plotsurface.Width, this.plotsurface.Height, xdpi, ydpi);
		}
		
		public Bitmap GetBitmap(int width, int height, float xdpi, float ydpi)
		{
			PlatformID osversion = Environment.OSVersion.Platform;
			if (osversion != PlatformID.Win32NT      &&
			    osversion != PlatformID.Win32S       &&
			    osversion != PlatformID.Win32Windows &&
			    osversion != PlatformID.WinCE)
				return this.GetBitmap(width, height);
			
			Bitmap bmp = new System.Drawing.Bitmap((int)Math.Round(width *xdpi/72f), 
			                                       (int)Math.Round(height*ydpi/72f));
			bmp.SetResolution(xdpi, ydpi);
			Graphics g = Graphics.FromImage(bmp);
			g.PageUnit  = GraphicsUnit.Point;
			g.Clear(Color.White);
			this.Draw(g, new Rectangle(0, 0,
			                           (int)Math.Round((bmp.Width -1)*72f/xdpi),
			                           (int)Math.Round((bmp.Height-1)*72f/ydpi)
			                           )
			          );
			this.DrawOverlays(g, true);
			this.DrawLegend(g);
			return bmp;
		}

		public Cairo.Surface GetCairoSurface()
		{
			Cairo.Context context = Gdk.CairoHelper.Create(this.GtkWidget.GdkWindow);
			Cairo.Surface surface = context.Target;

			((IDisposable)context).Dispose();

			return surface;
		}
		
		public Cairo.Context GetCairoContext()
		{
			Cairo.Context context = Gdk.CairoHelper.Create(this.GtkWidget.GdkWindow);
			Cairo.Surface surface = context.Target;

			Cairo.Context cr = new Cairo.Context(surface);
			
			((IDisposable)context).Dispose();

			return cr;
		}

		public void Draw(Graphics g, Rectangle bounds)
		{
			if (this.EstimatedLegendHeight > bounds.Height || !this.LegendVisible)
				this.plotsurface.Legend = null;
			else
				this.plotsurface.Legend = this.legend;
			
			this.plotsurface.Draw(g, bounds);
		}
		
		public void DrawLegend(Graphics g)
		{
			this.plotsurface.DrawLegend(g);
		}

		public void DrawOverlays(Graphics g, bool only_static_overlays)
		{
			if (this.selection != null)
			{
				if ((only_static_overlays && this.selection.IsStatic) ||
				    !only_static_overlays)
					this.selection.Draw(g);
				else
					this.AsyncRefresh(true);
			}
			
			if (this.horizontal_selection != null)
			{
				if ((only_static_overlays && this.horizontal_selection.IsStatic) ||
				    !only_static_overlays)
					this.horizontal_selection.Draw(g);
				else
					this.AsyncRefresh(true);
			}

			if (this.vertical_selection != null)
			{
				if ((only_static_overlays && this.vertical_selection.IsStatic) ||
				    !only_static_overlays)
					this.vertical_selection.Draw(g);
				else
					this.AsyncRefresh(true);
			}
			
			lock (this.data_set_selections)
			{
				foreach (DataSetSelection dss in this.data_set_selections)
				{
					dss.Draw(g);
				}
			}

			lock (this.markers)
			{
				if (this.markers.Count > 0)
				{
					int x_min = this.PhysicalXMin;
					int x_max = this.PhysicalXMax;
					int y_min = this.PhysicalYMax;
					int y_max = this.PhysicalYMin;
					
					Pen p = new System.Drawing.Pen(Color.Black);
					SolidBrush b = new SolidBrush(Color.Black);
					
					foreach (CrossHair marker in this.markers)
					{
						PointF pt1 = new PointF(x_min, marker.Y);
						PointF pt2 = new PointF(x_max, marker.Y);
						g.DrawLine(p, pt1, pt2);
						
						pt1 = new PointF(marker.X, y_min);
						pt2 = new PointF(marker.X, y_max);
						g.DrawLine(p, pt1, pt2);
						
						Rectangle r = new Rectangle(marker.X+5, marker.Y-35, 200, 35);
						string s = string.Format("x={0}\ny={1}", 
						                         this.GetWorldXCoord(marker.X).ToString("f2"),
						                         this.GetWorldYCoord(marker.Y).ToString("f2"));
						g.DrawString(s, new Font("Arial", 10f), b, r);
					}
				}
			}

			lock (this.annotations)
			{
				if (this.annotations.Count > 0)
				{
					SmoothingMode sav_smoothing_mode = g.SmoothingMode;
					g.SmoothingMode = SmoothingMode.AntiAlias;
					
					foreach (Annotation a in this.annotations)
					{
						a.Draw(g);
					}
					
					g.SmoothingMode = sav_smoothing_mode;
				}
			}
		}

		public void SetSelection(int x, int y, int width, int height)
		{
			if (this.disable_selections)
				return;

			try
			{
				this.selection = new RubberBandSelection(this, x, y, width, height);
				this.selection.Changed += delegate(object o, EventArgs e) {
					this.AsyncRefresh(true);
				};
				this.Refresh(true);
			}
			catch (NullReferenceException)
			{
				// axes may not be initialized
				this.selection = null;
			}
		}
		
		public void RequestUpdate()
		{
			this.data_sets_changed = true;
		}
		
		public void AsyncRefresh()
		{
			this.refresh_is_pending = true;
		}
		
		public void AsyncRefresh(bool only_overlays)
		{
			this.refresh_overlay_is_pending = true;
		}
		
		public void __AsyncRefresh()
		{
			this.Refresh();
		}
		
		public void StartAutoUpdate()
		{
			if (this.__thread_running)
				return;
			
			this.do_process_changes = new Thread(new ThreadStart(this.__DoProcessChanges));
			this.do_process_changes.IsBackground = true;
			this.__thread_running = true;
			this.do_process_changes.Start();
		}
		
		public void StopAutoUpdate()
		{
			this.__thread_running = false;
		}
		
		public void SetBackgroundPlot(IPlotEngine iplot, BackgroundType bg_type)
		{
			NPlotEngine plot = iplot as NPlotEngine;
			
			lock (this)
			{
				bool sav_limits = false;
				double xmin = 0.0, xmax = 0.0, ymin = 0.0, ymax = 0.0;
				if (this.plotsurface.XAxis2 != null && this.plotsurface.YAxis2 != null &&
				    !double.IsInfinity(this.plotsurface.XAxis2.WorldMin) &&
				    !double.IsNaN(this.plotsurface.XAxis2.WorldMin))
				{
					xmin = this.plotsurface.XAxis2.WorldMin;
					xmax = this.plotsurface.XAxis2.WorldMax;
					ymin = this.plotsurface.YAxis2.WorldMin;
					ymax = this.plotsurface.YAxis2.WorldMax;
					sav_limits = true;
				}
				
				this.__background_data_sets.Clear();
				
				foreach (string id in plot.DataSets.Keys)
				{
					if (!plot.DataSets[id].IsVisible || plot.DataSets[id].IsPreview)
						continue;
					
					DataSet ds = new DataSet(plot.DataSets[id]);
					
					ds.XLabel    = plot.DataSets[id].XLabel;
					ds.YLabel    = plot.DataSets[id].YLabel;
					ds.Label     = plot.DataSets[id].Label + "*";
					ds.Id        = plot.DataSets[id].Id + "%%%background%%%";
					ds.Color     = plot.DataSets[id].Color;
					ds.LineStyle = plot.DataSets[id].LineStyle;
					ds.LineWidth = plot.DataSets[id].LineWidth;
					ds.IsVisible = plot.DataSets[id].IsVisible;
					ds.ZOrder    = plot.DataSets[id].ZOrder;
					
					switch (bg_type)
					{
						
					case BackgroundType.Unchanged:
						break;
						
					case BackgroundType.Grayscaled:
						Color c = ds.Color;
						int luma = (int)(c.R*0.3f + c.G*0.59f + c.B*0.11f);
						ds.Color = Color.FromArgb(luma, luma, luma);
						break;
						
					case BackgroundType.Dashed:
						ds.LineStyle = LineStyle.Dash;
						break;
						
					}
					
					ds.IsBackground = true;
					
					this.__background_data_sets.Add(id, ds);
				}
				
				this.xaxis2.Label = plot.XLabel + "*";
				this.yaxis2.Label = plot.YLabel + "*";

				if (sav_limits)
				{
					this.xaxis2.WorldMin = xmin;
					this.xaxis2.WorldMax = xmax;
					this.yaxis2.WorldMin = ymin;
					this.yaxis2.WorldMax = ymax;
				}
				else
				{
					this.xaxis2.WorldMin = plot.xaxis1.WorldMin;
					this.xaxis2.WorldMax = plot.xaxis1.WorldMax;
					this.yaxis2.WorldMin = plot.yaxis1.WorldMin;
					this.yaxis2.WorldMax = plot.yaxis1.WorldMax;
				}
				
				this.plotsurface.XAxis2 = new LinearAxis(this.xaxis2);
				this.plotsurface.YAxis2 = new LinearAxis(this.yaxis2);

				this.xaxis_changed = true;
				this.yaxis_changed = true;
			}
		}
		
		public void ClearBackgroundPlot()
		{
			lock (this)
				this.__background_data_sets.Clear();
		}
		
		public void SyncAxes()
		{
			lock (this)
			{
				this.xaxis2.WorldMin = this.xaxis1.WorldMin;
				this.xaxis2.WorldMax = this.xaxis1.WorldMax;
				this.yaxis2.WorldMin = this.yaxis1.WorldMin;
				this.yaxis2.WorldMax = this.yaxis1.WorldMax;
				this.xaxis_changed = true;
				this.yaxis_changed = true;
			}
		}

		public double XMin
        {
			get { return this.xaxis1.WorldMin; }
            set
            {
				lock (this)
				{
					if (this.__background_data_sets.Count > 0 && this.lock_axes)
					{
						double rel_change = (value - this.xaxis1.WorldMin) / (this.xaxis1.WorldMax - this.xaxis1.WorldMin);
						if (!double.IsNaN(rel_change))
							this.xaxis2.WorldMin += (this.xaxis2.WorldMax - this.xaxis2.WorldMin) * rel_change;
					}
					this.xaxis1.WorldMin = value;
					this.xaxis_changed = true;
				}
			}
        }

        public double XMax
        {
            get { return this.xaxis1.WorldMax; }
            set
            {
				lock (this)
				{
					if (this.__background_data_sets.Count > 0 && this.lock_axes)
					{
						double rel_change = (value - this.xaxis1.WorldMax) / (this.xaxis1.WorldMax - this.xaxis1.WorldMin);
						if (!double.IsNaN(rel_change))
							this.xaxis2.WorldMax += (this.xaxis2.WorldMax - this.xaxis2.WorldMin) * rel_change;
					}
					this.xaxis1.WorldMax = value;
					this.xaxis_changed = true;
				}
			}
        }

        public double YMin
        {
            get 
			{
				if (this.use_second_axes && this.__background_data_sets.Count == 0)
					return this.yaxis2.WorldMin;
				else
					return this.yaxis1.WorldMin; 
			}
            set
            {
				lock (this)
				{
					if (this.__background_data_sets.Count > 0 && this.lock_axes)
					{
						double rel_change = (value - this.yaxis1.WorldMin) / (this.yaxis1.WorldMax - this.yaxis1.WorldMin);
						if (!double.IsNaN(rel_change))
							this.yaxis2.WorldMin += (this.yaxis2.WorldMax - this.yaxis2.WorldMin) * rel_change;
					}
					if (this.use_second_axes && this.__background_data_sets.Count == 0)
						this.yaxis2.WorldMin = value;
					else
						this.yaxis1.WorldMin = value;
					this.yaxis_changed = true;
				}
			}
        }

        public double YMax
        {
            get 
			{
				if (this.use_second_axes && this.__background_data_sets.Count == 0)
					return this.yaxis2.WorldMax;
				else
					return this.yaxis1.WorldMax; 
			}
            set
            {
				lock (this)
				{
					if (this.__background_data_sets.Count > 0 && this.lock_axes)
					{
						double rel_change = (value - this.yaxis1.WorldMax) / (this.yaxis1.WorldMax - this.yaxis1.WorldMin);
						if (!double.IsNaN(rel_change))
							this.yaxis2.WorldMax += (this.yaxis2.WorldMax - this.yaxis2.WorldMin) * rel_change;
					}
					if (this.use_second_axes && this.__background_data_sets.Count == 0)
						this.yaxis2.WorldMax = value;
					else
						this.yaxis1.WorldMax = value;
					this.yaxis_changed = true;
				}
			}
        }

		public double GlobalXMin
		{
			get { return this.global_x_min;	}
		}

		public double GlobalXMax
		{
			get { return this.global_x_max; }
		}

		public double GlobalYMin
		{
			get { return this.global_y_min; }
		}

		public double GlobalYMax
		{
			get { return this.global_y_max; }
		}
		
		public double ExtendedGlobalXMin
		{
			get
			{
				double x_min = this.global_x_min - (this.global_x_max-this.global_x_min) * this.x_extend_factor;
				double x_max = this.global_x_max + (this.global_x_max-this.global_x_min) * this.x_extend_factor;
				if (x_max-x_min < this.x_min_range)
					x_min -= this.x_min_range/2.0;
				return x_min;
			}
		}
		
		public double ExtendedGlobalXMax
		{
			get
			{
				double x_min = this.global_x_min - (this.global_x_max-this.global_x_min) * this.x_extend_factor;
				double x_max = this.global_x_max + (this.global_x_max-this.global_x_min) * this.x_extend_factor;
				if (x_max-x_min < this.x_min_range)
					x_max += this.x_min_range/2.0;
				return x_max;
			}
		}

		public double ExtendedGlobalYMin
		{
			get
			{
				double y_min = this.global_y_min - (this.global_y_max-this.global_y_min) * this.y_extend_factor;
				double y_max = this.global_y_max + (this.global_y_max-this.global_y_min) * this.y_extend_factor;
				if (y_max-y_min < this.y_min_range)
					y_min -= this.y_min_range/2f;
				return y_min;
			}
		}
		
		public double ExtendedGlobalYMax
		{
			get
			{
				double y_min = this.global_y_min - (this.global_y_max-this.global_y_min) * this.y_extend_factor;
				double y_max = this.global_y_max + (this.global_y_max-this.global_y_min) * this.y_extend_factor;
				if (y_max-y_min < this.y_min_range)
					y_max += this.y_min_range/2f;
				return y_max;
			}
		}

		public string XLabel
		{
			get 
			{ 
				if (this.use_second_axes)
					return this.xaxis2.Label;
				else
					return this.xaxis1.Label;  
			}
			set	
			{ 
				lock (this)
				{
					if (this.use_second_axes)
						this.xaxis2.Label = value;
					else
						this.xaxis1.Label = value; 
					this.xaxis_changed = true;
				}
			}
		}

		public string YLabel
		{
			get 
			{ 
				if (this.use_second_axes)
					return this.yaxis2.Label;
				else
					return this.yaxis1.Label;  
			}
			set	
			{ 
				lock (this)
				{
					if (this.use_second_axes)
						this.yaxis2.Label = value;
					else
						this.yaxis1.Label = value; 
					this.yaxis_changed = true;
				}
			}
		}

		public bool IsEmpty
		{
			get
			{ 
				lock (this)
					return (this.__data_sets.Count == 0);
			}
		}
		
		public bool IsValid
		{
			get
			{
				lock (this)
					return (!double.IsNaN(this.XMin) && !double.IsNaN(this.XMax) && !double.IsNaN(this.YMin) && !double.IsNaN(this.YMax) &&
					        !double.IsInfinity(this.XMin) && !double.IsInfinity(this.XMax) && !double.IsInfinity(this.YMin) && !double.IsInfinity(this.YMax));
			}
		}
		
		public float DefaultLineWidth
		{
			get { return this.default_line_width;  }
			set	{ this.default_line_width = value; }
		}
		
		public double XExtendFactor
		{
			get { return this.x_extend_factor;  }
			set { this.x_extend_factor = value; }
		}

		public double YExtendFactor
		{
			get { return this.y_extend_factor;  }
			set { this.y_extend_factor = value; }
		}
		
		public double YMinRange
		{
			get { return this.y_min_range;  }
			set { this.y_min_range = value; }
		}

		public int PhysicalXMin
		{
			get { return this.plotsurface.PhysicalXAxis1Cache.PhysicalMin.X; }
		}
		
		public int PhysicalXMax
		{
			get { return this.plotsurface.PhysicalXAxis1Cache.PhysicalMax.X; }
		}
		
		public int PhysicalYMin
		{
			get { return this.plotsurface.PhysicalYAxis1Cache.PhysicalMin.Y; }
		}
		
		public int PhysicalYMax
		{
			get { return this.plotsurface.PhysicalYAxis1Cache.PhysicalMax.Y; }
		}

		public RubberBandSelection Selection
		{
			get { return this.selection;  }
			set 
			{ 
				if (!this.disable_selections)
					this.selection = value; 
			}
		}
		
		public HorizontalSelection HSelection
		{
			get { return this.horizontal_selection;  }
			set
			{ 
				if (!this.disable_selections)
					this.horizontal_selection = value; 
			}
		}
		
		public VerticalSelection VSelection
		{
			get { return this.vertical_selection;  }
			set 
			{ 
				if (!this.disable_selections)
					this.vertical_selection = value; 
			}
		}
		
		public List<CrossHair> Markers
		{
			get 
			{
				lock (this.markers)
					return this.markers;  
			}
		}

		public List<Annotation> Annotations
		{
			get 
			{ 
				lock (this.annotations)
					return this.annotations;
			}
		}
		
		public List<DataSetSelection> DataSetSelections
		{
			get 
			{ 
				// DrawOverlays may block the data set selections
				lock (this.data_set_selections)
					return this.data_set_selections;
			}
		}

		public Rectangle Bounds
		{
			get
			{
				return new Rectangle(0, 0, this.plotsurface.Width, this.plotsurface.Height);
			}
		}
		
		public Gdk.Color BackgroundColor
		{
			get { return this.plotsurface.BackgroundColor;  }
			set { this.plotsurface.BackgroundColor = value; }
		}
		
		public Color PlotBackColor
		{
			set { this.plotsurface.PlotBackColor = value; }
		}
		
		public string Title
		{
			get { return this.plotsurface.Title;  }
			set { this.plotsurface.Title = value; }
		}
		
		public Dictionary<string,DataSet> DataSets
		{
			get
			{
				lock (this)
					return this.__data_sets;
			}
			set
			{
				lock (this)
				{
					this.__data_sets = value;
					this.data_sets_changed = true;
				}
			}
		}

		public Widget GtkWidget
		{
			get { return this.plotsurface; }
		}
		
		public bool IsDragging
		{
			get { return this.is_dragging; }
			set 
			{
				this.is_dragging = value;
				
				if (this.is_dragging)
					this.GtkWidget.GdkWindow.Cursor = new Gdk.Cursor(Gdk.CursorType.Fleur);
				else
					this.GtkWidget.GdkWindow.Cursor = new Gdk.Cursor(Gdk.CursorType.Tcross);
			}
		}
		
		public bool DisableSelections
		{
			get { return this.disable_selections;  }
			set 
			{
				this.disable_selections = value;
				if (value)
				{
					this.selection = null;
					this.vertical_selection   = null;
					this.horizontal_selection = null;
				}
			}
		}
		
		public double EstimatedLegendHeight
		{
			get
			{ 
				if (this.legend == null)
					return 0f;
				return this.plotsurface.EstimatedLegendHeight(this.legend.Font); 
			}
		}
		
		public int Width
		{
			get { return this.plotsurface.Width; }
		}
		
		public int Height
		{
			get { return this.plotsurface.Height; }
		}
		
		public bool LegendVisible
		{
			get { return this.legend_visible;  }
			set 
			{ 
				bool need_update = (value != this.legend_visible);
				if (value)
					this.plotsurface.Legend = this.legend;
				else
					this.plotsurface.Legend = null;
				this.legend_visible = value; 
				if (need_update)
					this.refresh_is_pending = true;
				this.plotsurface.CheckLegendSize();
			}
		}
		
		public int PlotTypeId
		{
			get { return this.plot_type_id;  }
			set { this.plot_type_id = value; }
		}
		
		public bool LockAxes
		{
			get { return this.lock_axes;  }
			set { this.lock_axes = value; }
		}
		
		public bool UseSecondAxes
		{
			get { return this.use_second_axes;  }
			set { this.use_second_axes = value; }
		}
		
		public Gdk.Pixbuf Thumbnail
		{
			get
			{ 
				lock (this)
					return this.__thumbnail_pixbuf;
			}
		}
		
		public bool ReverseXAxes
		{
			get { return this.reverse_x_axes;  }
			set 
			{ 
				if (this.reverse_x_axes != value)
				{
					this.reverse_x_axes = value;
					this.xaxis_changed = true;
					
					if (this.XAxesReversed != null)
						this.XAxesReversed(this, EventArgs.Empty);
				}
			}
		}
		
		public bool HasBackgroundPlot
		{
			get { return this.__background_data_sets != null && this.__background_data_sets.Count > 0; }
		}
	}
}