using System;
using System.Collections.Generic;
using Windows.Graphics.Display;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Media.Imaging;
using SharpDX;
using DXGI = SharpDX.DXGI;
using D2D = SharpDX.Direct2D1;
using D3D = SharpDX.Direct3D;
using D3D11 = SharpDX.Direct3D11;
using FooEditEngine.Metro;
using DotNetTextStore;
using DotNetTextStore.UnmanagedAPI.TSF;
using DotNetTextStore.UnmanagedAPI.WinDef;

namespace FooEditEngine
{
    class D2DRender : ITextRender,IDisposable
    {
        public const int CramToFitWidth = 30;   //訂正できる場合はこの値を使う
        public const int MiniumeWidth = 40;    //これ以上ないと誤操作が起こる

        Windows.UI.Color ForegroundColor, BackgroundColor, HilightColor, Keyword1Color, Keyword2Color, LiteralColor, UrlColor, ControlCharColor, CommentColor, InsertCaretColor, OverwriteCaretColor, LineMarkerColor;
        FontFamily _FontFamily;
        double _FontSize;
        SurfaceImageSource SurfaceImage;
        DXGI.ISurfaceImageSourceNative SurfaceImageNative;
        D3D11.Device1 D3DDevice;
        DXGI.Device DXGIDevice;
        D2D.Device D2DDevice;
        D2D.DeviceContext D2DContext;
        D2D.Bitmap1 Bitmap;
        Size Size = new Size();
        DXGI.Surface Surface;
        D2DRenderCommon common = new D2DRenderCommon();
        TextStore2 store;

        public D2DRender(FooTextBox textbox,Windows.UI.Xaml.Shapes.Rectangle rect,TextStore2 store)
        {
            this.store = store;

            var creationFlags = SharpDX.Direct3D11.DeviceCreationFlags.VideoSupport | SharpDX.Direct3D11.DeviceCreationFlags.BgraSupport;
            D3D.FeatureLevel[] featureLevels ={D3D.FeatureLevel.Level_11_1,
                                                 D3D.FeatureLevel.Level_11_0,
                                                 D3D.FeatureLevel.Level_10_1,
                                                 D3D.FeatureLevel.Level_10_0,
                                                 D3D.FeatureLevel.Level_9_3,
                                                 D3D.FeatureLevel.Level_9_2,
                                                 D3D.FeatureLevel.Level_9_1};
            using (var device = new D3D11.Device(D3D.DriverType.Hardware, creationFlags, featureLevels))
            {
                this.D3DDevice = device.QueryInterface<D3D11.Device1>();
            }
            this.DXGIDevice = this.D3DDevice.QueryInterface<DXGI.Device>();

            this.common.ConstructRender = this.ConstructRenderHandler;
            this.common.ConstrctedResource = this.ConstructedResourceHandler;
            this.common.DestructRender = this.DestructRenderHandler;
            this.common.ReCreateTarget = this.ReCreateTarget;
            this.common.GetDpi = this.GetDpi;
            this.common.ConstructDeviceResource(rect.ActualWidth, rect.ActualHeight);
            this.common.InitTextFormat(textbox.FontFamily.Source, (float)textbox.FontSize);

            this._FontFamily = textbox.FontFamily;
            this._FontSize = textbox.FontSize;
            this.Foreground = textbox.Foreground;
            this.Background = textbox.Background;
            this.Hilight = textbox.Hilight;
            this.Keyword1 = textbox.Keyword1;
            this.Keyword2 = textbox.Keyword2;
            this.Literal = textbox.Literal;
            this.Url = textbox.URL;
            this.ControlChar = textbox.ControlChar;
            this.Comment = textbox.Comment;
            this.InsertCaret = textbox.InsertCaret;
            this.OverwriteCaret = textbox.OverwriteCaret;
            this.LineMarker = textbox.LineMarker;

            this.Size.Width = rect.ActualWidth;
            this.Size.Height = rect.ActualHeight;

            this.CreateSurface(rect, rect.ActualWidth, rect.ActualHeight);
        }

        private void GetDpi(out int dpix, out int dpiy)
        {
            dpix = (int)DisplayProperties.LogicalDpi;
            dpiy = (int)DisplayProperties.LogicalDpi;
        }

