/*
 * 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.Input;
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 D3D11 = SharpDX.Direct3D11;
using D3D9 = SharpDX.Direct3D9;
using DXGI = SharpDX.DXGI;

namespace FooEditEngine.WPF
{
    sealed class D2DRender : D2DRenderCommon, IEditorRender
    {
        DotNetTextStore.TextStore store;
        D3D11.Texture2D texture;
        DXGI.Surface surface;
        D3D11.Device device;
        D3D9.Device device9;
        D3D9.Surface surface9;
        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.Foreground = ToColor4(textbox.Foreground);
            this.Background = ToColor4(textbox.Background);
            this.ControlChar = ToColor4(textbox.ControlChar);
            this.Hilight = ToColor4(textbox.Hilight);
            this.Comment = ToColor4(textbox.Comment);
            this.Url = ToColor4(textbox.URL);
            this.Keyword1 = ToColor4(textbox.Keyword1);
            this.Keyword2 = ToColor4(textbox.Keyword2);
            this.Literal = ToColor4(textbox.Literal);
            this.InsertCaret = ToColor4(textbox.InsertCaret);
            this.OverwriteCaret = ToColor4(textbox.OverwriteCaret);
            this.LineMarker = ToColor4(textbox.LineMarker);
            this.UpdateArea = ToColor4(textbox.UpdateArea);
            this.LineNumber = ToColor4(textbox.LineNumber);
            this.HilightForeground = ToColor4(textbox.HilightForeground);
            this.store = textbox.TextStore;

            this.CreateDevice();

            this.ConstructRenderAndResource(width, height);
            this.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;
        }

        public FontFamily FontFamily
        {
            get { return this.fontFamily; }
            set
            {
                this.fontFamily = value;
                this.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.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.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.InitTextFormat(this.fontFamily.Source, (float)this.fontSize, this.GetDWFontWeigth(this.fontWeigth), this.GetDWFontStyle(this.fontStyle));
            }
        }

        public static 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 bool Resize(double width, double height)
        {
            if (Math.Floor(width) != Math.Floor(this.imageSource.Width) || Math.Floor(height) != Math.Floor(this.imageSource.Height))
            {
                this.ReConstructRenderAndResource(width, height);
                this.imageSource.Lock();
                this.imageSource.SetBackBuffer(D3DResourceType.IDirect3DSurface9, this.surface9.NativePointer);
                this.imageSource.Unlock();
                return true;
            }
            return false;
        }

        public void ReConstructRenderAndResource(double width, double height)
        {
            this.DestructRenderAndResource();
            this.ConstructRenderAndResource(width, height);
        }

        public void DrawContent(EditView view, bool IsEnabled, Rectangle updateRect)
        {
            if (this.imageSource.IsFrontBufferAvailable)
            {
                this.imageSource.Lock();
                this.imageSource.SetBackBuffer(D3DResourceType.IDirect3DSurface9, this.surface9.NativePointer);

                base.BegineDraw();
                if (IsEnabled)
                    view.Draw(updateRect);
                else
                    this.FillBackground(updateRect);
                base.EndDraw();
                this.device.ImmediateContext.Flush();

                this.imageSource.AddDirtyRect(new Int32Rect(0, 0, (int)this.imageSource.PixelWidth, (int)this.imageSource.PixelHeight));
                this.imageSource.Unlock();
            }
        }

        public void DrawOneLine(Document doc, LineToIndexTable lti, int row, double x, double y)
        {
            PreDrawOneLineHandler PreDrawOneLine = null;

            if (InputMethod.Current.ImeState == InputMethodState.On)
                PreDrawOneLine = this.DrawImeConversionLine;

            base.DrawOneLine(doc,
                lti,
                row,
                x,
                y,
                PreDrawOneLine
                );
        }

        private void DrawImeConversionLine(MyTextLayout layout, LineToIndexTable lti, int row, double x, double y)
        {
            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.DrawMarkerEffect(layout, type, start, length, x, y, attr.attribute.fBoldLine, color);

                    color = this.GetColor4(attr.attribute.crText);
                    if (color != null)
                    {
                        this.SetTextColor(layout, start, length, color);
                        layout.Invaild = true;
                    }
                }
            }

        }

        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);
        }

        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());
        }

        public override void DrawCachedBitmap(Rectangle rect)
        {
            if (this.render == null || this.render.IsDisposed || this.cachedBitMap == null || this.cachedBitMap.IsDisposed)
                return;
            render.DrawBitmap(this.cachedBitMap, rect, 1.0f, D2D.BitmapInterpolationMode.Linear, rect);
        }

        public override void CacheContent()
        {
            if (this.render == null || this.cachedBitMap == null || this.cachedBitMap.IsDisposed || this.render.IsDisposed)
                return;
            render.Flush();
            this.cachedBitMap.CopyFromRenderTarget(this.render, new SharpDX.Point(), new SharpDX.Rectangle(0, 0, (int)this.renderSize.Width, (int)this.renderSize.Height));
            this.hasCache = true;
        }

        void CreateDevice()
        {
            SharpDX.Direct3D.FeatureLevel[] levels = new SharpDX.Direct3D.FeatureLevel[]{
                SharpDX.Direct3D.FeatureLevel.Level_11_0,
                SharpDX.Direct3D.FeatureLevel.Level_10_1,
                SharpDX.Direct3D.FeatureLevel.Level_10_0,
                SharpDX.Direct3D.FeatureLevel.Level_9_3,
                SharpDX.Direct3D.FeatureLevel.Level_9_2,
                SharpDX.Direct3D.FeatureLevel.Level_9_1};
            foreach (var level in levels)
            {
                try
                {
                    this.device = new D3D11.Device(SharpDX.Direct3D.DriverType.Hardware, D3D11.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;
            }
        }

        public void ConstructRenderAndResource(double width, double height)
        {
            float dpiX, dpiY;
            this.GetDpi(out dpiX, out dpiY);
            D2D.RenderTargetProperties prop = new D2D.RenderTargetProperties(
                D2D.RenderTargetType.Default,
                new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Premultiplied),
                dpiX,
                dpiY,
                D2D.RenderTargetUsage.None,
                D2D.FeatureLevel.Level_DEFAULT);

            D3D11.Texture2DDescription desc = new D3D11.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 = D3D11.ResourceUsage.Default;
            desc.BindFlags = D3D11.BindFlags.RenderTarget | D3D11.BindFlags.ShaderResource;
            desc.CpuAccessFlags = D3D11.CpuAccessFlags.None;
            desc.OptionFlags = D3D11.ResourceOptionFlags.Shared;
            this.texture = new D3D11.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(this.D2DFactory, this.surface, prop);

            D2D.BitmapProperties bmpProp = new D2D.BitmapProperties();
            bmpProp.DpiX = dpiX;
            bmpProp.DpiY = dpiY;
            bmpProp.PixelFormat = new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Premultiplied);
            this.cachedBitMap = new D2D.Bitmap(this.render, new SharpDX.Size2((int)width, (int)height), bmpProp);
            this.hasCache = false;

            this.textRender = new CustomTextRenderer(this.render, this.Brushes, this.Strokes, this.Foreground);

            this.renderSize = new Size(width, height);
        }

        public void DestructRenderAndResource()
        {
            this.hasCache = false;
            if (this.cachedBitMap != null)
                this.cachedBitMap.Dispose();
            this.Brushes.Clear();
            this.Strokes.Clear();
            if (this.textRender != null)
                this.textRender.Dispose();
            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();
        }

        public override void GetDpi(out float dpix, out float dpiy)
        {
            var dpiXProperty = typeof(SystemParameters).GetProperty("DpiX", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);
            var dpiYProperty = typeof(SystemParameters).GetProperty("Dpi", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static);

            dpix = (int)dpiXProperty.GetValue(null, null);
            dpiy = (int)dpiYProperty.GetValue(null, null);
        }

        protected override void Dispose(bool dispose)
        {
            base.Dispose(dispose);
            this.DestructRenderAndResource();
            this.DestructDevice();
        }

    }
}
