// 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.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Xml.Serialization;
using System.IO;
using Gtk;

using Hiperscan.Spectroscopy;
using Hiperscan.Unix;


namespace Hiperscan.GUI.Figure
{
	
	public enum CapStyle : int
	{
		ArrowAnchor,
		DiamondAnchor,
		RoundAnchor,
		SquareAnchor
	}

	public static class LineCaps
	{
		public static CustomLineCap GetLineCap(CapStyle style, float line_width)
		{
//			return new AdjustableArrowCap(1.2f*line_width, 2f*line_width);
			GraphicsPath path = new GraphicsPath();
			float k;

			switch (style)
			{

			case CapStyle.ArrowAnchor:
				k = (float)Math.Sqrt(line_width*0.3f);
				path.AddLine(0f, -5f*k, -2f*k, -5f*k);
				path.AddLine(-2f*k, -5f*k, 0f, 0f);
				path.AddLine(0f, 0f, 2f*k, -5f*k);
				path.AddLine(2f*k, -5f*k, 0f, -5f*k);
				path.AddLine(0f, -4.5f*k, -1.5f*k, -4.5f*k);
				path.AddLine(-1.5f*k, -4.5f*k, 0f, -0.5f);
				path.AddLine(0f, -0.5f, 1.5f*k, -4.5f*k);
				path.AddLine(1.5f*k, -4.5f*k, 0f, -4.5f*k);
				path.AddLine(0f, -4f*k, -1f*k, -4f*k);
				path.AddLine(-1f*k, -4f*k, 0f, -1f);
				path.AddLine(0f, -1f, 1f*k, -4f*k);
				path.AddLine(1f*k, -4f*k, 0f, -4f*k);
				path.AddLine(0f, -3.5f*k, -0.5f*k, -3.5f*k);
				path.AddLine(-0.5f*k, -3.5f*k, 0f, -1.5f);
				path.AddLine(0f, -1.5f, 0.5f*k, -3.5f*k);
				path.AddLine(0.5f*k, -3.5f*k, 0f, -3.5f*k);
				break;

			case CapStyle.DiamondAnchor:
				k = (float)Math.Sqrt(line_width*0.5f);
				path.AddLine(0f, -2f*k, -2f*k, 0f);
				path.AddLine(-2f*k, 0f, 0f, 2f);
				path.AddLine(0f, 2f, 2f*k, 0f);
				path.AddLine(2f*k, 0f, 0f, -2f*k);
				path.AddLine(0f, -1.5f*k, -1.5f*k, 0f);
				path.AddLine(-1.5f*k, 0f, 0f, 1.5f);
				path.AddLine(0f, 1.5f, 1.5f*k, 0f);
				path.AddLine(1.5f*k, 0f, 0f, -1.5f*k);
				path.AddLine(0f, -1f*k, -1f*k, 0f);
				path.AddLine(-1f*k, 0f, 0f, 1f);
				path.AddLine(0f, 1f, 1f*k, 0f);
				path.AddLine(1f*k, 0f, 0f, -1f*k);
				path.AddLine(0f, -0.5f*k, -0.5f*k, 0f);
				path.AddLine(-0.5f*k, 0f, 0f, 0.5f);
				path.AddLine(0f, 0.5f, 0.5f*k, 0f);
				path.AddLine(0.5f*k, 0f, 0f, -0.5f*k);
				break;

			case CapStyle.RoundAnchor:
				k = (float)Math.Sqrt (line_width * 0.2f);
				path.AddEllipse(-2f, -2f, 4f, 4f);
				path.AddEllipse(-1.5f, -1.5f, 3f, 3f);
				path.AddEllipse(-1f, -1f, 2f, 2f);
				path.AddEllipse(-0.5f, -0.5f, 1f, 1f);
				break;

			case CapStyle.SquareAnchor:
				k = (float)Math.Sqrt (line_width * 0.2f);
				path.AddRectangle(new RectangleF(-2f, -2f, 4f, 4f));
				path.AddRectangle(new RectangleF(-1.5f, -1.5f, 3f, 3f));
				path.AddRectangle(new RectangleF(-1f, -1f, 2f, 2f));
				path.AddRectangle(new RectangleF(-0.5f, -0.5f, 1f, 1f));
				break;

			}

			CustomLineCap line_cap = new CustomLineCap(null, path);
			line_cap.SetStrokeCaps(LineCap.Round, LineCap.Round);
			return line_cap;
		}
	}