        public void Dispose()
        {
            if (this.DXGIDevice != null)
                this.DXGIDevice.Dispose();
            if (this.D3DDevice != null)
                this.D3DDevice.Dispose();
            this.common.Dispose();
        }

        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(value.Source, (float)this._FontSize);
                this.TabWidthChar = this.TabWidthChar;
            }
        }

        public double FontSize
        {
            get { return this._FontSize; }
            set
            {
                this._FontSize = value;
                this.common.InitTextFormat(this._FontFamily.Source, (float)value);
                this.TabWidthChar = this.TabWidthChar;
            }
        }

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

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

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

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

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

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

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

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

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

        public Windows.UI.Color Hilight
        {
            get
            {
                return this.HilightColor;
            }
            set
            {
                this.HilightColor = value;
                this.HilightColor.A = 50;
                this.common.Hilight = this.ToColor4(this.HilightColor);
            }
        }

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

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

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

        public Windows.UI.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 Math.Max(D2DRender.MiniumeWidth,this.common.FoldingWidth);
            }
        }

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

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

        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 IsCanDraw()
        {
            return this.Size.Height != 0 && this.Size.Width != 0;
        }

        public void BeginDraw()
        {
            SharpDX.Point offset;
            this.Surface = this.SurfaceImageNative.BeginDraw(
                new SharpDX.Rectangle(0, 0, (int)this.Size.Width, (int)this.Size.Height), out offset);
            int dpix, dpiy;
            this.GetDpi(out dpix, out dpiy);
            D2D.BitmapProperties1 prop = new D2D.BitmapProperties1(new D2D.PixelFormat(DXGI.Format.B8G8R8A8_UNorm, D2D.AlphaMode.Premultiplied),
                    dpix, dpiy, D2D.BitmapOptions.Target | D2D.BitmapOptions.CannotDraw);
            this.Bitmap = new D2D.Bitmap1(this.D2DContext, this.Surface, prop);
            this.D2DContext.Target = this.Bitmap;
            this.D2DContext.Transform = Matrix3x2.Translation(offset.X,offset.Y);
            this.common.BegineDraw();
        }

        public void EndDraw()
        {
            this.common.EndDraw();
            this.Surface.Dispose();
            this.Bitmap.Dispose();
            this.SurfaceImageNative.EndDraw();
        }

        public bool Resize(Windows.UI.Xaml.Shapes.Rectangle rect,double width, double height)
        {
            if (this.Size.Width == width && this.Size.Height == height)
                return false;
            this.common.ReConstructDeviceResource(width, height);
            this.CreateSurface(rect, width, height);
            return true;
        }

        void CreateSurface(Windows.UI.Xaml.Shapes.Rectangle rect, double width, double height)
        {
            if (this.SurfaceImageNative != null)
                this.SurfaceImageNative.Dispose();
            this.SurfaceImage = new SurfaceImageSource((int)width, (int)height);
            this.SurfaceImageNative = ComObject.As<DXGI.ISurfaceImageSourceNative>(this.SurfaceImage);
            this.SurfaceImageNative.Device = this.DXGIDevice;
            this.Size.Width = width;
            this.Size.Height = height;
            ImageBrush brush = new ImageBrush();
            brush.ImageSource = this.SurfaceImage;
            rect.Fill = brush;
        }

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

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

        public void CacheContent()
        {
            //this.Common.CacheContent();
        }

        public bool IsVaildCache()
        {
            //return this.Common.IsVaildCache();
            return false;
        }

        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 DrawGripper(Point p,double radius)
        {
            this.common.DrawGripper(p, radius);
        }

        public void DrawFoldingMark(bool expand, double x, double y)
        {
            string mark = expand ? "-" : "+";
            this.DrawString(mark, x, y, StringAlignment.Center, new Size(this.FoldingWidth, this.emSize.Height));
        }

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

        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.ToColor4(this.Foreground);
                                break;
                            case TF_DA_LINESTYLE.TF_LS_SOLID:
                                type = HilightType.Sold;
                                color = this.ToColor4(this.Foreground);
                                break;
                            case TF_DA_LINESTYLE.TF_LS_DASH:
                                type = HilightType.Dash;
                                color = this.ToColor4(this.Foreground);
                                break;
                            case TF_DA_LINESTYLE.TF_LS_SQUIGGLE:
                                type = HilightType.Squiggle;
                                color = this.ToColor4(this.Foreground);
                                break;
                        }

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

                        this.common.DrawMarkerEffect(layout, type, start, length, x, y, attr.attribute.fBoldLine, color);
                    }
                }
            };
            this.common.DrawOneLine(lti,
                row,
                x,
                y,
                SelectRanges,
                PreDrawOneLine);
        }

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

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

        D2D.RenderTarget ConstructRenderHandler(D2D.Factory1 factory, D2D.RenderTargetProperties prop, double width, double height)
        {
            this.D2DDevice = new D2D.Device(factory,this.DXGIDevice);
            this.D2DContext = new D2D.DeviceContext(this.D2DDevice, D2D.DeviceContextOptions.None);
            return this.D2DContext;
        }

        void ConstructedResourceHandler()
        {
        }

        void DestructRenderHandler()
        {
            if (this.D2DDevice != null)
                this.D2DDevice.Dispose();
            if (this.D2DContext != null)
                this.D2DContext.Dispose();
        }

        void ReCreateTarget()
        {
        }
    }
}
