using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using BmFont;
using Framefield.Core.OperatorPartTraits;
using Framefield.Core.Rendering;
using SharpDX;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using Buffer = SharpDX.Direct3D11.Buffer;
using MapFlags = SharpDX.Direct3D11.MapFlags;

namespace Framefield.Core.IDabf37643_6e5b_48a3_aa9a_84a7c82edf89
{
    public class Class_DrawBMFontText : OperatorPart.Function, IMeshSupplier
    {
        // >>> _inputids
        private enum InputId
        {
            ColorA = 0,
            ColorB = 1,
            ColorG = 2,
            ColorR = 3,
            Text = 4,
            FontImage = 5,
            FontInfo = 6,
            KerningEnabled = 7,
            CharacterSpacing = 8,
            LineHeight = 9,
            Slant = 10
        }
        // <<< _inputids
        
        public Class_DrawBMFontText()
        {
        }

        public override void Dispose()
        {
            Utilities.DisposeObj(ref _rectangleMesh);
        }

        // IMeshSupplier
        public void AddMeshesTo(ICollection<Mesh> meshes)
        {
            UpdateMesh(new OperatorPartContext(), OperatorPart.Connections);
            meshes.Add(_rectangleMesh);
        }

        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            try
            {
                UpdateMesh(context, inputs);

                using (var srv = new ShaderResourceView(context.D3DDevice, _fontImage))
                {
                    var prevTexture = context.Texture0;
                    context.Texture0 = srv;
                    var prevDepthState = context.DepthStencilState;
                    context.DepthStencilState = OperatorPartContext.DefaultRenderer.DisabledDepthStencilState;

                    context.Renderer.SetupEffect(context);
                    context.Renderer.Render(_rectangleMesh, context);

                    context.DepthStencilState = prevDepthState;
                    context.Texture0 = prevTexture;
                }
            }
            catch (Exception)
            {
                Logger.Error(this,"error");
            }

            return context;
        }

