/*
 * 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 System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Interop;
using FooEditEngine;
using DotNetTextStore;
using DotNetTextStore.UnmanagedAPI.TSF;
using DotNetTextStore.UnmanagedAPI.WinDef;
using SharpDX;
using D2D = SharpDX.Direct2D1;
using DW = SharpDX.DirectWrite;
using D3D10 = SharpDX.Direct3D10;
using D3D9 = SharpDX.Direct3D9;
using DXGI = SharpDX.DXGI;

namespace FooEditEngine.WPF
{
    class D2DRender : ITextRender, IDisposable
    {
        D2DRenderCommon common;
        System.Windows.Media.Color ForegroundColor, BackgroundColor, HilightColor, Keyword1Color, Keyword2Color, LiteralColor, UrlColor, ControlCharColor, CommentColor, InsertCaretColor,OverwriteCaretColor,LineMarkerColor;
        DotNetTextStore.TextStore store;
        D3D10.Texture2D texture;
        DXGI.Surface surface;
        D3D10.Device device;
        D3D9.Device device9;
        D3D9.Surface surface9;
        D2D.RenderTarget render;
        double fontSize;
        FontFamily fontFamily;
        FontWeight fontWeigth;
        FontStyle fontStyle;
        D3DImage imageSource;

        public D2DRender(FooTextBox textbox, double width, double height,Image image)
        {
            this.fontFamily = textbox.FontFamily;
            this.fontSize = textbox.FontSize;
            this.fontWeigth = textbox.FontWeight;
            this.fontStyle = textbox.FontStyle;
            this.ForegroundColor = textbox.Foreground;
            this.BackgroundColor = textbox.Background;
            this.ControlCharColor = textbox.ControlChar;
            this.HilightColor = textbox.Hilight;
            this.CommentColor = textbox.Comment;
            this.UrlColor = textbox.URL;
            this.Keyword1Color = textbox.Keyword1;
            this.Keyword2Color = textbox.Keyword2;
            this.LiteralColor = textbox.Literal;
            this.InsertCaretColor = textbox.InsertCaret;
            this.OverwriteCaretColor = textbox.OverwriteCaret;
            this.LineMarkerColor = textbox.LineMarker;
            this.store = textbox.TextStore;

            this.CreateDevice();

            this.common = new D2DRenderCommon();
            this.common.ConstructRender = ConstructRenderHandler;
            this.common.ConstrctedResource = ConstructedResourceHandler;
            this.common.DestructRender = this.DestructRenderHandler;
            this.common.ReCreateTarget = this.ReCreateTarget;
            this.common.GetDpi = this.GetDpi;
            this.common.ConstructDeviceResource(width, height);
            this.common.InitTextFormat(this.fontFamily.Source, (float)this.fontSize, this.GetDWFontWeigth(this.fontWeigth), this.GetDWFontStyle(this.fontStyle));

            this.imageSource = new D3DImage();
            this.imageSource.Lock();
            this.imageSource.SetBackBuffer(D3DResourceType.IDirect3DSurface9, this.surface9.NativePointer);  //設定しないとロード時に例外が発生する
            this.imageSource.Unlock();

            image.Source = this.imageSource;
        }

        private void GetDpi(out int dpix, out int dpiy)
        {
            IntPtr hDc = NativeMethods.GetDC(IntPtr.Zero);
            dpix = NativeMethods.GetDeviceCaps(hDc, NativeMethods.LOGPIXELSX);
            dpiy = NativeMethods.GetDeviceCaps(hDc, NativeMethods.LOGPIXELSY);
            NativeMethods.ReleaseDC(IntPtr.Zero, hDc);
        }

        public TextAntialiasMode TextAntialiasMode
        {
            get
            {
                return this.common.TextAntialiasMode;
            }
            set
            {
                this.common.TextAntialiasMode = value;
            }
        }

        public bool ShowFullSpace
        {
            get
            {
                return this.common.ShowFullSpace;
            }
            set
            {
                this.common.ShowFullSpace = value;
            }
        }

        public bool ShowHalfSpace
        {
            get
            {
                return this.common.ShowHalfSpace;
            }
            set
            {
                this.common.ShowHalfSpace = value;
            }
        }

        public bool ShowTab
        {
            get
            {
                return this.common.ShowTab;
            }
            set
            {
                this.common.ShowTab = value;
            }
        }

        public FontFamily FontFamily
        {
            get { return this.fontFamily; }
            set
            {
                this.fontFamily = value;
                this.common.InitTextFormat(this.fontFamily.Source, (float)this.fontSize, this.GetDWFontWeigth(this.fontWeigth), this.GetDWFontStyle(this.fontStyle));
                this.TabWidthChar = this.TabWidthChar;
            }
        }

        public double FontSize
        {
            get { return this.fontSize; }
            set
            {
                this.fontSize = value;
                this.common.InitTextFormat(this.fontFamily.Source, (float)value, this.GetDWFontWeigth(this.fontWeigth), this.GetDWFontStyle(this.fontStyle));
                this.TabWidthChar = this.TabWidthChar;
            }
        }

        public FontWeight FontWeigth
        {
            get
            {
                return this.fontWeigth;
            }
            set
            {
                this.fontWeigth = value;
                this.common.InitTextFormat(this.fontFamily.Source, (float)this.fontSize, this.GetDWFontWeigth(value),this.GetDWFontStyle(this.fontStyle));
            }
        }

        public FontStyle FontStyle
        {
            get
            {
                return this.fontStyle;
            }
            set
            {
                this.fontStyle = value;
                this.common.InitTextFormat(this.fontFamily.Source, (float)this.fontSize, this.GetDWFontWeigth(this.fontWeigth), this.GetDWFontStyle(this.fontStyle));
            }
        }

        public bool RightToLeft
        {
            get
            {
                return this.common.RightToLeft;
            }
            set
            {
                this.common.RightToLeft = value;
            }
        }

        Color4 ToColor4(System.Windows.Media.Color color)
        {
            return new Color4(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, color.A / 255.0f);
        }

        public System.Windows.Media.Color Foreground
        {
            get
            {
                return this.ForegroundColor;
            }
            set
            {
                this.ForegroundColor = value;
                this.common.Foreground = this.ToColor4(value);
            }
        }

        public System.Windows.Media.Color Background
        {
            get
            {
                return this.BackgroundColor;
            }
            set
            {
                this.BackgroundColor = value;
                this.common.Background = this.ToColor4(value);
            }
        }

        public System.Windows.Media.Color InsertCaret
        {
            get
            {
                return this.InsertCaretColor;
            }
            set
            {
                this.InsertCaretColor = value;
                this.common.InsertCaret = this.ToColor4(value);
            }
        }

        public System.Windows.Media.Color OverwriteCaret
        {
            get
            {
                return this.OverwriteCaretColor;
            }
            set
            {
                this.OverwriteCaretColor = value;
                this.common.OverwriteCaret = this.ToColor4(value);
            }
        }

        public System.Windows.Media.Color LineMarker
        {
            get
            {
                return this.LineMarkerColor;
            }
            set
            {
                this.LineMarkerColor = value;
                this.common.LineMarker = this.ToColor4(value);
            }
        }

        public System.Windows.Media.Color ControlChar
        {
            get
            {
                return this.ControlCharColor;
            }
            set
            {
                this.ControlCharColor = value;
                this.common.ControlChar = this.ToColor4(value);
            }
        }

        public System.Windows.Media.Color Url
        {
            get
            {
                return this.UrlColor;
            }
            set
            {
                this.UrlColor = value;
                this.common.Url = this.ToColor4(value);
            }
        }

        public System.Windows.Media.Color Hilight
        {
            get
            {
                return this.HilightColor;
            }
            set
            {
                this.HilightColor = value;
                this.common.Hilight = this.ToColor4(this.HilightColor);
            }
        }

        public System.Windows.Media.Color Comment
        {
            get
            {
                return this.CommentColor;
            }
            set
            {
                this.CommentColor = value;
                this.common.Comment = this.ToColor4(value);
            }
        }

        public System.Windows.Media.Color Literal
        {
            get
            {
                return this.LiteralColor;
            }
            set
            {
                this.LiteralColor = value;
                this.common.Literal = this.ToColor4(value);
            }
        }

        public System.Windows.Media.Color Keyword1
        {
            get
            {
                return this.Keyword1Color;
            }
            set
            {
                this.Keyword1Color = value;
                this.common.Keyword1 = this.ToColor4(value);
            }
        }

        public System.Windows.Media.Color Keyword2
        {
            get
            {
                return this.Keyword2Color;
            }
            set
            {
                this.Keyword2Color = value;
                this.common.Keyword2 = this.ToColor4(value);
            }
        }

        public Rectangle TextArea
        {
            get { return this.common.ClipRect; }
            set { this.common.ClipRect = value; }
        }

        public double LineNemberWidth
        {
            get
            {
                return this.common.LineNemberWidth;
            }
        }

        public double FoldingWidth
        {
            get
            {
                return this.common.FoldingWidth;
            }
        }

        public int TabWidthChar
        {
            get { return this.common.TabWidthChar; }
            set
            {
                if (value == 0)
                    return;
                this.common.TabWidthChar = value;
            }
        }

        public Size emSize
        {
            get
            {
                return this.common.emSize;
            }
        }

        public event ChangedRenderResourceEventHandler ChangedRenderResource
        {
            add
            {
                this.common.ChangedRenderResource += value;
            }
            remove
            {
                this.common.ChangedRenderResource -= value;
            }
        }

        public event EventHandler ChangedRightToLeft
        {
            add
            {
                this.common.ChangedRightToLeft += value;
            }
            remove
            {
                this.common.ChangedRightToLeft -= value;
            }
        }

        public bool Resize(double width, double height)
        {
            if (width != this.imageSource.Width || height != this.imageSource.Height)
            {
                this.common.ReConstructDeviceResource(width, height);
                this.imageSource.Lock();
                this.imageSource.SetBackBuffer(D3DResourceType.IDirect3DSurface9, this.surface9.NativePointer);
                this.imageSource.Unlock();
                return true;
            }
            return false;
        }

        public void DrawCachedBitmap(Rectangle rect)
        {
            this.common.DrawCachedBitmap(rect);
        }

        public void CacheContent()
        {
            this.common.CacheContent();
        }

        public bool IsVaildCache()
        {
            return this.common.IsVaildCache();
        }

        public void BegineDraw()
        {
            this.common.BegineDraw();
        }

        public void EndDraw()
        {
            this.common.EndDraw();
            this.device.Flush();

            if (this.imageSource.IsFrontBufferAvailable)
            {
                this.imageSource.Lock();
                this.imageSource.SetBackBuffer(D3DResourceType.IDirect3DSurface9, this.surface9.NativePointer);
                this.imageSource.AddDirtyRect(new Int32Rect(0, 0, (int)this.imageSource.Width, (int)this.imageSource.Height));
                this.imageSource.Unlock();
            }
        }

        public void DrawString(string str, double x, double y, StringAlignment align, Size layoutRect)
        {
            this.common.DrawString(str, x, y,align,layoutRect);
        }

        public void FillRectangle(Rectangle rect,FillRectType type)
        {
            this.common.FillRectangle(rect, type);
        }

        public void DrawFoldingMark(bool expand, double x, double y)
        {
            this.common.DrawFoldingMark(expand, x, y);
        }

        public void FillBackground(Rectangle rect)
        {
            this.common.FillBackground(rect);
        }

        public void DrawLine(Point from, Point to)
        {
            this.common.DrawLine(from, to);
        }

        public void DrawOneLine(LineToIndexTable lti, int row, double x, double y, IEnumerable<Selection> SelectRanges)
        {
            PreDrawOneLineHandler PreDrawOneLine = (layout) => {
                using (Unlocker locker = this.store.LockDocument(false))
                {
                    int lineIndex = lti.GetIndexFromLineNumber(row);
                    int lineLength = lti.GetLengthFromLineNumber(row);
                    foreach (TextDisplayAttribute attr in this.store.EnumAttributes(lineIndex,lineIndex + lineLength))
                    {
                        if (attr.startIndex == attr.endIndex)
                            continue;
                        int length = attr.endIndex - attr.startIndex;
                        int start = attr.startIndex - lineIndex;

                        HilightType type = HilightType.None;
                        Color4? color = null;
                        switch (attr.attribute.lsStyle)
                        {
                            case TF_DA_LINESTYLE.TF_LS_DOT:
                                type = HilightType.Dot;
                                color = this.GetColor4(attr.attribute.crLine);
                                break;
                            case TF_DA_LINESTYLE.TF_LS_SOLID:
                                type = HilightType.Sold;
                                color = this.GetColor4(attr.attribute.crLine);
                                break;
                            case TF_DA_LINESTYLE.TF_LS_DASH:
                                type = HilightType.Dash;
                                color = this.GetColor4(attr.attribute.crLine);
                                break;
                            case TF_DA_LINESTYLE.TF_LS_SQUIGGLE:
                                type = HilightType.Squiggle;
                                color = this.GetColor4(attr.attribute.crLine);
                                break;
                        }

                        if (attr.attribute.crBk.type != TF_DA_COLORTYPE.TF_CT_NONE)
                        {
                            type = HilightType.Select;
                            color = this.GetColor4(attr.attribute.crBk);
                        }

                        this.common.DrawMarkerEffect(layout, type, start, length,x,y,attr.attribute.fBoldLine,color);

                        color = this.GetColor4(attr.attribute.crText);
                        if (color != null)
                        {
                            this.common.SetTextColor(layout, start, length, color);
                            layout.Invaild = true;
                        }
                    }
                }
            };
            this.common.DrawOneLine(lti,
                row,
                x,
                y,
                SelectRanges,
                PreDrawOneLine);
        }

        private Color4? GetColor4(TF_DA_COLOR cr)
        {
            COLORREF colorref;
            switch(cr.type)
            {
                case TF_DA_COLORTYPE.TF_CT_SYSCOLOR:
                    colorref = new COLORREF(NativeMethods.GetSysColor((int)cr.indexOrColorRef));
                    break;
                case TF_DA_COLORTYPE.TF_CT_COLORREF:
                    colorref = new COLORREF(cr.indexOrColorRef);
                    break;
                default:
                    return null;
            }
            return new Color4(colorref.R / 255.0f, colorref.G / 255.0f, colorref.B / 255.0f, 1);
        }

        public ITextLayout CreateLaytout(string str, SyntaxInfo[] syntaxCollection, IEnumerable<Marker> MarkerRanges)
        {
            return this.common.CreateLaytout(str,syntaxCollection,MarkerRanges);
        }

        public List<LineToIndexTableData> BreakLine(Document doc, int startIndex, int endIndex, double wrapwidth)
        {
            return this.common.BreakLine(doc, startIndex, endIndex, wrapwidth);
        }

        public void Dispose()
        {
            //CA2213が表示されるが、DestructRenderHandlerで破棄しているので問題はない
            this.common.Dispose();
            this.DestructDevice();
        }

        void ReCreateTarget()
        {
            System.Diagnostics.Debug.WriteLine("ReCreatedDevice");
            this.DestructDevice();
            this.CreateDevice();
            this.common.ReConstructDeviceResource();
        }

        DW.FontStyle GetDWFontStyle(FontStyle style)
        {
            return (DW.FontStyle)Enum.Parse(typeof(DW.FontStyle), style.ToString());
        }

        DW.FontWeight GetDWFontWeigth(FontWeight weigth)
        {
            return (DW.FontWeight)Enum.Parse(typeof(DW.FontWeight), weigth.ToString());
        }

        void CreateDevice()
        {
            D3D10.FeatureLevel[] levels = new D3D10.FeatureLevel[]{D3D10.FeatureLevel.Level_10_1,
                D3D10.FeatureLevel.Level_10_0,
                D3D10.FeatureLevel.Level_9_3,
                D3D10.FeatureLevel.Level_9_2,
                D3D10.FeatureLevel.Level_9_1};
            foreach (D3D10.FeatureLevel level in levels)
            {
                try
                {
                    this.device = new D3D10.Device1(D3D10.DriverType.Hardware, D3D10.DeviceCreationFlags.BgraSupport,level);
                    break;
                }
                catch
                {
                    continue;
                }
            }
            if (this.device == null)
                throw new PlatformNotSupportedException("DirectX10デバイスの作成に失敗しました");

            IntPtr DesktopWnd = NativeMethods.GetDesktopWindow();
            D3D9.Direct3DEx d3dex = new D3D9.Direct3DEx();

            D3D9.PresentParameters param = new D3D9.PresentParameters();
            param.Windowed = true;
            param.SwapEffect = D3D9.SwapEffect.Discard;
            param.DeviceWindowHandle = DesktopWnd;
            param.PresentationInterval = D3D9.PresentInterval.Default;

            try
            {
                this.device9 = new D3D9.DeviceEx(
                    d3dex,
                    0,
                    D3D9.DeviceType.Hardware,
                    DesktopWnd,
                    D3D9.CreateFlags.HardwareVertexProcessing | D3D9.CreateFlags.Multithreaded | D3D9.CreateFlags.FpuPreserve,
                    param);
            }
            catch
            {
                try
                {
                    this.device9 = new D3D9.DeviceEx(
                        d3dex,
                        0,
                        D3D9.DeviceType.Hardware,
                        DesktopWnd,
                        D3D9.CreateFlags.SoftwareVertexProcessing | D3D9.CreateFlags.Multithreaded | D3D9.CreateFlags.FpuPreserve,
                        param);
                }
                catch
                {
                    throw new PlatformNotSupportedException("DirectX9デバイスの作成に失敗しました");
                }
            }
            finally
            {
                d3dex.Dispose();
            }
        }

        void DestructDevice()
        {
            if (this.device != null)
            {
                this.device.Dispose();
                this.device = null;
            }
            if (this.device9 != null)
            {
                this.device9.Dispose();
                this.device9 = null;
            }
        }

        D2D.RenderTarget ConstructRenderHandler(D2D.Factory factory, D2D.RenderTargetProperties prop, double width, double height)
        {
            D3D10.Texture2DDescription desc = new D3D10.Texture2DDescription();
            desc.Width = (int)width;
            desc.Height = (int)height;
            desc.MipLevels = 1;
            desc.ArraySize = 1;
            desc.Format = DXGI.Format.B8G8R8A8_UNorm;
            desc.SampleDescription = new DXGI.SampleDescription(1, 0);
            desc.Usage = D3D10.ResourceUsage.Default;
            desc.BindFlags = D3D10.BindFlags.RenderTarget | D3D10.BindFlags.ShaderResource;
            desc.CpuAccessFlags = D3D10.CpuAccessFlags.None;
            desc.OptionFlags = D3D10.ResourceOptionFlags.Shared;
            this.texture = new D3D10.Texture2D(this.device, desc);

            this.surface = this.texture.QueryInterface<DXGI.Surface>();

            DXGI.Resource resource = this.texture.QueryInterface<DXGI.Resource>();
            IntPtr handel = resource.SharedHandle;
            D3D9.Texture texture = new D3D9.Texture(
                this.device9,
                this.texture.Description.Width,
                this.texture.Description.Height,
                1,
                D3D9.Usage.RenderTarget,
                D3D9.Format.A8R8G8B8,
                D3D9.Pool.Default,
                ref handel);
            this.surface9 = texture.GetSurfaceLevel(0);
            resource.Dispose();
            texture.Dispose();

            this.render = new D2D.RenderTarget(factory, this.surface, prop);
            return this.render;
        }

        void ConstructedResourceHandler()
        {
            this.common.Foreground = this.ToColor4(this.ForegroundColor);
            this.common.Background = this.ToColor4(this.BackgroundColor);
            this.common.ControlChar = this.ToColor4(this.ControlCharColor);
            this.common.Url = this.ToColor4(this.UrlColor);
            this.common.Keyword1 = this.ToColor4(this.Keyword1Color);
            this.common.Keyword2 = this.ToColor4(this.Keyword2Color);
            this.common.Literal = this.ToColor4(this.LiteralColor);
            this.common.Comment = this.ToColor4(this.CommentColor);
            this.common.Hilight = this.ToColor4(this.HilightColor);
            this.common.LineMarker = this.ToColor4(this.LineMarkerColor);
            this.common.InsertCaret = this.ToColor4(this.InsertCaretColor);
            this.common.OverwriteCaret = this.ToColor4(this.OverwriteCaretColor);
        }

        void DestructRenderHandler()
        {
            if (this.texture != null)
                this.texture.Dispose();
            if (this.surface != null)
                this.surface.Dispose();
            if (this.surface9 != null)
                this.surface9.Dispose();
            if (this.render != null)
                this.render.Dispose();
        }
    }
}
