context: //
  // BasicChart.cs
  //
  // Author:
  //   Lluis Sanchez Gual
  //
  // Copyright (C) 2005 Novell, Inc (http://www.novell.com)
  //
  // Permission is hereby granted, free of charge, to any person obtaining
  // a copy of this software and associated documentation files (the
  // "Software"), to deal in the Software without restriction, including
  // without limitation the rights to use, copy, modify, merge, publish,
  // distribute, sublicense, and/or sell copies of the Software, and to
  // permit persons to whom the Software is furnished to do so, subject to
  // the following conditions:
  // 
  // The above copyright notice and this permission notice shall be
  // included in all copies or substantial portions of the Software.
  // 
  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  //
  
  using System;
  using System.Collections;
  using Gtk;
  using Gdk;
  using MonoDevelop.Ide.Fonts;
  
  namespace MonoDevelop.Components.Chart
  {
  	public class BasicChart: Control
  	{
  		ChartWidget widget;
  
  		public BasicChart ()
  		{
  			widget = new ChartWidget ();
  		}
  
  		protected override object CreateNativeWidget<T> ()
  		{
  			return widget;
  		}
  
  		protected internal virtual void OnSelectionChanged ()
  		{
  			if (SelectionChanged != null)
  				SelectionChanged (this, EventArgs.Empty);
  		}
  
  		public event EventHandler SelectionChanged;
  
  		public bool AllowSelection {
  			get { return widget.AllowSelection; }
  			set { widget.AllowSelection = value; }
  		}
  
  		public ChartCursor SelectionStart {
  			get { return widget.SelectionStart; }
  		}
  
  		public ChartCursor SelectionEnd {
  			get { return widget.SelectionEnd; }
  		}
  
  		public ChartCursor ActiveCursor {
  			get { return widget.ActiveCursor; }
  		}
  		public bool ReverseXAxis {
  			get { return widget.ReverseXAxis; }
  			set { widget.ReverseXAxis = value; }
  		}
  
  		public bool ReverseYAxis {
  			get { return widget.ReverseYAxis; }
  			set { widget.ReverseYAxis = value; }
  		}
  
  		public double OriginX {
  			get { return widget.OriginX; }
  			set { widget.OriginX = value; }
  		}
  
  		public double OriginY {
  			get { return widget.OriginY; }
  			set { widget.OriginY = value; }
  		}
  
  		public double StartX {
  			get { return widget.StartX; }
  			set { widget.StartX = value; }
  		}
  
  		public double EndX {
  			get { return widget.EndX; }
  			set { widget.EndX = value; }
  		}
  
  		public double StartY {
  			get { return widget.StartY; }
  			set { widget.StartY = value; }
  		}
  
  		public double EndY {
  			get { return widget.EndY; }
  			set { widget.EndY = value; }
  		}
  
  		public void Reset ()
  		{
  			widget.Reset ();
  		}
  
  		public void AddAxis (Axis ax, AxisPosition position)
  		{
  			widget.AddAxis (ax, position);
  		}
  
  		public void AddSerie (Serie serie)
  		{
  			widget.AddSerie (serie);
  		}
  
  		public void RemoveSerie (Serie serie)
  		{
  			widget.RemoveSerie (serie);
  		}
  
  		public void AddCursor (ChartCursor cursor, AxisDimension dimension)
  		{
  			widget.AddCursor (cursor, dimension);
  		}
  
  		public void RemoveCursor (ChartCursor cursor)
  		{
  			widget.RemoveCursor (cursor);
  		}
  
  		public void SetAutoScale (AxisDimension ad, bool autoStart, bool autoEnd)
  		{
  			widget.SetAutoScale (ad, autoStart, autoEnd);
  		}
  
  		public void Clear ()
  		{
  			widget.Clear ();
  		}
  	}
  
  	class ChartWidget : DrawingArea
  	{
  		double startX, endX;
  		double startY, endY;
  		int left, top, width, height;
  		int minTickStep = 5;
  		bool xrangeChanged = true;
  		bool yrangeChanged = true;
  		
  		bool autoStartX, autoStartY;
  		bool autoEndX, autoEndY;
  				
  		double originX;
  		double originY;
  		bool reverseXAxis;
  		bool reverseYAxis;
  		
  		int AreaBorderWidth = 1;
  		int AutoScaleMargin = 3;
  		int MinLabelGapX = 3;
  		int MinLabelGapY = 1;
  		BackgroundDisplay backgroundDisplay = BackgroundDisplay.Gradient;
  		Cairo.Color backroundColor = new Cairo.Color (0.9, 0.9, 1);
  		
  		ArrayList series = new ArrayList ();
  		ArrayList axis = new ArrayList ();
  		ArrayList cursors = new ArrayList ();
  		
  		bool enableSelection;
  		bool draggingCursor;
  		ChartCursor activeCursor;
  		
  		ChartCursor selectionStart;
  		ChartCursor selectionEnd;
  		BasicChart chart;
  
  		public ChartWidget ()
  		{
  			this.Events = EventMask.ButtonPressMask | EventMask.ButtonReleaseMask | EventMask.PointerMotionMask; 
  			selectionStart = new ChartCursor ();
  			selectionStart.Visible = false;
  			selectionEnd = new ChartCursor ();
  			selectionEnd.Visible = false;
  			AddCursor (selectionStart, AxisDimension.X);
  			AddCursor (selectionEnd, AxisDimension.X);
  			selectionStart.ValueChanged += new EventHandler (OnSelectionCursorChanged);
  			selectionEnd.ValueChanged += new EventHandler (OnSelectionCursorChanged);
  		}
  
  		internal void Initialize (BasicChart owner)
  		{
  			chart = owner;
  		}
  
  		public bool AllowSelection {
  			get {
  				return enableSelection;
  			}
  			set {
  				enableSelection = value;
  				if (!enableSelection) {
  					selectionStart.Visible = false;
  					selectionEnd.Visible = false;
  				}
  			}
  		}
  		
  		public ChartCursor SelectionStart {
  			get { return selectionStart; }
  		}
  		
  		public ChartCursor SelectionEnd {
  			get { return selectionEnd; }
  		}
  		
  		public ChartCursor ActiveCursor {
  			get { return activeCursor; }
  		}
  		
  		public bool ReverseXAxis {
  			get { return reverseXAxis; }
  			set { reverseXAxis = value; QueueDraw (); }
  		}
  		
  		public bool ReverseYAxis {
  			get { return reverseYAxis; }
  			set { reverseYAxis = value; QueueDraw (); }
  		}
  		
  		public double OriginX {
  			get { return originX; }
  			set {
  				xrangeChanged = true;
  				originX = value;
  				OnSerieChanged ();
  			}
  		}
  		
  		public double OriginY {
  			get { return originY; }
  			set {
  				yrangeChanged = true;
  				originY = value;
  				OnSerieChanged ();
  			}
  		}
  		
  		public double StartX {
  			get { return startX; }
  			set {
  				xrangeChanged = true;
  				startX = value;
  				if (startX > endX)
  					endX = startX;
  				OriginX = value;
  				UpdateCursors ();
  				OnSerieChanged ();
  			}
  		}
  		
  		public double EndX {
  			get { return endX; }
  			set {
  				xrangeChanged = true;
  				endX = value;
  				if (endX < startX)
  					startX = endX;
  				UpdateCursors ();
  				OnSerieChanged ();
  			}
  		}
  		
  		public double StartY {
  			get { return startY; }
  			set {
  				yrangeChanged = true;
  				startY = value;
  				if (startY > endY)
  					endY = startY;
  				OriginY = value;
  				UpdateCursors ();
  				OnSerieChanged ();
  			}
  		}
  		
  		public double EndY {
  			get { return endY; }
  			set {
  				yrangeChanged = true;
  				endY = value;
  				if (endY < startY)
  					startY = endY;
  				UpdateCursors ();
  				OnSerieChanged ();
  			}
  		}
  		
  		void FixOrigins ()
  		{
  			if (originX < startX)
  				originX = startX;
  			else if (originX > endX)
  				originX = endX;
  			if (originY < startY)
  				originY = startY;
  			else if (originY > endY)
  				originY = endY;
  		}
  		
  		public void Reset ()
  		{
  			ArrayList list = (ArrayList) series.Clone ();
  			foreach (Serie s in list)
  				RemoveSerie (s);
  
  			axis.Clear ();
  		}
  		
  		public void AddAxis (Axis ax, AxisPosition position)
  		{
  			ax.Owner = this;
  			ax.Position = position;
  			axis.Add (ax);
  			QueueDraw ();
  		}
  		
  		public void AddSerie (Serie serie)
  		{
  			serie.Owner = this;
  			series.Add (serie);
  			OnSerieChanged ();
  		}
  		
  		public void RemoveSerie (Serie serie)
  		{
  			series.Remove (serie);
  			serie.Owner = null;
  			OnSerieChanged ();
  		}
  		
  		public void AddCursor (ChartCursor cursor, AxisDimension dimension)
  		{
  			cursor.Dimension = dimension;
  			cursor.ValueChanged += new EventHandler (OnCursorChanged);
  			cursor.LayoutChanged += new EventHandler (OnCursorChanged);
  			cursors.Add (cursor);
  			xrangeChanged = yrangeChanged = true;
  			QueueDraw ();
  		}
  		
  		public void RemoveCursor (ChartCursor cursor)
  		{
  			cursor.ValueChanged -= new EventHandler (OnCursorChanged);
  			cursor.LayoutChanged -= new EventHandler (OnCursorChanged);
  			cursors.Remove (cursor);
  			QueueDraw ();
  		}
  		
  		public void SetAutoScale (AxisDimension ad, bool autoStart, bool autoEnd)
  		{
  			if (ad == AxisDimension.X) {
  				autoStartX = autoStart;
  				autoEndX = autoEnd;
  			} else {
  				autoStartY = autoStart;
  				autoEndY = autoEnd;
  			}
  		}
  		
  		void UpdateCursors ()
  		{
  			foreach (ChartCursor c in cursors) {
  				if (c.Value < GetStart (c.Dimension)) c.Value = GetStart (c.Dimension);
  				else if (c.Value > GetEnd (c.Dimension)) c.Value = GetEnd (c.Dimension);
  			}
  		}
  		
  		void OnCursorChanged (object sender, EventArgs args)
  		{
  			ChartCursor c = (ChartCursor) sender;
  			if (c.Value < GetStart (c.Dimension)) c.Value = GetStart (c.Dimension);
  			else if (c.Value > GetEnd (c.Dimension)) c.Value = GetEnd (c.Dimension);
  			QueueDraw ();
  		}
  		
  		internal void OnSerieChanged ()
  		{
  			xrangeChanged = true;
  			yrangeChanged = true;
  			QueueDraw ();
  		}
  		
  		public void Clear ()
  		{
  			foreach (Serie s in series)
  				s.Clear ();
  			OnSerieChanged ();
  		}
  		
  		double GetOrigin (AxisDimension ad)
  		{
  			if (ad == AxisDimension.X)
  				return OriginX;
  			else
  				return OriginY;
  		}
  		
  		double GetStart (AxisDimension ad)
  		{
  			if (ad == AxisDimension.X)
  				return startX;
  			else
  				return startY;
  		}
  		
  		double GetEnd (AxisDimension ad)
  		{
  			if (ad == AxisDimension.X)
  				return endX;
  			else
  				return endY;
  		}
  		
  		int GetAreaSize (AxisDimension ad)
  		{
  			if (ad == AxisDimension.X)
  				return width;
  			else
  				return height;
  		}
  		
  		double GetMinTickStep (AxisDimension ad)
  		{
  			return (((double) minTickStep) * (GetEnd (ad) - GetStart (ad))) / (double) GetAreaSize (ad);
  		}
  		
  		protected override bool OnExposeEvent (Gdk.EventExpose args)
  		{
  			Gdk.Window win = GdkWindow;
  			int rwidth, rheight;
  			
  			Cairo.Context ctx = CairoHelper.Create (win);
  			
  			win.GetSize (out rwidth, out rheight);
  			
  			if (autoStartY || autoEndY) {
  				double nstartY = double.MaxValue;
  				double nendY = double.MinValue;
  				GetValueRange (AxisDimension.Y, out nstartY, out nendY);
  				
  				if (!autoStartY) nstartY = startY;
  				if (!autoEndY) nendY = endY;
  				if (nendY < nstartY) nendY = nstartY;
  				
  				if (nstartY != startY || nendY != endY) {
  					yrangeChanged = true;
  					startY = nstartY;
  					endY = nendY;
  				}
  			}
  			
  			if (autoStartX || autoEndX) {
  				double nstartX = double.MaxValue;
  				double nendX = double.MinValue;
  				GetValueRange (AxisDimension.X, out nstartX, out nendX);
  				
  				if (!autoStartX) nstartX = startX;
  				if (!autoEndX) nendX = endX;
  				if (nendX < nstartX) nendX = nstartX;
  				
  				if (nstartX != startX || nendX != endX) {
  					xrangeChanged = true;
  					startX = nstartX;
  					endX = nendX;
  				}
  			}
  			
  			if (yrangeChanged) {
  				FixOrigins ();
  				int right = rwidth - 2 - AreaBorderWidth;
  				left = AreaBorderWidth;
  				left += MeasureAxisSize (AxisPosition.Left) + 1;
  				right -= MeasureAxisSize (AxisPosition.Right) + 1;
  				yrangeChanged = false;
  				width = right - left + 1;
  				if (width <= 0) width = 1;
  			}
  			
  			if (xrangeChanged) {
  				FixOrigins ();
  				int bottom = rheight - 2 - AreaBorderWidth;
  				top = AreaBorderWidth;
  				bottom -= MeasureAxisSize (AxisPosition.Bottom);
  				top += MeasureAxisSize (AxisPosition.Top);
  				
  				// Make room for cursor handles
  				foreach (ChartCursor cursor in cursors) {
  					if (cursor.Dimension == AxisDimension.X && top - AreaBorderWidth < cursor.HandleSize)
  						top = cursor.HandleSize + AreaBorderWidth;
  				}
  				
  				xrangeChanged = false;
  				height = bottom - top + 1;
  				if (height <= 0) height = 1;
  			}
  			
  			if (AutoScaleMargin != 0 && height > 0) {
  				double margin = (double)AutoScaleMargin * (endY - startY) / (double) height;
  				if (autoStartY) startY -= margin;
  				if (autoEndY) endY += margin;
  			}
  
  //			Console.WriteLine ("L:" + left + " T:" + top + " W:" + width + " H:" + height);
  			
  			// Draw the background
  
  			if (backgroundDisplay == BackgroundDisplay.Gradient) {
  				ctx.Rectangle (left - 1, top - 1, width + 2, height + 2);
  
  				// FIXME: VV: Remove gradient features
  				using (var pat = new Cairo.LinearGradient (left - 1, top - 1, left - 1, height + 2)) {
  					pat.AddColorStop (0, backroundColor);
  					Cairo.Color endc = new Cairo.Color (1,1,1);
  					pat.AddColorStop (1, endc);
  					ctx.SetSource (pat);
  					ctx.Fill ();
  				}
  			} else {
  				ctx.Rectangle (left - 1, top - 1, width + 2, height + 2);
  				ctx.SetSourceColor (backroundColor);
  				ctx.Fill ();
  			}
  //			win.DrawRectangle (Style.WhiteGC, true, left - 1, top - 1, width + 2, height + 2);
  			win.DrawRectangle (Style.BlackGC, false, left - AreaBorderWidth, top - AreaBorderWidth, width + AreaBorderWidth*2, height + AreaBorderWidth*2);
  			
  			// Draw selected area
  			
  			if (enableSelection) {
  				int sx, sy, ex, ey;
  				GetPoint (selectionStart.Value, selectionStart.Value, out sx, out sy);
  				GetPoint (selectionEnd.Value, selectionEnd.Value, out ex, out ey);
  				if (sx > ex) {
  					int tmp = sx; sx = ex; ex = tmp;
  				}
  				using (Gdk.GC sgc = new Gdk.GC (GdkWindow)) {
  					sgc.RgbFgColor = new Color (225, 225, 225);
  					win.DrawRectangle (sgc, true, sx, top, ex - sx, height + 1);
  				}
  			}
  			
  			// Draw axes
  			
  			Gdk.GC gc = Style.BlackGC;
  			
  			foreach (Axis ax in axis)
  				DrawAxis (win, gc, ax);
  			
  			// Draw values
  			foreach (Serie serie in series)
  				if (serie.Visible)
  					DrawSerie (ctx, serie);
  
  			// Draw cursors
  			foreach (ChartCursor cursor in cursors)
  				DrawCursor (cursor);
  			
  			// Draw cursor labels
  			foreach (ChartCursor cursor in cursors)
  				if (cursor.ShowValueLabel)
  					DrawCursorLabel (cursor);
  			
  			((IDisposable)ctx).Dispose ();
  			return true;
  		}
  		
  		void GetValueRange (AxisDimension ad, out double min, out double max)
  		{
  			min = double.MaxValue;
  			max = double.MinValue;
  			
  			foreach (Serie serie in series) {
  				if (!serie.HasData || !serie.Visible)
  					continue;
  				
  				double lmin, lmax;
  				serie.GetRange (ad, out lmin, out lmax);
  				if (lmin < min) min = lmin;
  				if (lmax > max) max = lmax;
  			}
  		}
  		
  		void DrawAxis (Gdk.Window win, Gdk.GC gc, Axis ax)
  		{
  			double minStep = GetMinTickStep (ax.Dimension);
  			
  			TickEnumerator enumSmall = ax.GetTickEnumerator (minStep);
  			if (enumSmall == null)
  				return;
  				
  			TickEnumerator enumBig = ax.GetTickEnumerator (minStep * 2);
  			
  			if (enumBig == null) {
  				DrawTicks (win, gc, enumSmall, ax.Position, ax.Dimension, ax.TickSize, ax.ShowLabels);
  			} else {
  				DrawTicks (win, gc, enumSmall, ax.Position, ax.Dimension, ax.TickSize / 2, false);
  				DrawTicks (win, gc, enumBig, ax.Position, ax.Dimension, ax.TickSize, ax.ShowLabels);
  			}
  		}
  		
  		void DrawTicks (Gdk.Window win, Gdk.GC gc, TickEnumerator e, AxisPosition pos, AxisDimension ad, int tickSize, bool showLabels)
  		{
  			int rwidth, rheight;
  			win.GetSize (out rwidth, out rheight);
  			
  			Pango.Layout layout = null;
  			
  			if (showLabels) {
  				layout = new Pango.Layout (this.PangoContext);
  				layout.FontDescription = FontService.SansFont.CopyModified (Ide.Gui.Styles.FontScale11);
  			}
  			
  			bool isX = pos == AxisPosition.Top || pos == AxisPosition.Bottom;
  			bool isTop = pos == AxisPosition.Top || pos == AxisPosition.Right;
  			
  			double start = GetStart (ad);
  			double end = GetEnd (ad);
  			
  			e.Init (GetOrigin (ad));
  			
  			while (e.CurrentValue > start)
  				e.MovePrevious ();
  			
  			int lastPosLabel;
  			int lastPos;
  			int lastTw = 0;
  			
  			if (isX) {
  				lastPosLabel = reverseXAxis ? left + width + MinLabelGapX : left - MinLabelGapX;
  				lastPos = left - minTickStep*2;
  			}
  			else {
  				lastPosLabel = reverseYAxis ? top - MinLabelGapY : rheight + MinLabelGapY;
  				lastPos = top + height + minTickStep*2;
  			}
  			
  			for ( ; e.CurrentValue <= end; e.MoveNext ())
  			{
  				int px, py;
  
gt: 				int tw = 0, th = 0;