// Created with MonoDevelop
//
//    QuickStep: Acquire, view and identify spectra 
//    Copyright (C) 2012 Thomas Klose & Torsten Schache, 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;


namespace Hiperscan.GtkExtensions.Compiler
{
	
	public class SystemDrawingGraphicCompiler : Hiperscan.GtkExtensions.Compiler.GenericGraphicCompiler
	{
		
		public static readonly double DPI = 100;
		private static System.Drawing.Graphics MeasureContext = Graphics.FromImage(System.Drawing.Image.FromHbitmap(new Bitmap(1,1).GetHbitmap()));
		public static GraphicsSize MeasureGraphicsText(GraphicsText text)
		{			
			System.Drawing.FontStyle style;
			switch(text.Font.FontWeight)
			{
			case GraphicsFontWeights.Bold:
				style = System.Drawing.FontStyle.Bold;
				break;
				
			default:
				style = System.Drawing.FontStyle.Regular;
				break;
			}
			System.Drawing.Font font = new System.Drawing.Font(text.Font.FontName, (float) text.Font.FontSize, style, System.Drawing.GraphicsUnit.Pixel);
			
			System.Drawing.SizeF sizef = MeasureContext.MeasureString(text.Text, font);
			return new GraphicsSize((float) sizef.Width, (double)sizef.Height);
		}
		
		public System.Drawing.Graphics Context {get;set;}
		private Stack<System.Drawing.Drawing2D.GraphicsState> GraphicsStates {get;set;}
		private System.Drawing.Pen Pen{get;set;}
		private System.Drawing.Brush Brush{get;set;}
		private System.Drawing.Drawing2D.GraphicsPath Path{get;set;}
		
		public SystemDrawingGraphicCompiler (System.Drawing.Graphics ctx)
		{
			this.StackAlpha = new Stack<double>();
			this.Context = ctx;
			this.GraphicsStates = new Stack<System.Drawing.Drawing2D.GraphicsState>();
			this.ReferenceDpi =SystemDrawingGraphicCompiler.DPI;
		}
		
		
		#region implemented abstract members of Hiperscan.GtkExtensions.Compiler.CompileGraphicsTo
		public override void SetTranslateTransform(GraphicsTranslate translate)
		{
			this.Context.TranslateTransform((float) translate.XOffset, (float) translate.YOffset);
		}
		
		
		public override void SetRotateTransform(GraphicsRotate rotate)
		{
			this.Context.RotateTransform((float) rotate.AngleDegree);
		}
		
		
		public override void SetScaleTransform(GraphicsScale scale)
		{
			this.Context.ScaleTransform((float) scale.XScale, (float) scale.YScale);
		}
		
		public override void Clip()
		{
			this.Context.Clip = new Region(this.Path);
		}
		
		public override void SaveContext()
		{
			 this.GraphicsStates.Push(this.Context.Save());
		}
		
		
		public override void RestoreContext()
		{
			this.Context.Restore(this.GraphicsStates.Pop());
		}
		
		
		public override void SetStroke(GraphicsPen pen)
		{
			byte r = (byte) (pen.Color.R * 255);
			byte g = (byte) (pen.Color.G * 255);
			byte b = (byte) (pen.Color.B * 255);
			byte a = (byte) (pen.Color.A * 255 * this.PeekAlpha());
			System.Drawing.Color color = System.Drawing.Color.FromArgb(a, r, g, b);
			this.Pen = new System.Drawing.Pen(color);
			this.Pen.Width = (float) pen.LineWidth;

		}
		
