// 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 Cairo;


namespace Hiperscan.GtkExtensions.Compiler
{
    
    public class MonoCairoGraphicCompiler : Hiperscan.GtkExtensions.Compiler.GenericGraphicCompiler
    {
        
        private static Pango.Layout MeasureLayout = Pango.CairoHelper.CreateLayout(new Context(new ImageSurface(Format.ARGB32, 1, 1)));
        public static GraphicsSize MeasureGraphicsText(GraphicsText text)
        {
            MeasureLayout.FontDescription = new Pango.FontDescription();
            MeasureLayout.FontDescription.AbsoluteSize = 1024.0 * text.Font.FontSize;
            MeasureLayout.FontDescription.Family = text.Font.FontName;
            switch(text.Font.FontWeight)
            {
            case GraphicsFontWeights.Normal:
                MeasureLayout.FontDescription.Weight = Pango.Weight.Normal;
                break;
                
            case GraphicsFontWeights.Bold:
                MeasureLayout.FontDescription.Weight = Pango.Weight.Bold;
                break;
            }
            
            MeasureLayout.SetText(text.Text);
            int width, height;
            MeasureLayout.GetSize (out width, out height);
            
            return new GraphicsSize(width / 1024.0, height / 1024.0);
        }
        
        public static readonly double DPI = 72;
        
        public Cairo.Context Context {get;set;}
        private Pango.Layout PangoLayout {get;set;}
        
        public MonoCairoGraphicCompiler (Cairo.Context ctx)
        {
            this.StackAlpha = new Stack<double>();
            this.Context = ctx; 
            this.PangoLayout = Pango.CairoHelper.CreateLayout(ctx);
            this.PangoLayout.FontDescription = new Pango.FontDescription();
            this.ReferenceDpi = MonoCairoGraphicCompiler.DPI;
        }
        
        public override void Dispose()
        {
            ((IDisposable) this.Context.GetTarget()).Dispose();                                      
            ((IDisposable) this.Context).Dispose();
        }
        
        #region implemented abstract members of Hiperscan.GtkExtensions.Compiler.CompileGraphicsTo
        
        
        public override void SetTranslateTransform (GraphicsTranslate translate)
        {
            this.Context.Translate(translate.XOffset, translate.YOffset);
        }
        
        
        public override void SetRotateTransform (GraphicsRotate rotate)
        {
            this.Context.Rotate(rotate.AngleDegree / 180.0 * Math.PI);
        }
        
        
        public override void SetScaleTransform (GraphicsScale scale)
        {
            this.Context.Scale(scale.XScale, scale.YScale);
        }
        
