// >>> _using
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.Windows;
// <<< _using
using SharpDX.Direct2D1;
using SharpDX.DXGI;
using SharpDX.DirectWrite;

namespace Framefield.Core.ID70322625_ad2f_42ec_b324_b20eeeaa299c
{
    public class Class_Text : OperatorPart.Function
    {

        private void CreateResources(Vector2 canvasSize)
        {
            _usedCanvasSize = canvasSize;

            //Create a D3D10.1 render target texture and share it with our D3D11.
            var dx10TextureDesc = new SharpDX.Direct3D10.Texture2DDescription
                                      {
                                          Width = (int) canvasSize.X,
                                          Height = (int) canvasSize.Y,
                                          MipLevels = 1,
                                          ArraySize = 1,
                                          Format = Format.R8G8B8A8_UNorm,
                                          SampleDescription = new SampleDescription(1, 0),
                                          Usage = SharpDX.Direct3D10.ResourceUsage.Default,
                                          BindFlags = SharpDX.Direct3D10.BindFlags.RenderTarget | SharpDX.Direct3D10.BindFlags.ShaderResource,
                                          CpuAccessFlags = SharpDX.Direct3D10.CpuAccessFlags.None,
                                          OptionFlags = SharpDX.Direct3D10.ResourceOptionFlags.Shared
                                      };

            //Create the RT texture on D3D10
            Utilities.DisposeObj(ref _dx10Texture);
            _dx10Texture = new SharpDX.Direct3D10.Texture2D(D3DDevice.DX10_1Device, dx10TextureDesc);

            //Get DXGI Resource and retrieve the sharing handle.
            Utilities.DisposeObj(ref _dx11Texture);
            using (var dxgiSurface = _dx10Texture.QueryInterface<Surface>())
            using (var dxgiResource = dxgiSurface.QueryInterface<SharpDX.DXGI.Resource>())
            using (var dx11Resource = D3DDevice.Device.OpenSharedResource<SharpDX.Direct3D11.Resource>(dxgiResource.SharedHandle))
            {
                _dx11Texture = dx11Resource.QueryInterface<Texture2D>();
            }

            //Get DXGI Surface from the created render target.
            using (var dx10Surface = _dx10Texture.QueryInterface<Surface1>())
            {
                var referenceSize = 96.0f;
                _usedDpi = canvasSize.Y*ReferenceDpi/referenceSize;
                // Create a DC render target.
                var properties = new RenderTargetProperties
                                     {
                                         DpiX = _usedDpi,
                                         DpiY = _usedDpi,
                                         PixelFormat = new PixelFormat(Format.R8G8B8A8_UNorm, AlphaMode.Premultiplied),
                                         Type = RenderTargetType.Default
                                     };
                Utilities.DisposeObj(ref _bitmapRenderTarget);
                _bitmapRenderTarget = new RenderTarget(D3DDevice.Direct2DFactory, dx10Surface, properties);
            }

            Utilities.DisposeObj(ref _sceneColorBrush);
            _sceneColorBrush = new SolidColorBrush(_bitmapRenderTarget, Color.White);

            CreateSyncResource();
        }

        private void CreateSyncResource()
        {
            if (_syncResource != null)
                return;

            // Create the synchronizer for D3D10 which is a STAGING resource which we can lock (map)
            var syncResourceDesc = new SharpDX.Direct3D10.Texture2DDescription
                                       {
                                           Width = 16,
                                           Height = 16,
                                           MipLevels = 1,
                                           ArraySize = 1,
                                           Format = Format.R8G8B8A8_UNorm,
                                           SampleDescription = new SampleDescription(1, 0),
                                           Usage = SharpDX.Direct3D10.ResourceUsage.Staging,
                                           BindFlags = SharpDX.Direct3D10.BindFlags.None,
                                           CpuAccessFlags = SharpDX.Direct3D10.CpuAccessFlags.Read,
                                           OptionFlags = SharpDX.Direct3D10.ResourceOptionFlags.None
                                       };
            _syncResource = new SharpDX.Direct3D10.Texture2D(D3DDevice.DX10_1Device, syncResourceDesc);
        }