		public override void SetFill(GraphicsBrush brush)
		{
			if (brush is GraphicsSolidColorBrush)
			{
				GraphicsSolidColorBrush solid = brush as GraphicsSolidColorBrush;
				byte r = (byte) (solid.Color.R * 255);
				byte g = (byte) (solid.Color.G * 255);
				byte b = (byte) (solid.Color.B * 255);
				byte a = (byte) (solid.Color.A * 255 * this.PeekAlpha());
				System.Drawing.Color color = System.Drawing.Color.FromArgb(a, r, g, b);
				this.Brush = new System.Drawing.SolidBrush(color);
			}
			else if (brush is GraphicsLinearGradientenBrush) {
				GraphicsLinearGradientenBrush linear = brush as GraphicsLinearGradientenBrush;

				Point p1 = new Point();
				p1.X = (int) linear.BoundingBox.X;
				p1.Y = (int) linear.BoundingBox.Y;
				
				Point p2 = new Point();
				p2.X = (int) (linear.BoundingBox.X + linear.BoundingBox.Width);
				p2.Y = (int) (linear.BoundingBox.Y + linear.BoundingBox.Height);
					
				byte r1 = (byte) (linear.ColorStop1.Color.R * 255);
				byte g1 = (byte) (linear.ColorStop1.Color.G * 255);
				byte b1 = (byte) (linear.ColorStop1.Color.B * 255);
				byte a1 = (byte) (linear.ColorStop1.Color.A * 255 * this.PeekAlpha());
				System.Drawing.Color color1 = System.Drawing.Color.FromArgb(a1, r1, g1, b1);

				byte r2 = (byte) (linear.ColorStop2.Color.R * 255);
				byte g2 = (byte) (linear.ColorStop2.Color.G * 255);
				byte b2 = (byte) (linear.ColorStop2.Color.B * 255);
				byte a2 = (byte) (linear.ColorStop2.Color.A * 255 * this.PeekAlpha());
				System.Drawing.Color color2 = System.Drawing.Color.FromArgb(a2, r2, g2, b2);
				
				LinearGradientBrush lgb = new LinearGradientBrush(p1, p2, color1, color2);
				this.Brush = lgb;
			}
			else if (brush is GraphicsRadialGradientenBrush)
			{
				GraphicsRadialGradientenBrush radial = brush as GraphicsRadialGradientenBrush;
				
				double circle_radius = radial.BoundingBox.Width > radial.BoundingBox.Height ? 
					radial.BoundingBox.Width / 2 : radial.BoundingBox.Height / 2;
								
				double max_radius = Math.Pow((2 * circle_radius * circle_radius), 0.5);
				double reverse_scale = circle_radius / max_radius;
				
				double center_x = radial.BoundingBox.X + radial.BoundingBox.Width / 2.065;
				double center_y = radial.BoundingBox.Y + radial.BoundingBox.Height / 2;
				
				double scale_x = radial.BoundingBox.Width / 2 / circle_radius;
				double scale_y = radial.BoundingBox.Height / 2 / circle_radius;

				byte r1 = (byte) (radial.ColorStop1.Color.R * 255);
				byte g1 = (byte) (radial.ColorStop1.Color.G * 255);
				byte b1 = (byte) (radial.ColorStop1.Color.B * 255);
				byte a1 = (byte) (radial.ColorStop1.Color.A * 255 * this.PeekAlpha());
				System.Drawing.Color color1 = System.Drawing.Color.FromArgb(a1, r1, g1, b1);
				
				byte r2 = (byte) (radial.ColorStop2.Color.R * 255);
				byte g2 = (byte) (radial.ColorStop2.Color.G * 255);
				byte b2 = (byte) (radial.ColorStop2.Color.B * 255);
				byte a2 = (byte) (radial.ColorStop2.Color.A * 255 * this.PeekAlpha());
				System.Drawing.Color color2 = System.Drawing.Color.FromArgb(a2, r2, g2, b2);

				GraphicsPath path = new GraphicsPath();
        		path.AddEllipse((float)-max_radius, (int)-max_radius, (int)(2 * max_radius), (int)(2 * max_radius));
				PathGradientBrush pthGrBrush = new PathGradientBrush(path);
				pthGrBrush.CenterColor = color1;
				pthGrBrush.SurroundColors = new Color[]{color2};
				
				double offset1 = 1 - radial.ColorStop2.Offset * reverse_scale;
				double offset2 = 1 - radial.ColorStop1.Offset * reverse_scale;
				
				Blend blend = new Blend();
				blend.Factors = new float[]	{0f, 0f, 1f ,1f};
				blend.Positions = new float[]	{0f, (float)offset1 , (float)offset2, 1f};
				pthGrBrush.Blend = blend;
						
				pthGrBrush.TranslateTransform((float)center_x, (float)center_y);
				pthGrBrush.ScaleTransform((float)scale_x, (float)scale_y);

				this.Brush = pthGrBrush;
			}
		}
		