        private void UpdateMesh(OperatorPartContext context, IList<OperatorPart> inputs)
        {
            if (_rectangleMesh != null && !Changed)
                return;

            // >>> params
            var ColorA = inputs[(int)InputId.ColorA].Eval(context).Value;
            var ColorB = inputs[(int)InputId.ColorB].Eval(context).Value;
            var ColorG = inputs[(int)InputId.ColorG].Eval(context).Value;
            var ColorR = inputs[(int)InputId.ColorR].Eval(context).Value;
            var Color = new Vector4(ColorA, ColorB, ColorG, ColorR);
            var Text = inputs[(int)InputId.Text].Eval(context).Text;
            bool fontImageChanged = inputs[(int) InputId.FontImage].Func.Changed;
            var FontImage = inputs[(int)InputId.FontImage].Eval(context).Image; 
            var FontInfo = inputs[(int)InputId.FontInfo].Eval(context).Dynamic;
            bool KerningEnabled = inputs[(int)InputId.KerningEnabled].Eval(context).Value > 0.5f;
            var CharacterSpacing = inputs[(int)InputId.CharacterSpacing].Eval(context).Value;
            var LineHeight = inputs[(int)InputId.LineHeight].Eval(context).Value;
            var Slant = inputs[(int)InputId.Slant].Eval(context).Value;
            // <<< params

            _fontImage = FontImage;
            if (fontImageChanged)
            {
                Utilities.DisposeObj(ref _fontImageSRV);
                _fontImageSRV = new ShaderResourceView(context.D3DDevice, _fontImage);
            }

            int numCharactersInText = Text.Count();
            _drawnText = Text;

            var normal = new Vector3(0.0f, 0.0f, -1.0f);
            var color = new Vector4(ColorR, ColorG, ColorB, ColorA);
            var tangent = new Vector3(1.0f, 0.0f, 0.0f);
            var binormal = new Vector3(0.0f, -1.0f, 0.0f);

            var inputElements = new InputElement[]
                                    {
                                        new InputElement("POSITION", 0, Format.R32G32B32A32_Float, 0, 0),
                                        new InputElement("NORMAL", 0, Format.R32G32B32_Float, 16, 0),
                                        new InputElement("COLOR", 0, Format.R32G32B32A32_Float, 28, 0),
                                        new InputElement("TEXCOORD", 0, Format.R32G32_Float, 44, 0),
                                        new InputElement("TANGENT", 0, Format.R32G32B32_Float, 52, 0),
                                        new InputElement("BINORMAL", 0, Format.R32G32B32_Float, 64, 0)
                                    };

            int numQuads = numCharactersInText;

            const int attributesSize = 76;
            int numTriangles = numQuads*2;
            var streamSize = numTriangles*3*attributesSize;

            if (_rectangleMesh == null || streamSize != _rectangleMesh.NumTriangles*3*_rectangleMesh.AttributesSize)
            {
                Utilities.DisposeObj(ref _rectangleMesh);
                using (var stream = new DataStream(streamSize, true, true))
                {
                    var vertices = new Buffer(context.D3DDevice, stream, new BufferDescription
                                                                             {
                                                                                 BindFlags = BindFlags.VertexBuffer,
                                                                                 CpuAccessFlags = CpuAccessFlags.Write,
                                                                                 OptionFlags = ResourceOptionFlags.None,
                                                                                 SizeInBytes = streamSize,
                                                                                 Usage = ResourceUsage.Dynamic
                                                                             });
                    _rectangleMesh = new Mesh
                                         {
                                             InputElements = inputElements,
                                             Vertices = vertices,
                                             NumTriangles = numTriangles,
                                             AttributesSize = attributesSize
                                         };
                }
            }

            DataStream vertexStream;
            context.D3DDevice.ImmediateContext.MapSubresource(_rectangleMesh.Vertices, MapMode.WriteDiscard, MapFlags.None, out vertexStream);
            using (vertexStream)
            {
                vertexStream.Position = 0;

                var fontFile = (FontFile) FontInfo;
                float textureWidth = fontFile.Common.ScaleW;
                float textureHeight = fontFile.Common.ScaleH;
                float cursorX = 0;
                float cursorY = 0;
                const float scale = 0.01f;

                for (int charIndex = 0; charIndex < _drawnText.Length; ++charIndex)
                {
                    var charToDraw = _drawnText[charIndex];
                    if (charToDraw == '\n')
                    {
                        cursorY -= fontFile.Common.LineHeight * LineHeight;
                        cursorX = 0;
                        continue;
                    }
                    var charInfo = (from @char in fontFile.Chars
                                    where @char.ID == (int) charToDraw
                                    select @char).SingleOrDefault();
                    if (charInfo == null)
                    {
                        continue;
                    }

                    float uLeft = charInfo.X/textureWidth;
                    float vTop = charInfo.Y/textureHeight;
                    float uRight = (charInfo.X + charInfo.Width)/textureWidth;
                    float vBottom = (charInfo.Y + charInfo.Height)/textureHeight;

                    var SizeWidth = charInfo.Width;
                    var SizeHeight = charInfo.Height;

                    float bottom = cursorY + charInfo.YOffset;
                    float top = bottom + SizeHeight;
                    float left = cursorX + charInfo.XOffset;
                    float right = left + SizeWidth;
                    float slantTop = Slant * 0.001f * SizeHeight;

                    bottom *= scale;
                    top *= scale;
                    left *= scale;
                    right *= scale;

                    // tri 1 vert 1
                    vertexStream.Write(new Vector4(right + slantTop, top, 0, 1));
                    vertexStream.Write(normal);
                    vertexStream.Write(color);
                    vertexStream.Write(new Vector2(uRight, vTop));
                    vertexStream.Write(tangent);
                    vertexStream.Write(binormal);

                    // tri 1 vert 2
                    vertexStream.Write(new Vector4(right, bottom, 0, 1));
                    vertexStream.Write(normal);
                    vertexStream.Write(color);
                    vertexStream.Write(new Vector2(uRight, vBottom));
                    vertexStream.Write(tangent);
                    vertexStream.Write(binormal);

                    // tri 1 vert 3
                    vertexStream.Write(new Vector4(left, bottom, 0, 1));
                    vertexStream.Write(normal);
                    vertexStream.Write(color);
                    vertexStream.Write(new Vector2(uLeft, vBottom));
                    vertexStream.Write(tangent);
                    vertexStream.Write(binormal);

                    // tri 2 vert 1
                    vertexStream.Write(new Vector4(left, bottom, 0, 1));
                    vertexStream.Write(normal);
                    vertexStream.Write(color);
                    vertexStream.Write(new Vector2(uLeft, vBottom));
                    vertexStream.Write(tangent);
                    vertexStream.Write(binormal);

                    // tri 2 vert 2
                    vertexStream.Write(new Vector4(left + slantTop, top, 0, 1));
                    vertexStream.Write(normal);
                    vertexStream.Write(color);
                    vertexStream.Write(new Vector2(uLeft, vTop));
                    vertexStream.Write(tangent);
                    vertexStream.Write(binormal);

                    // tri 2 vert 3
                    vertexStream.Write(new Vector4(right + slantTop, top, 0, 1));
                    vertexStream.Write(normal);
                    vertexStream.Write(color);
                    vertexStream.Write(new Vector2(uRight, vTop));
                    vertexStream.Write(tangent);
                    vertexStream.Write(binormal);

                    float kerning = 0.0f;
                    if (KerningEnabled && charIndex < _drawnText.Length - 1)
                    {
                        var nextCharInfo = (from @char in fontFile.Chars
                                            where @char.ID == (int) _drawnText[charIndex + 1]
                                            select @char).SingleOrDefault();
                        if (nextCharInfo != null)
                        {
                            var kerningInfo = (from d in fontFile.Kernings
                                               where d.First == charInfo.ID
                                               where d.Second == nextCharInfo.ID
                                               select d).SingleOrDefault();
                            if (kerningInfo != null)
                            {
                                kerning = kerningInfo.Amount;
                                //Logger.Info(this, "Kerning for {0} and {1}: {2}", charInfo.ID, nextCharInfo.ID, kerningInfo.Amount);
                            }
                        }
                    }
                    cursorX += kerning;
                    cursorX += charInfo.XAdvance;
                    cursorX += CharacterSpacing;
                }
            }
            context.D3DDevice.ImmediateContext.UnmapSubresource(_rectangleMesh.Vertices, 0);

            Changed = false;
        }

        private Mesh _rectangleMesh;
        private Texture2D _fontImage;
        private ShaderResourceView _fontImageSRV;
        private string _drawnText = string.Empty;
    }
}