        private void SyncResources()
        {
            if (_syncResource == null || _bitmapRenderTarget == null)
                return;

            var region = new SharpDX.Direct3D10.ResourceRegion
                             {
                                 Left = 0,
                                 Top = 0,
                                 Front = 0,
                                 Right = 16,
                                 Bottom = 16,
                                 Back = 1
                             };
            D3DDevice.DX10_1Device.CopySubresourceRegion(_dx10Texture, 0, region, _syncResource, 0, 0, 0, 0);

            // this will wait until all GPU operations to _syncResource are done.
            _syncResource.Map(0, SharpDX.Direct3D10.MapMode.Read, SharpDX.Direct3D10.MapFlags.None);
            _syncResource.Unmap(0);
        }


        public override void Dispose()
        {
            Utilities.DisposeObj(ref _textFormat);
            Utilities.DisposeObj(ref _sceneColorBrush);
            Utilities.DisposeObj(ref _dx11Texture);
            Utilities.DisposeObj(ref _bitmapRenderTarget);
            Utilities.DisposeObj(ref _syncResource);
            Utilities.DisposeObj(ref _dx10Texture);
        }

        // >>> _inputids
        private enum InputId
        {
            Text = 0,
            Font = 1,
            FontSize = 2,
            FontColorR = 3,
            FontColorG = 4,
            FontColorB = 5,
            FontColorA = 6,
            PositionX = 7,
            PositionY = 8,
            Antialiased = 9,
            LineHeight = 10,
            ClearColorR = 11,
            ClearColorG = 12,
            ClearColorB = 13,
            ClearColorA = 14,
            SizeWidth = 15,
            SizeHeight = 16,
            FontWeight = 17,
            FontStyle = 18,
            VerticalAlign = 19,
            HorizontalAlign = 20
        }
        // <<< _inputids

        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            var SizeWidth = inputs[(int)InputId.SizeWidth].Eval(context).Value;
            var SizeHeight = inputs[(int)InputId.SizeHeight].Eval(context).Value;
            var Size = new Vector2(SizeWidth, SizeHeight);

            var useContextSize = Size.X < 16 || Size.Y < 16;
            var sizeToUse = useContextSize ? new Vector2(context.Viewport.Width, context.Viewport.Height) : Size;
            
            if (sizeToUse.X < 16 || sizeToUse.Y < 16)
            {
                Logger.Error("Targetbuffer has smaller width or height than 16!");
                return context;
            }

            if (sizeToUse != _usedCanvasSize)
            {
                CreateResources(sizeToUse);
                Changed = true;
            }