	public class RubberBandSelection
	{
		private IPlotEngine plot;
		private int x, y, width, height;
		private int _x, _y, _w, _h;
		private bool is_static = false;
		
		public event EventHandler<EventArgs> Changed;
		
		
		public RubberBandSelection(IPlotEngine p, int x, int y, int width, int height)
		{
			this.plot = p;
			this.x = x;
			this.y = y;
			this.width  = width;
			this.height = height;
			this.Update();
		}
		
		private void Update()
		{
			try
			{
				int x_min = this.plot.PhysicalXMin;
				int x_max = this.plot.PhysicalXMax;
				int y_min = this.plot.PhysicalYMax;
				int y_max = this.plot.PhysicalYMin;
			
				if (this.width >= 0)
				{
					_x = this.x;
					_w = this.width;
				}
				else
				{
					_x = this.x + this.width;
					_w = -this.width;
				}
			
				if (this.height >= 0)
				{
					_y = this.y;
					_h = this.height;
				}
				else
				{
					_y = this.y + this.height;
					_h = -this.height;
				}
			
				// crop to diagram size
				if (_x < x_min)
				{
					_w = _w - x_min + _x;
					_x = x_min;
				}
				if (_x > x_max) _x = x_max;
				if (_x+_w > x_max) _w = x_max-_x; 
			
				if (_y < y_min)
				{
					_h = _h - y_min + _y;
					_y = y_min;
				}
				if (_y > y_max) _y = y_max;
				if (_y+_h > y_max) _h = y_max-_y; 
			}
			catch (NullReferenceException ex)
			{
				// may happen if plot is changed asynchronously
				Console.WriteLine("Exception in RubberBandSelection.Update(): " + ex.ToString());
			}
		}
		
		public void Draw(Graphics g)
		{
			Rectangle r = new Rectangle(this.X, this.Y, this.Width, this.Height);
			Brush b = new SolidBrush(Color.FromArgb(50, Color.Blue));
			g.FillRectangle(b, r);
			//			System.Drawing.Pen p = new System.Drawing.Pen(Color.Black);
			//			g.DrawRectangle(p, r);
		}
		
		public void SetSize(int width, int height)
		{
			this.width  = width;
			this.height = height;
			this.Update();

			if (this.Changed != null)
				this.Changed(this, EventArgs.Empty);
		}
		
		public void SetPosition(int x, int y)
		{
			this.x = x;
			this.y = y;
			this.Update();
			
			if (this.Changed != null)
				this.Changed(this, EventArgs.Empty);
		}
		
		public int X
		{
			get { return this._x; }
		}
		
		public int Y
		{
			get { return this._y; }
		}
		
		public int XStart
		{
			get { return this.x; }
		}
		
		public int YStart
		{
			get { return this.y; }
		}
		
		public int Width
		{
			get { return this._w;  }
		}
		
		public int Height
		{
			get { return this._h;  }
		}
		
		public bool IsStatic
		{
			get { return this.is_static;  }
			set { this.is_static = value; }
		}
	}
	
	public class CrossHair : RubberBandSelection
	{
		public CrossHair(IPlotEngine p, int x, int y) : base(p, x, y, 0, 0)
		{
		}
	}
	
	public class HorizontalSelection : RubberBandSelection
	{
		public HorizontalSelection(IPlotEngine p, int x1, int x2) : base(p, x1, 0, x2-x1, int.MaxValue)
		{
		}
	}
	
	public class VerticalSelection : RubberBandSelection
	{
		public VerticalSelection(IPlotEngine p, int y1, int y2) : base(p, 0, y1, int.MaxValue, y2-y1)
		{
		}
	}
	
