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,
            HorizontalAlign = 11,
            VerticalAlign = 12
        }
        // <<< _inputids
        
        //>>> _outputids
        private enum OutputId
        {
            Out = 0,
            LastWidth = 1
        }
        //<<< _outputids
        
        public override void Dispose()
        {
            Utilities.DisposeObj(ref _mesh);
        }

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

        private string _text;
        private float _characterSpacing;
        private float _lineHeight;
        private float _slant;
        private int _horizontalAlign;
        private int _verticalAlign;
        
        public override OperatorPartContext Eval(OperatorPartContext context, List<OperatorPart> inputs, int outputIdx)
        {
            
            UpdateMesh(context, inputs);                    
        
            if(outputIdx == 0) {
                if(_fontImage == null || _mesh ==null)
                    return context;
            
                try
                {                
    
                    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(_mesh, context);
    
                        context.DepthStencilState = prevDepthState;
                        context.Texture0 = prevTexture;
                    }
                }
                catch (Exception e)
                {
                    Logger.Error(this,"error: {0}", e);
                }            
            }
            else if (outputIdx == 1) {            
                context.Value = _lastWidth;
            }

            return context;
        }
        
        private float _lastWidth;

        private void UpdateMesh(OperatorPartContext context, IList<OperatorPart> inputs)
        {
        
            if (_mesh != 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;
            var FontImage = inputs[(int)InputId.FontImage].Eval(context).Image; // Needs to be checked for null!
            var FontInfo = inputs[(int)InputId.FontInfo].Eval(context).Dynamic;  // Needs to be checked for null!
            var KerningEnabled = (int) inputs[(int)InputId.KerningEnabled].Eval(context).Value;
            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;
            var HorizontalAlign = (int) inputs[(int)InputId.HorizontalAlign].Eval(context).Value;
            var VerticalAlign = (int) inputs[(int)InputId.VerticalAlign].Eval(context).Value;
            // <<< _params
            
            if(FontImage == null) {
                Logger.Warn("DrawBMFontText.FontImage missing");
                return;
            }
            
            if(FontInfo == null) {
                Logger.Warn("DrawBMFontText.FontInfo missing");
                return;
            }
            
            var somethingChanged = false;
            
            if( _horizontalAlign != (int)HorizontalAlign) {
                _horizontalAlign = (int)HorizontalAlign;
                somethingChanged = true;
            }
            
            if( _verticalAlign != (int)VerticalAlign) {
                _verticalAlign = (int)VerticalAlign;
                somethingChanged = true;
            }
            
            if( _text != Text) {
                _text = Text;
                somethingChanged = true;
            }
            
            if( _characterSpacing != CharacterSpacing) {
                _characterSpacing = CharacterSpacing;
                somethingChanged= true;
            }
            
            if( _slant != Slant) {
                _slant = Slant;
                somethingChanged = true;
            }
            
            if( _lineHeight != LineHeight) {
                _lineHeight = LineHeight;
                somethingChanged = true;
            }
            
            if( !somethingChanged ) {
                return;
            }


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

            if(Text == "") 
                Text = " "; // prevent crash error
                
            int numCharactersInText = Text.Length;
            int numLinesInText = Text.Split('\n').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);

            int numQuads = numCharactersInText;

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

            if (_mesh == null || streamSize != _mesh.NumTriangles*3*_mesh.AttributesSize)
            {
                Utilities.DisposeObj(ref _mesh);
                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
                                                                             });
                    _mesh = new Mesh
                                         {
                                             InputElements = _inputElements,
                                             Vertices = vertices,
                                             NumTriangles = numTriangles,
                                             AttributesSize = attributesSize
                                         };
                }
            }

            DataStream vertexStream;
            context.D3DDevice.ImmediateContext.MapSubresource(_mesh.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;
                
                if((int)VerticalAlign == 1) 
                {
                    cursorY = fontFile.Common.LineHeight * LineHeight * (numLinesInText - 2f)/2;
                }
                else if( (int)VerticalAlign == 2) 
                {
                    cursorY = fontFile.Common.LineHeight * LineHeight * numLinesInText;
                }
                
                const float scale = 0.01f;
                float maxWidth = float.NegativeInfinity;
                numTriangles = 0;
                //double lineWidthInitialized = Double.NaN;
                float lineWidth = float.NaN;

                for (int charIndex = 0; charIndex < _drawnText.Length; ++charIndex)
                {
                
                    // Compute linewidth for horizontal Alignment                    
                    if(  float.IsNaN(lineWidth )){
                        
                        int lookAheadIndex = 0;
                        lineWidth = 0;
                                                
                        while(charIndex + lookAheadIndex < _drawnText.Length ) {
                            var charForWidth = _drawnText[charIndex + lookAheadIndex];
                            lookAheadIndex++;
                            if(charForWidth == '\n' || charForWidth == '\r')
                                break;
                        
                            var charInfo2 = (from @char in fontFile.Chars
                                            where @char.ID == (int) charForWidth
                                            select @char).SingleOrDefault();
                            var width = charInfo2.Width;
    
                            float kerning2 = 0.0f;
                            
                            if (KerningEnabled > 0.5f && charIndex + lookAheadIndex < _drawnText.Length - 1 && _drawnText[charIndex+lookAheadIndex+1] != '\n')
                            {
                                var nextCharInfo2 = (from @char in fontFile.Chars
                                                    where @char.ID == (int) _drawnText[charIndex + lookAheadIndex + 1]
                                                    select @char).SingleOrDefault();
                                if (nextCharInfo2 != null)
                                {
                                    var kerningInfo2 = (from d in fontFile.Kernings
                                                       where d.First == charInfo2.ID
                                                       where d.Second == nextCharInfo2.ID
                                                       select d).SingleOrDefault();
                                    if (kerningInfo2 != null)
                                    {
                                        kerning2 = kerningInfo2.Amount;
                                    }
                                }
                            }
                            lineWidth += kerning2;
                            lineWidth += charInfo2.XAdvance;
                            lineWidth += CharacterSpacing;    
                        }
                        if(HorizontalAlign == 1) {
                            cursorX-= lineWidth/2 - CharacterSpacing/2;
                        }
                        else if(HorizontalAlign == 2) {
                            cursorX-= lineWidth;
                        }
                        if( lineWidth > maxWidth) {
                            maxWidth = lineWidth;
                        }
                    }
                    
                
                    var charToDraw = _drawnText[charIndex];
                    if (charToDraw == '\n')
                    {
                        cursorY -= fontFile.Common.LineHeight * LineHeight;
                        cursorX = 0;
                        lineWidth = float.NaN;
                        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 > 0.5f && 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;
                    numTriangles += 2;
                }
                _mesh.NumTriangles = numTriangles; // set real drawn number of triangles
                _lastWidth = maxWidth;            }
            context.D3DDevice.ImmediateContext.UnmapSubresource(_mesh.Vertices, 0);


            Changed = false;
        }

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

        private readonly InputElement[] _inputElements = {
                                                             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)
                                                         };

    }
}