        public override void Clip ()
        {
            this.Context.Clip();
        }
        
        
        public override void SaveContext ()
        {
            this.Context.Save();
        }
        
        
        public override void RestoreContext ()
        {
            this.Context.Restore();
        }
        
        
        public override void SetStroke (GraphicsPen pen)
        {
            this.Context.LineWidth = pen.LineWidth;
            double alpha = this.PeekAlpha() * pen.Color.A;
            Cairo.Color color = new Cairo.Color(pen.Color.R, pen.Color.G, pen.Color.B, alpha);
            this.Context.SetSourceColor(color);
        }
        
        
        public override void SetFill (GraphicsBrush brush)
        {
            if (brush is GraphicsSolidColorBrush) 
            {
                GraphicsSolidColorBrush solid = brush as GraphicsSolidColorBrush;
                double alpha = this.PeekAlpha () * solid.Color.A;
                Cairo.Color color = new Cairo.Color (solid.Color.R, solid.Color.G, solid.Color.B, alpha);
                this.Context.SetSourceColor(color);
            }
            else if (brush is GraphicsLinearGradientenBrush) {
                GraphicsLinearGradientenBrush linear = brush as GraphicsLinearGradientenBrush;

                
                Cairo.LinearGradient lgb = new Cairo.LinearGradient(linear.BoundingBox.X + linear.BoundingBox.Width * linear.Start.X, 
                                                                    linear.BoundingBox.Y + linear.BoundingBox.Height * linear.Start.Y,
                                                                    linear.BoundingBox.X + linear.BoundingBox.Width * linear.End.X,
                                                                    linear.BoundingBox.Y + linear.BoundingBox.Height * linear.End.Y);
                
                
                double alpha1 = this.PeekAlpha () * linear.ColorStop1.Color.A;
                Cairo.Color color1 = new Cairo.Color(linear.ColorStop1.Color.R, linear.ColorStop1.Color.G, linear.ColorStop1.Color.B, alpha1);
                lgb.AddColorStop(linear.ColorStop1.Offset, color1);
                
                double alpha2 = this.PeekAlpha () * linear.ColorStop2.Color.A;
                Cairo.Color color2 = new Cairo.Color(linear.ColorStop2.Color.R, linear.ColorStop2.Color.G, linear.ColorStop2.Color.B, alpha2);
                lgb.AddColorStop(linear.ColorStop2.Offset, color2);
                
                this.Context.SetSource(lgb);
            }
            else if (brush is GraphicsRadialGradientenBrush)
            {
                GraphicsRadialGradientenBrush radial = brush as GraphicsRadialGradientenBrush;
                double center_x = radial.BoundingBox.X + 0.5 * radial.BoundingBox.Width;;
                double center_y = radial.BoundingBox.Y + 0.5 * radial.BoundingBox.Height;
                
                double r1 = 0.5 * radial.Radius1; 
                double r2 = 0.5 * radial.Radius2;
                double scale_x = 1;
                double scale_y = 1;
                bool width_is_longer =  radial.BoundingBox.Width > radial.BoundingBox.Height;
                if (width_is_longer == true)
                {
                    r1 *= radial.BoundingBox.Width;
                    r2 *= radial.BoundingBox.Width;
                    scale_y = radial.BoundingBox.Height / radial.BoundingBox.Width;
                }
                else
                {
                    r1 *= radial.BoundingBox.Height;
                    r2 *= radial.BoundingBox.Height;
                    scale_x = radial.BoundingBox.Width / radial.BoundingBox.Height;
                }
                
                
                Cairo.RadialGradient rgb = new RadialGradient(0, 0, r1, 0, 0, r2);
                
                double alpha1 = this.PeekAlpha () * radial.ColorStop1.Color.A;
                Cairo.Color color1 = new Cairo.Color(radial.ColorStop1.Color.R, radial.ColorStop1.Color.G, radial.ColorStop1.Color.B, alpha1);
                rgb.AddColorStop(radial.ColorStop1.Offset, color1);
                
                double alpha2 = this.PeekAlpha () * radial.ColorStop2.Color.A;
                Cairo.Color color2 = new Cairo.Color(radial.ColorStop2.Color.R, radial.ColorStop2.Color.G, radial.ColorStop2.Color.B, alpha2);
                rgb.AddColorStop(radial.ColorStop2.Offset, color2);
                
                this.Context.Translate(center_x, center_y);
                this.Context.Scale(scale_x, scale_y);
                this.Context.SetSource(rgb);
            }
        }
        
            
        public override void DrawText (GraphicsText text)
        {
            this.PangoLayout.FontDescription.AbsoluteSize = 1024.0 * text.Font.FontSize;
            this.PangoLayout.FontDescription.Family = text.Font.FontName;
            switch(text.Font.FontWeight)
            {
            case GraphicsFontWeights.Normal:
                this.PangoLayout.FontDescription.Weight = Pango.Weight.Normal;
                break;
                
            case GraphicsFontWeights.Bold:
                this.PangoLayout.FontDescription.Weight = Pango.Weight.Bold;
                break;
            }
            this.CutText(text);
            this.PangoLayout.SetText(text.Text);
            this.SetFill(text.Brush);
            this.Context.MoveTo(text.Position.X, text.Position.Y);
            Pango.CairoHelper.ShowLayout (this.Context, this.PangoLayout);
        }
        
        
        public override GraphicsSize MeasureText (GraphicsText text)
        {
            return MeasureGraphicsText(text);
        }
        

        public override void DrawImage (GraphicsImage image)
        {
            this.SaveContext ();
            this.Context.Translate(image.Coordinates.X, image.Coordinates.Y);
            this.Context.Scale(image.Coordinates.Width / image.ImageWidth,
                               image.Coordinates.Height / image.ImageHeight);
            this.Context.SetSource(image.ImageSurface);
            this.Context.Paint ();
            this.RestoreContext ();
        }
        
        
        public override void NewPath ()
        {
            this.Context.NewPath();
        }
        
        
        public override void ClosePath ()
        {
            this.Context.ClosePath();
        }
    
        
        public override void DrawLine (GraphicsShapeLine line)
        {
            this.Context.MoveTo(line.XStart, line.YStart);
            this.Context.LineTo(line.XEnd, line.YEnd);
        }
        
        
        public override void DrawRectangle (GraphicsShapeRectangle rectangle)
        {
            Cairo.Rectangle cairo_rect = new Rectangle(rectangle.Coordinates.X, rectangle.Coordinates.Y,
                                                       rectangle.Coordinates.Width, rectangle.Coordinates.Height);
            this.Context.Rectangle(cairo_rect);
        }
        
        public override void DrawCircle (GraphicsShapeCircle circle)
        {
            this.Context.Arc(circle.X, circle.Y, circle.Radius, 0, 2 * Math.PI);
        }
        
        public override void DrawArc (GraphicsShapeArc arc)
        {
            double arc1 = arc.StartArcInDegree * Math.PI / 180;
            double arc2 = arc.EndArcInDegree * Math.PI / 180;
            this.Context.Arc(arc.Position.X, arc.Position.Y, arc.Radius, arc1, arc2);
        }
        
        public override void DrawPolygone (GraphicsShapePolygone polygon)
        {
            if(polygon.Points.Length > 0)
                this.Context.MoveTo(polygon.Points[0].X, polygon.Points[0].Y);
                    
            for (int i = 1; i < polygon.Points.Length; i++)
                this.Context.LineTo(polygon.Points[i].X, polygon.Points[i].Y);
                    
            this.Context.ClosePath();
        }
        
        public override void Stroke ()
        {
            this.Context.Stroke();
        }
        
        public override void StrokePreverse ()
        {
            this.Context.StrokePreserve();
        }
        
        public override void Fill ()
        {
            this.Context.Fill();
        }
        
        #endregion
    }
}