	public enum AnnotationType
	{
		Text,
		Line,
		Rectangle,
		Ellipse,
		Arrow,
		DoubleArrow
	}
	
	[Serializable()]
	public class Annotation
	{
		protected IPlotEngine plot;
		
		private bool interactive;
		
		public double X { get; set; }
		public double Y { get; set; }
		public double Width  { get; set; }
		public double Height { get; set; }
		
		public bool Visible { get; set; }
		
		public int ColorArgb { get; set; }
		public string Text  { get; set; }
		public string FontFamily { get; set; }
		public float  FontSize   { get; set; }
		public FontStyle FontStyle { get; set; }
		
		public float     LineWidth { get; set; }
		public LineStyle LineStyle { get; set; }
		public CapStyle  CapStyle  { get; set; }
		
		public int PlotTypeId { get; set; }
		
		public AnnotationType Type;
		
		
		protected Annotation()
		{
		}
		
		protected Annotation(IPlotEngine plot, AnnotationType type)
		{
			this.plot = plot;
			this.Visible = false;
			this.Interactive = false;
			this.PlotTypeId = plot.PlotTypeId;
			this.Text = string.Empty;
			this.FontFamily = string.Empty;
			this.Type = type;
		}
		
		protected Annotation(IPlotEngine plot, Annotation a)
		{
			this.plot = plot;
			this.X = a.X;
			this.Y = a.Y;
			this.Width = a.Width;
			this.Height = a.Height;
			this.Visible = a.Visible;
			this.Interactive = a.Interactive;
			this.Text = a.Text;
			this.FontFamily = a.FontFamily;
			this.FontSize = a.FontSize;
			this.ColorArgb = a.ColorArgb;
			this.LineWidth = a.LineWidth;
			this.LineStyle = a.LineStyle;
			this.CapStyle = a.CapStyle;
			this.PlotTypeId = a.PlotTypeId;
			this.Type = a.Type;
		}
		
		public virtual void Draw(Graphics g)
		{
		}
		
		public virtual void Motion(int x, int y, Gdk.ModifierType modifier)
		{
		}
		
		public virtual void ButtonPressed(int x, int y, Gdk.ModifierType modifier)
		{
		}
		
		public virtual void ButtonReleased(int x, int y, Gdk.ModifierType modifier)
		{
		}
		
		public override bool Equals(object obj)
		{
			Annotation a = obj as Annotation;
			
			if (a == null)
				return false;
			
			return (this.CapStyle  == a.CapStyle  &&
			        this.ColorArgb == a.ColorArgb &&
			        this.FontFamily == a.FontFamily &&
			        this.FontSize   == a.FontSize &&
			        this.FontStyle  == a.FontStyle &&
			        this.Height     == a.Height &&
			        this.LineStyle  == a.LineStyle &&
			        this.LineWidth  == a.LineWidth &&
			        this.PlotTypeId == a.PlotTypeId &&
			        this.Text       == a.Text &&
			        this.Width      == a.Width &&
			        this.X          == a.X &&
			        this.Y          == a.Y &&
			        this.Type       == a.Type);
		}
		
		public override int GetHashCode ()
		{
			return  this.CapStyle.GetHashCode()    ^
				    this.ColorArgb.GetHashCode()  ^
					this.FontFamily.GetHashCode() ^
					this.FontSize.GetHashCode()   ^
					this.FontStyle.GetHashCode()  ^
					this.Height.GetHashCode()     ^
					this.LineStyle.GetHashCode()  ^
					this.LineWidth.GetHashCode()  ^
			        this.PlotTypeId.GetHashCode() ^
			        this.Text.GetHashCode()       ^
			        this.Width.GetHashCode()      ^
			        this.X.GetHashCode()          ^
			        this.Y.GetHashCode()          ^
			        this.Type.GetHashCode();
		}
		
