/*
 * Copyright (C) 2013 FooProject
 * * 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 SharpDX;
using D2D = SharpDX.Direct2D1;
using DW = SharpDX.DirectWrite;

namespace FooEditEngine
{
    sealed class CustomTextRenderer : CallbackBase, DW.TextRenderer
    {
        D2D.RenderTarget render;
        ColorBrushCollection brushes;
        StrokeCollection strokes;

        public CustomTextRenderer(D2D.RenderTarget render, ColorBrushCollection brushes,StrokeCollection strokes,Color4 defalut)
        {
            this.render = render;
            this.DefaultFore = defalut;
            this.brushes = brushes;
            this.strokes = strokes;
        }

        public Color4 DefaultFore
        {
            get;
            set;
        }

        #region TextRenderer Members

        public Result DrawGlyphRun(object clientDrawingContext, float baselineOriginX, float baselineOriginY, D2D.MeasuringMode measuringMode, DW.GlyphRun glyphRun, DW.GlyphRunDescription glyphRunDescription, ComObject clientDrawingEffect)
        {
            D2D.SolidColorBrush foreBrush = this.brushes.Get(this.DefaultFore);
            if (clientDrawingEffect != null)
            {
                if (clientDrawingEffect is D2D.SolidColorBrush)
                {
                    D2D.SolidColorBrush effect;
                    effect = clientDrawingEffect as D2D.SolidColorBrush;
                    foreBrush = effect;
                }
                else if (clientDrawingEffect is DrawingEffect)
                {
                    DrawingEffect effect = clientDrawingEffect as DrawingEffect;
                    RectangleF rect;
                    if (effect.Stroke == HilightType.Select)
                    {
                        D2D.SolidColorBrush backBrush = this.brushes.Get(effect.Fore);
                        rect = this.GetGlyphBound(glyphRun, baselineOriginX, baselineOriginY);
                        render.FillRectangle(rect, backBrush);
                    }
                    else if (effect.Stroke == HilightType.Squiggle)
                    {
                        SquilleLineMarker marker = new D2DSquilleLineMarker(this.render, this.brushes.Get(effect.Fore), this.strokes.Get(effect.Stroke), 1);
                        rect = this.GetGlyphBound(glyphRun, baselineOriginX, baselineOriginY,true);
                        marker.Draw(rect.Left, rect.Bottom, rect.Width, rect.Height);
                    }
                    else
                    {
                        LineMarker marker = new LineMarker(this.render, this.brushes.Get(effect.Fore), this.strokes.Get(effect.Stroke), GetThickness(1));
                        rect = this.GetGlyphBound(glyphRun, baselineOriginX, baselineOriginY,true);
                        marker.Draw(rect.Left, rect.Bottom, rect.Width, 0);
                    }

                    if (effect.Stroke == HilightType.Url)
                        foreBrush = this.brushes.Get(effect.Fore);
                }
            }

            render.DrawGlyphRun(new Vector2(baselineOriginX, baselineOriginY),
                glyphRun,
                foreBrush,
                measuringMode);

            return SharpDX.Result.Ok;
        }

        RectangleF GetGlyphBound(DW.GlyphRun myGlyphRun, float baselineOriginX, float baselineOriginY,bool underline = false)
        {
            RectangleF bounds = new RectangleF();

            DW.FontMetrics fontMetrics = myGlyphRun.FontFace.Metrics;

            float ascentPixel = myGlyphRun.FontSize * fontMetrics.Ascent / fontMetrics.DesignUnitsPerEm;
            float dscentPixel = underline ?
                myGlyphRun.FontSize * (fontMetrics.Descent + fontMetrics.LineGap + fontMetrics.UnderlinePosition) / fontMetrics.DesignUnitsPerEm : 
                myGlyphRun.FontSize * (fontMetrics.Descent + fontMetrics.LineGap) / fontMetrics.DesignUnitsPerEm;

            float right = baselineOriginX;

            int glyphCount = myGlyphRun.Advances.Length;

            for (int i = 0; i < glyphCount; i++)
            {
                if (myGlyphRun.BidiLevel % 2 == 1)
                    right -= myGlyphRun.Advances[i];
                else
                    right += myGlyphRun.Advances[i];
            }

            bounds.Left = baselineOriginX;
            if (glyphCount > 0)
                bounds.Right = right;
            else
                bounds.Right = baselineOriginX + myGlyphRun.Advances[0];
            bounds.Top = baselineOriginY - ascentPixel;
            bounds.Bottom = baselineOriginY + dscentPixel;

            return bounds;
        }

        public Result DrawInlineObject(object clientDrawingContext, float originX, float originY, DW.InlineObject inlineObject, bool isSideways, bool isRightToLeft, ComObject clientDrawingEffect)
        {
            inlineObject.Draw(this.render, this, originX, originY, isSideways, isRightToLeft, clientDrawingEffect);
            return Result.Ok;
        }

        public Result DrawStrikethrough(object clientDrawingContext, float baselineOriginX, float baselineOriginY, ref DW.Strikethrough strikethrough, ComObject clientDrawingEffect)
        {
            D2D.SolidColorBrush foreBrush = this.brushes.Get(this.DefaultFore);
            DrawingEffect effect = null;
            if (clientDrawingEffect != null && clientDrawingEffect is DrawingEffect)
            {
                effect = clientDrawingEffect as DrawingEffect;
                foreBrush = this.brushes.Get(effect.Fore);
            }
            if (effect == null)
            {
                render.DrawLine(new Vector2(baselineOriginX, baselineOriginY + strikethrough.Offset),
                    new Vector2(baselineOriginX + strikethrough.Width - 1, baselineOriginY + strikethrough.Offset),
                    foreBrush,
                    GetThickness(strikethrough.Thickness));
            }
            return Result.Ok;
        }

        public Result DrawUnderline(object clientDrawingContext, float baselineOriginX, float baselineOriginY, ref DW.Underline underline, ComObject clientDrawingEffect)
        {
            D2D.SolidColorBrush foreBrush = this.brushes.Get(this.DefaultFore);
            DrawingEffect effect = null;
            if (clientDrawingEffect != null && clientDrawingEffect is DrawingEffect)
            {
                effect = clientDrawingEffect as DrawingEffect;
                foreBrush = this.brushes.Get(effect.Fore);
            }
            if (effect == null)
            {
                render.DrawLine(new Vector2(baselineOriginX, baselineOriginY + underline.Offset),
                    new Vector2(baselineOriginX + underline.Width - 1, baselineOriginY + underline.Offset),
                    foreBrush,
                    GetThickness(underline.Thickness));
            }

            return SharpDX.Result.Ok;
        }

        #endregion

        #region PixelSnapping Members

        public DW.Matrix GetCurrentTransform(object clientDrawingContext)
        {
            Matrix3x2 d2Dmatrix = render.Transform;
            var dwMatrix = new SharpDX.DirectWrite.Matrix()
            {
                M11 = d2Dmatrix.M11,
                M12 = d2Dmatrix.M12,
                M21 = d2Dmatrix.M22,
                M22 = d2Dmatrix.M22,
                Dx = d2Dmatrix.M31,
                Dy = d2Dmatrix.M32
            };
            return dwMatrix;
        }

        public float GetPixelsPerDip(object clientDrawingContext)
        {
            return render.PixelSize.Width / 96f;
        }

        public bool IsPixelSnappingDisabled(object clientDrawingContext)
        {
            return false;
        }

        #endregion

        float GetThickness(float thickness)
        {
            if (this.render.AntialiasMode == D2D.AntialiasMode.Aliased)
                return (int)(thickness + 0.5);
            else
                return thickness;
        }
    }
}