		public override void DrawText(GraphicsText text)
		{
			System.Drawing.PointF point = new System.Drawing.PointF();
			point.X = (float) text.Position.X;
			point.Y = (float) text.Position.Y;
			
			System.Drawing.FontStyle style;
			switch(text.Font.FontWeight)
			{
			case GraphicsFontWeights.Bold:
				style = System.Drawing.FontStyle.Bold;
				break;
				
			default:
				style = System.Drawing.FontStyle.Regular;
				break;
			}
			System.Drawing.Font font = new System.Drawing.Font(text.Font.FontName, (float) text.Font.FontSize, style, System.Drawing.GraphicsUnit.Pixel);
			
			this.SetFill(text.Brush);
			
			this.CutText(text);
			this.Context.DrawString(text.Text, font, this.Brush, point);
		}
		
		public override GraphicsSize MeasureText(GraphicsText text)
		{
			return MeasureGraphicsText(text);
		}
		
		public override void DrawImage(GraphicsImage image)
		{
			this.Context.DrawImage(image.Bitmap, 
			                       (float)image.Coordinates.X, 
			                       (float)image.Coordinates.Y, 
			                       (float)image.Coordinates.Width, 
			                       (float)image.Coordinates.Height);
		}
		
		
		public override void NewPath()
		{
			this.Path = new System.Drawing.Drawing2D.GraphicsPath();
		}
		
		
		public override void ClosePath()
		{
			this.Path.CloseFigure();
		}
		
		
		public override void DrawLine(GraphicsShapeLine line)
		{
			Path.AddLine((float) line.XStart, (float) line.YStart, (float) line.XEnd, (float) line.YEnd);
		}
		
		
		public override void DrawRectangle(GraphicsShapeRectangle rectangle)
		{
			System.Drawing.RectangleF rectf = new System.Drawing.RectangleF((float) rectangle.Coordinates.X,
			                                  (float) rectangle.Coordinates.Y, (float) rectangle.Coordinates.Width,
			                                  (float) rectangle.Coordinates.Height);                                                                
			this.Path.AddRectangle(rectf);
		}
		
		
		public override void DrawCircle(GraphicsShapeCircle circle)
		{
			System.Drawing.RectangleF rectf = new System.Drawing.RectangleF(
			                                  (float) (circle.X - circle.Radius),
			                                  (float) (circle.Y - circle.Radius),
			                                  (float) (2 * circle.Radius),
			                                  (float) (2 * circle.Radius));
			this.Path.AddArc(rectf, 0f, 360f);
		}
		
		public override void DrawArc(GraphicsShapeArc arc)
		{
			System.Drawing.RectangleF rectf = new System.Drawing.RectangleF(
			                                  (float) (arc.Position.X - arc.Radius),
			                                  (float) (arc.Position.Y - arc.Radius),
			                                  (float) (2 * arc.Radius),
			                                  (float) (2 * arc.Radius));
			this.Path.AddArc(rectf, (float) arc.StartArcInDegree, (float) arc.EndArcInDegree);
		}
		
		public override void DrawPolygone(GraphicsShapePolygone polygon)
		{
			System.Drawing.PointF[] points = new System.Drawing.PointF[polygon.Points.Length];
			
			for(int i = 0; i < points.Length; i++)
				points[i] = new System.Drawing.PointF((float) polygon.Points[i].X, (float) polygon.Points[i].Y);
				
			this.Path.AddPolygon(points);
		}
		
		public override void Stroke()
		{
			this.Context.DrawPath(this.Pen, this.Path);
		}
		
		public override void StrokePreverse()
		{
			this.Context.DrawPath(this.Pen, this.Path);
		}
		
		
		public override void Fill()
		{
			this.Context.FillPath(this.Brush, this.Path);
		}
		
		#endregion
	}
}