		public static Annotation Deserialize(IPlotEngine plot, byte[] data)
		{
			using (MemoryStream stream = new MemoryStream(data))
			{
				XmlSerializer serializer = new XmlSerializer(typeof(Annotation));
				Annotation a = (Annotation)serializer.Deserialize(stream);

				if (a == null)
					throw new Exception(Catalog.GetString("Error while deserialization."));
				
				switch (a.Type)
				{
					
				case AnnotationType.Text:
					return new TextAnnotation(plot, a);
					
				case AnnotationType.Line:
					return new LineAnnotation(plot, a);
					
				case AnnotationType.Rectangle:
					return new RectangleAnnotation(plot, a);
					
				case AnnotationType.Ellipse:
					return new EllipseAnnotation(plot, a);
					
				case AnnotationType.Arrow:
					return new ArrowAnnotation(plot, a);
					
				case AnnotationType.DoubleArrow:
					return new DoubleArrowAnnotation(plot, a);
					
				default:
					// annotation type unknown
					return null;
					
				}					
			}
		}

		public byte[] Serialize()
		{
//			using (FileStream stream = new FileStream("/tmp/delme.xml", FileMode.Create))
//			{
//				XmlSerializer serializer = new XmlSerializer(typeof(Annotation));
//				serializer.Serialize(stream, new Annotation(null, this));
//			}
			using (MemoryStream stream = new MemoryStream())
			{
				XmlSerializer serializer = new XmlSerializer(typeof(Annotation));
				serializer.Serialize(stream, new Annotation(null, this));
				return stream.ToArray();
			}
		}
		
		public virtual bool Interactive
		{
			get { return this.interactive;  }
			set 
			{ 
				if (this.plot != null && this.plot.GtkWidget.GdkWindow != null)
				{
					if (value)
						this.plot.GtkWidget.GdkWindow.Cursor = new Gdk.Cursor(Gdk.CursorType.Pencil);
					else
						this.plot.GtkWidget.GdkWindow.Cursor = new Gdk.Cursor(Gdk.CursorType.Tcross);
				}
				this.interactive = value; 
			}
		}
		
		protected Font Font
		{
			get 
			{ 
				try
				{
					return new Font(this.FontFamily, this.FontSize, this.FontStyle, GraphicsUnit.Pixel); 
				}
				catch
				{
					return new Font(System.Drawing.FontFamily.GenericSansSerif, 12f, GraphicsUnit.Pixel);
				}
			}
		}
		
		protected Color Color
		{
			get { return Color.FromArgb(this.ColorArgb); }
		}
	
		public Region ClippingRegion
		{
			get
			{
				int x = Math.Min(this.plot.PhysicalXMin, this.plot.PhysicalXMax);
				int y = Math.Min(this.plot.PhysicalYMin, this.plot.PhysicalYMax);
				int w = Math.Abs(this.plot.PhysicalXMax - this.plot.PhysicalXMin);
				int h = Math.Abs(this.plot.PhysicalYMax - this.plot.PhysicalYMin);
				Rectangle r = new Rectangle(x, y, w, h);
				return new Region(r);
			}
		}
	}
	
	public class TextAnnotation : Annotation
	{
		
		public TextAnnotation(IPlotEngine plot, string text, Font font, Color color) 
			: base(plot, AnnotationType.Text)
		{
			this.Text  = text;
			this.FontFamily = font.OriginalFontName;
			this.FontSize = font.Size;
			this.FontStyle = font.Style;
			this.ColorArgb = color.ToArgb();
			this.Interactive = true;
			this.Visible = true;
		}
		
		public TextAnnotation(IPlotEngine plot, string text) : 
			this(plot, text, new Font(System.Drawing.FontFamily.GenericSansSerif, 12f, GraphicsUnit.Pixel), Color.Black)
		{
		}
		
		public TextAnnotation(IPlotEngine plot, Annotation a) : base(plot, a)
		{
		}
		
		public override void Draw(Graphics g)
		{
			if (!this.Visible || this.PlotTypeId != this.plot.PlotTypeId)
				return;
			
			float x = (float)this.plot.GetPhysicalXCoord(this.X, false);
			float y = (float)this.plot.GetPhysicalYCoord(this.Y, false);
			
			Brush brush = new SolidBrush(this.Color);
			Region clip = g.Clip;
			g.Clip = this.ClippingRegion;
			g.DrawString(this.Text, this.Font, brush, x, y);
			g.Clip = clip;
		}
		
