// 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
    }
}