            if (Changed) 
            {
                //>>> __params
                var Text = inputs[(int)InputId.Text].Eval(context).Text;
                var Font = inputs[(int)InputId.Font].Eval(context).Text;
                var FontSize = inputs[(int)InputId.FontSize].Eval(context).Value;
                var FontColorR = inputs[(int)InputId.FontColorR].Eval(context).Value;
                var FontColorG = inputs[(int)InputId.FontColorG].Eval(context).Value;
                var FontColorB = inputs[(int)InputId.FontColorB].Eval(context).Value;
                var FontColorA = inputs[(int)InputId.FontColorA].Eval(context).Value;
                var FontColor = new Color4(FontColorR, FontColorG, FontColorB, FontColorA);
                var PositionX = inputs[(int)InputId.PositionX].Eval(context).Value;
                var PositionY = inputs[(int)InputId.PositionY].Eval(context).Value;
                var Position = new Vector2(PositionX, PositionY);
                var Antialiased = (int) inputs[(int)InputId.Antialiased].Eval(context).Value;
                var LineHeight = inputs[(int)InputId.LineHeight].Eval(context).Value;
                var ClearColorR = inputs[(int)InputId.ClearColorR].Eval(context).Value;
                var ClearColorG = inputs[(int)InputId.ClearColorG].Eval(context).Value;
                var ClearColorB = inputs[(int)InputId.ClearColorB].Eval(context).Value;
                var ClearColorA = inputs[(int)InputId.ClearColorA].Eval(context).Value;
                var ClearColor = new Color4(ClearColorR, ClearColorG, ClearColorB, ClearColorA);
                var FontWeight = inputs[(int)InputId.FontWeight].Eval(context).Value;
                var FontStyle = (int) inputs[(int)InputId.FontStyle].Eval(context).Value;
                var VerticalAlign = (int) inputs[(int)InputId.VerticalAlign].Eval(context).Value;
                var HorizontalAlign = (int) inputs[(int)InputId.HorizontalAlign].Eval(context).Value;
                //<<< __params

                Utilities.DisposeObj(ref _textFormat);
                _textFormat = new TextFormat(D3DDevice.DirectWriteFactory, Font, (FontWeight)FontWeight, (FontStyle)FontStyle, FontSize)
                                  {
                                      TextAlignment = (TextAlignment) HorizontalAlign,
                                      ParagraphAlignment = (ParagraphAlignment) VerticalAlign,
                                      WordWrapping = WordWrapping.NoWrap,
                                  };
                                  
                Text = Text.Replace("\\n", "\n");
                _textFormat.SetLineSpacing(LineSpacingMethod.Uniform, FontSize*LineHeight, FontSize*LineHeight*0.8f);
                
                if (VerticalAlign == 2) // center to baseline
                {
                    var layout = new TextLayout(D3DDevice.DirectWriteFactory, Text, _textFormat, _usedCanvasSize.X*ReferenceDpi/_usedDpi, _usedCanvasSize.Y*ReferenceDpi/_usedDpi);
                    var lineMetrics = layout.GetLineMetrics();
                    var baseline = lineMetrics[0].Baseline;
                    var layoutCenter = layout.MaxHeight / 2.0f;
                    PositionY = 1.0f - 2.0f*(layoutCenter - baseline)*_usedDpi/(_usedCanvasSize.Y*ReferenceDpi);
                    _textFormat.ParagraphAlignment = 0;
                }

                float offsetX = ((PositionX + 1.0f)/2.0f)*_usedCanvasSize.X*ReferenceDpi/_usedDpi;
                float offsetY = ((-PositionY + 1.0f)/2.0f)*_usedCanvasSize.Y*ReferenceDpi/_usedDpi;
                _clientRectangle = new RectangleF(offsetX, offsetY, 
                                                  offsetX + _usedCanvasSize.X*ReferenceDpi/_usedDpi, 
                                                  offsetY + _usedCanvasSize.Y*ReferenceDpi/_usedDpi);
                _bitmapRenderTarget.TextAntialiasMode = (Antialiased == 1) ? TextAntialiasMode.Default : TextAntialiasMode.Aliased;

                _sceneColorBrush.Color = FontColor;
                _bitmapRenderTarget.BeginDraw();
                _bitmapRenderTarget.Clear(ClearColor);
                _bitmapRenderTarget.DrawText(Text, _textFormat, _clientRectangle, _sceneColorBrush);
                _bitmapRenderTarget.EndDraw();
                SyncResources();

                Changed = false;
            }

            context.Image = _dx11Texture;

            return context;
        }

        private readonly float ReferenceDpi = 96.0f;
        private float _usedDpi;
        private Vector2 _usedCanvasSize;
        private TextFormat _textFormat;
        private SolidColorBrush _sceneColorBrush;
        private RectangleF _clientRectangle;
        private Texture2D _dx11Texture;
        private RenderTarget _bitmapRenderTarget;
        private SharpDX.Direct3D10.Texture2D _syncResource;
        private SharpDX.Direct3D10.Texture2D _dx10Texture;
    }
}