		public override void Motion(int x, int y, Gdk.ModifierType modifier)
		{
			this.X = this.plot.GetWorldXCoord(x, false);
			this.Y = this.plot.GetWorldYCoord(y, false);
		}
		
		public override void ButtonPressed(int x, int y, Gdk.ModifierType modifier)
		{
			this.X = this.plot.GetWorldXCoord(x, false);
			this.Y = this.plot.GetWorldYCoord(y, false);
			this.Interactive = false;
		}
	}
	
	public class LineAnnotation : Annotation
	{

		public LineAnnotation(IPlotEngine plot, float line_width, Color color, LineStyle line_style) 
			: base(plot, AnnotationType.Line)
		{
			this.Visible = false;
			this.Interactive = true;
			this.ColorArgb = color.ToArgb();
			this.LineWidth = line_width;
			this.LineStyle = line_style;
		}
		
		public LineAnnotation(IPlotEngine plot, Annotation a) : base(plot, a)
		{
		}

		public override void Draw (Graphics g)
		{
			if (!this.Visible || this.PlotTypeId != this.plot.PlotTypeId)
				return;
			
			float x1 = (float)this.plot.GetPhysicalXCoord(this.X, false);
			float y1 = (float)this.plot.GetPhysicalYCoord(this.Y, false);
			float x2 = (float)this.plot.GetPhysicalXCoord(this.X + this.Width, false);
			float y2 = (float)this.plot.GetPhysicalYCoord(this.Y + this.Height, false);
			
			Pen pen = new Pen(this.Color, this.LineWidth);
			pen.DashStyle = (DashStyle)Enum.Parse(typeof(DashStyle), this.LineStyle.ToString());
			
			Region clip = g.Clip;
			g.Clip = this.ClippingRegion;
			g.DrawLine(pen, x1, y1, x2, y2);
			g.Clip = clip;
		}
		
		public override void ButtonPressed (int x, int y, Gdk.ModifierType modifier)
		{
			this.X = this.plot.GetWorldXCoord(x, false);
			this.Y = this.plot.GetWorldYCoord(y, false);
			this.Width  = 0f;
			this.Height  = 0f;
			this.Visible = true;
		}
		
		public override void Motion (int x, int y, Gdk.ModifierType modifier)
		{
			this.Width  = this.plot.GetWorldXCoord(x, false) - this.X;
			this.Height = this.plot.GetWorldYCoord(y, false) - this.Y;
			
			if ((modifier & Gdk.ModifierType.ShiftMask) != Gdk.ModifierType.None)
			{
				
				int x0 = this.plot.GetPhysicalXCoord(this.X, false);
				int y0 = this.plot.GetPhysicalYCoord(this.Y, false);
				int w = Math.Abs(x - x0);
				int h = Math.Abs(y - y0);
				
				if (w > h && w > 2*h)
					this.Height = 0;
				else if (w > h)
					this.Height = this.Y - this.plot.GetWorldYCoord(y0 + Math.Sign(this.Height) * w, false);
				else if (h > w && h > 2*w)
					this.Width = 0;
				else
					this.Width = this.plot.GetWorldXCoord(x0 + Math.Sign(this.Width) * h, false) - this.X;
			}
		}
		
		public override void ButtonReleased (int x, int y, Gdk.ModifierType modifier)
		{
			this.Motion(x, y, modifier);
			this.Interactive = false;
		}
	}
	
	public class RectangleAnnotation : LineAnnotation
	{
		public RectangleAnnotation(IPlotEngine plot, float width, Color color, LineStyle line_style)
			: base(plot, width, color, line_style)
		{
			this.Type = AnnotationType.Rectangle;
		}
		
		public RectangleAnnotation(IPlotEngine plot, Annotation a) : base(plot, a)
		{
		}

		public override void Draw (Graphics g)
		{
			if (!this.Visible || this.PlotTypeId != this.plot.PlotTypeId)
				return;
			
			float x1 = (float)this.plot.GetPhysicalXCoord(this.X, false);
			float y1 = (float)this.plot.GetPhysicalYCoord(this.Y, false);
			float x2 = (float)this.plot.GetPhysicalXCoord(this.X + this.Width, false);
			float y2 = (float)this.plot.GetPhysicalYCoord(this.Y + this.Height, false);
			
			Pen pen = new Pen(this.Color, this.LineWidth);
			pen.DashStyle = (DashStyle)Enum.Parse(typeof(DashStyle), this.LineStyle.ToString());

			Region clip = g.Clip;
			g.Clip = this.ClippingRegion;
			g.DrawRectangle(pen, Math.Min(x1, x2), Math.Min(y1, y2), Math.Abs(x2 - x1), Math.Abs(y2 - y1));
			g.Clip = clip;
		}
		
		public override void Motion (int x, int y, Gdk.ModifierType modifier)
		{
			this.Width  = this.plot.GetWorldXCoord(x, false) - this.X;
			this.Height = this.plot.GetWorldYCoord(y, false) - this.Y;
			
			if ((modifier & Gdk.ModifierType.ShiftMask) != Gdk.ModifierType.None)
			{
				
				int x0 = this.plot.GetPhysicalXCoord(this.X, false);
				int y0 = this.plot.GetPhysicalYCoord(this.Y, false);
				int w = Math.Abs(x - x0);
				int h = Math.Abs(y - y0);
				
				if (w > h)
					this.Height = this.Y - this.plot.GetWorldYCoord(y0 + Math.Sign(this.Height) * w, false);
				else
					this.Width = this.plot.GetWorldXCoord(x0 + Math.Sign(this.Width) * h, false) - this.X;
			}
		}
	}
	
	public class EllipseAnnotation : RectangleAnnotation
	{
		public EllipseAnnotation(IPlotEngine plot, float width, Color color, LineStyle line_style)
			: base(plot, width, color, line_style)
		{
			this.Type = AnnotationType.Ellipse;
		}
		
		public EllipseAnnotation(IPlotEngine plot, Annotation a) : base(plot, a)
		{
		}

		public override void Draw (Graphics g)
		{
			if (!this.Visible || this.PlotTypeId != this.plot.PlotTypeId)
				return;
			
			float x1 = (float)this.plot.GetPhysicalXCoord(this.X, false);
			float y1 = (float)this.plot.GetPhysicalYCoord(this.Y, false);
			float x2 = (float)this.plot.GetPhysicalXCoord(this.X + this.Width, false);
			float y2 = (float)this.plot.GetPhysicalYCoord(this.Y + this.Height, false);
			
			Pen pen = new Pen(this.Color, this.LineWidth);
			pen.DashStyle = (DashStyle)Enum.Parse(typeof(DashStyle), this.LineStyle.ToString());

			Region clip = g.Clip;
			g.Clip = this.ClippingRegion;
			g.DrawEllipse(pen, Math.Min(x1, x2), Math.Min(y1, y2), Math.Abs(x2 - x1), Math.Abs(y2 - y1));
			g.Clip = clip;
		}
	}

	public class ArrowAnnotation : LineAnnotation
	{
		public ArrowAnnotation(IPlotEngine plot, float width, Color color, LineStyle line_style, CapStyle cap_style) 
			: base(plot, width, color, line_style)
		{
			this.CapStyle = cap_style;
			this.Type = AnnotationType.Arrow;
		}
		
		public ArrowAnnotation(IPlotEngine plot, Annotation a) : base(plot, a)
		{
		}

		public override void Draw(Graphics g)
		{
			if (!this.Visible || this.PlotTypeId != this.plot.PlotTypeId)
				return;
			
			float x1 = (float)this.plot.GetPhysicalXCoord(this.X, false);
			float y1 = (float)this.plot.GetPhysicalYCoord(this.Y, false);
			float x2 = (float)this.plot.GetPhysicalXCoord(this.X + this.Width, false);
			float y2 = (float)this.plot.GetPhysicalYCoord(this.Y + this.Height, false);
			
			Pen pen = new Pen(this.Color, this.LineWidth);
			pen.DashStyle = (DashStyle)Enum.Parse(typeof(DashStyle), this.LineStyle.ToString());

			Region clip = g.Clip;
			g.Clip = this.ClippingRegion;
			g.DrawLine(pen, x1, y1, x2, y2);
			Pen cap_pen = new Pen(this.Color, this.LineWidth);
			cap_pen.CustomStartCap = LineCaps.GetLineCap(this.CapStyle, this.LineWidth);
			float k = 0.5f / Math.Max(Math.Abs(x2-x1), Math.Abs(y2-y1));
			g.DrawLine(cap_pen, x1, y1, (x1 + (x2-x1)*k), (y1 + (y2-y1)*k));
			g.Clip = clip;
		}
	}

	public class DoubleArrowAnnotation : ArrowAnnotation
	{
		public DoubleArrowAnnotation(IPlotEngine plot, float width, Color color, LineStyle line_style, CapStyle cap_style) 
			: base(plot, width, color, line_style, cap_style)
		{
			this.Type = AnnotationType.DoubleArrow;
		}
		
		public DoubleArrowAnnotation(IPlotEngine plot, Annotation a) : base(plot, a)
		{
		}

		public override void Draw(Graphics g)
		{
			if (!this.Visible || this.PlotTypeId != this.plot.PlotTypeId)
				return;
			
			float x1 = (float)this.plot.GetPhysicalXCoord(this.X, false);
			float y1 = (float)this.plot.GetPhysicalYCoord(this.Y, false);
			float x2 = (float)this.plot.GetPhysicalXCoord(this.X + this.Width, false);
			float y2 = (float)this.plot.GetPhysicalYCoord(this.Y + this.Height, false);
			
			Pen pen = new Pen(this.Color, this.LineWidth);
			pen.DashStyle = (DashStyle)Enum.Parse(typeof(DashStyle), this.LineStyle.ToString());

			Region clip = g.Clip;
			g.Clip = this.ClippingRegion;
			g.DrawLine(pen, x1, y1, x2, y2);
			Pen cap_pen = new Pen(this.Color, this.LineWidth);
			cap_pen.CustomStartCap = LineCaps.GetLineCap (this.CapStyle, this.LineWidth);
			float k = 0.5f / Math.Max(Math.Abs(x2-x1), Math.Abs(y2-y1));
			g.DrawLine(cap_pen, x1, y1, (x1 + (x2-x1)*k), (y1 + (y2-y1)*k));
			g.DrawLine(cap_pen, x2, y2, (x2 + (x1-x2)*k), (y2 + (y1-y2)*k));
			g.Clip = clip;
		}
	}

	public class DataSetSelection
	{
		IPlotEngine plot;
		DataSet ds;
		
		public DataSetSelection(IPlotEngine plot, Spectrum spectrum)
		{
			this.plot = plot;
			this.ds = this.plot.DataSets[spectrum.Id];
			this.ds.Color  = spectrum.Color;
			this.ds.Label  = spectrum.Label;
			this.ds.ZOrder = spectrum.ZOrder;
		}
		
		public void Draw(Graphics g)
		{
			if (!ds.IsVisible)
				return;
			
			int x_min = this.plot.PhysicalXMin;
			int x_max = this.plot.PhysicalXMax;
			int y_min = this.plot.PhysicalYMax;
			int y_max = this.plot.PhysicalYMin;
			
			Brush brush = new SolidBrush(this.ds.Color);

			float last_x = float.MinValue, last_y = float.MinValue;
			for (int ix=0; ix < this.ds.Count; ++ix)
			{
				float x = (float)this.plot.GetPhysicalXCoord(this.ds.X[ix]);
				float y = (float)this.plot.GetPhysicalYCoord(this.ds.Y[ix]);
				
				if (Math.Pow(x - last_x, 2f) + Math.Pow(y - last_y, 2f) < 120f)
					continue;
				
				if (x <= x_min || x >= x_max ||
				    y <= y_min || y >= y_max)
					continue;

				g.FillRectangle(brush, x-3f, y-3f, 7f, 7f);
				last_x = x;
				last_y = y;
			}
		}
	}
}