using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using SharpFont;
using System.Reflection;
using Encoding = SharpFont.Encoding;

namespace Updater;

internal class RenderText : IRenderObject
{
    public static readonly Shader DefaultTextShader = new(@"#version 460

            layout (location = 0) in vec2 in_pos;
            layout (location = 1) in vec2 in_uv;

            out vec2 vUV;

            layout (location = 0) uniform mat4 model;
            layout (location = 1) uniform mat4 projection;
            
            void main()
            {
                vUV         = in_uv.xy;
		        gl_Position = projection * model * vec4(in_pos.xy, 0.0, 1.0);
            }",
        @"
#version 460

in vec2 vUV;

layout (binding=0) uniform sampler2D u_texture;

layout (location = 2) uniform vec4 textColor;

out vec4 fragColor;

void main()
{
    vec2 uv = vUV.xy;
    float text = texture(u_texture, uv).r;
    fragColor = vec4(textColor.rgba*text);
}");

    public RenderText(string Text, string Font, uint PixelHeight, float x, float y, float scale, Vector2 dir, Vector4 col)
    {
        this.Text = Text;
        this.PixelHeight = PixelHeight;
        this.X = FloatToInt(x, 1);
        this.Y = FloatToInt(y, 1, true);
        this.Scale = scale;
        this.DIR = dir;
        this.Color = col;
        this.Font = Font;

        


        Library lib = new();

        Assembly assembly = Assembly.GetExecutingAssembly();
        Stream? resource_stream = assembly.GetManifestResourceStream($"Updater.Resource.{Font}.ttf");
        MemoryStream ms = new();
        resource_stream?.CopyTo(ms);
        Face face = new(lib, ms.ToArray(), 0);
        face.SetPixelSizes(0, PixelHeight);
        GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
        GL.ActiveTexture(TextureUnit.Texture0);
        face.SelectCharmap(Encoding.Unicode);

        for (uint c = 0; c < 0; c++)
        {
            try
            {
                face.LoadChar(c, LoadFlags.Render, LoadTarget.Normal);
                GlyphSlot glyph = face.Glyph;
                FTBitmap bitmap = glyph.Bitmap;
                int texObj = GL.GenTexture();
                GL.BindTexture(TextureTarget.Texture2D, texObj);
                GL.TexImage2D(TextureTarget.Texture2D, 0,
                              PixelInternalFormat.R8, bitmap.Width, bitmap.Rows, 0,
                              PixelFormat.Red, PixelType.UnsignedByte, bitmap.Buffer);

                GL.TextureParameter(texObj, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
                GL.TextureParameter(texObj, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                GL.TextureParameter(texObj, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
                GL.TextureParameter(texObj, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);

                Character ch = new();
                ch.TextureID = texObj;
                ch.Size = new Vector2(bitmap.Width, bitmap.Rows);
                ch.Bearing = new Vector2(glyph.BitmapLeft, glyph.BitmapTop);
                ch.Advance = (int)glyph.Advance.X.Value;
                if (!_characters.ContainsKey(this)) _characters.Add(this, new Dictionary<uint, Character>());
                _characters[this].Add(c, ch);
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        GL.BindTexture(TextureTarget.Texture2D, 0);

        GL.PixelStore(PixelStoreParameter.UnpackAlignment, 4);

        float[] vquad =
        {
            0.0f, -1.0f,   0.0f, 0.0f,
            0.0f,  0.0f,   0.0f, 1.0f,
            1.0f,  0.0f,   1.0f, 1.0f,
            0.0f, -1.0f,   0.0f, 0.0f,
            1.0f,  0.0f,   1.0f, 1.0f,
            1.0f, -1.0f,   1.0f, 0.0f
        };

        VBO = GL.GenBuffer();
        GL.BindBuffer(BufferTarget.ArrayBuffer, VBO);
        GL.BufferData(BufferTarget.ArrayBuffer, 4 * 6 * 4, vquad, BufferUsageHint.StaticDraw);

        VAO = GL.GenVertexArray();
        GL.BindVertexArray(VAO);
        GL.EnableVertexAttribArray(0);
        GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 4 * 4, 0);
        GL.EnableVertexAttribArray(1);
        GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 4 * 4, 2 * 4);

        GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
        GL.BindVertexArray(0);
    }


    public bool Visible { get; set; } = true;

    public static readonly Dictionary<RenderText, Dictionary<uint, Character>> _characters = new();

    public int VAO { get; set; }
    public int VBO { get; set; }
    public float X { get; set; }
    public float Y { get; set; }
    public Vector2 DIR { get; set; }
    public string Text { get; set; }
    public uint PixelHeight { get; set; }
    public float Scale { get; set; }
    public Shader Shader { get; } = DefaultTextShader;
    private string Font { get; set; }

    private Vector4 Color { get; set; }

    public void Clean()
    {
        
    }

    private static float FloatToInt(float p, int Size, bool Invert = false)
    {
        double half = Math.Round((double)Size / (double)2, 15);
        if (p == 0) return (int)half;
        if (Invert)
        {
            if (p < 0)
            {
                p *= -1;
                p++;
                return float.Parse($"{(half * p)}");
            }
            else
            {
                return float.Parse($"{half - (p * half)}");
            }
        }
        else
        {
            if (p < 0)
            {
                p *= -1;
                p++;
                return float.Parse($"{Size - (half * p)}");
            }
            else
            {
                return float.Parse($"{p * half + half}");
            }
        }
    }

    public void Draw()
    {
        if (Visible)
        {
            Shader.Use();
            GL.Enable(EnableCap.Blend);
            GL.Uniform4(2, Color);
            _ = Matrix4.CreateScale(new Vector3(1f / 1, 1f / 1, 1.0f));
            Matrix4 projectionM = Matrix4.CreateOrthographicOffCenter(0.0f, 1, 1, 0.0f, -1.0f, 1.0f);
            GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
            GL.BlendFunc(0, BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
            GL.UniformMatrix4(1, false, ref projectionM);
            GL.ActiveTexture(TextureUnit.Texture0);
            GL.BindVertexArray(VAO);

            float angle_rad = (float)Math.Atan2(DIR.Y, DIR.X);
            Matrix4 rotateM = Matrix4.CreateRotationZ(angle_rad);
            Matrix4 transOriginM = Matrix4.CreateTranslation(new Vector3(X, Y, 0f));

            // Iterate through all characters
            float char_x = 0.0f;


            Library lib = new();

            Assembly assembly = Assembly.GetExecutingAssembly();
            Stream? resource_stream = assembly.GetManifestResourceStream($"Updater.Resource.{Font}.ttf");
            MemoryStream ms = new();
            resource_stream?.CopyTo(ms);
            Face face = new(lib, ms.ToArray(), 0);

            face.SetPixelSizes(0, PixelHeight);

            // set 1 byte pixel alignment 
            GL.PixelStore(PixelStoreParameter.UnpackAlignment, 1);

            // set texture unit
            GL.ActiveTexture(TextureUnit.Texture0);
            face.SelectCharmap(Encoding.Unicode);



            foreach (char c in Text)
            {
                if (_characters.ContainsKey(this) == false || _characters[this].ContainsKey(c) == false)
                {
                    var temp = ((ushort)c);

                    try
                    {
                        face.LoadChar(temp, LoadFlags.Render, LoadTarget.Normal);
                        GlyphSlot glyph = face.Glyph;
                        FTBitmap bitmap = glyph.Bitmap;

                        int texObj = GL.GenTexture();
                        GL.BindTexture(TextureTarget.Texture2D, texObj);
                        GL.TexImage2D(TextureTarget.Texture2D, 0,
                                      PixelInternalFormat.R8, bitmap.Width, bitmap.Rows, 0,
                                      PixelFormat.Red, PixelType.UnsignedByte, bitmap.Buffer);

                        // set texture parameters
                        GL.TextureParameter(texObj, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
                        GL.TextureParameter(texObj, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
                        GL.TextureParameter(texObj, TextureParameterName.TextureWrapS, (int)TextureWrapMode.ClampToEdge);
                        GL.TextureParameter(texObj, TextureParameterName.TextureWrapT, (int)TextureWrapMode.ClampToEdge);

                        // add character
                        Character cha = new();
                        cha.TextureID = texObj;
                        cha.Size = new Vector2(bitmap.Width, bitmap.Rows);
                        cha.Bearing = new Vector2(glyph.BitmapLeft, glyph.BitmapTop);
                        cha.Advance = (int)glyph.Advance.X.Value;
                        if (!_characters.ContainsKey(this)) _characters.Add(this, new Dictionary<uint, Character>());
                        _characters[this].Add(temp, cha);
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine(ex);
                    }
                }

                Character ch = _characters[this][c];

                float w = ch.Size.X * Scale;
                float h = ch.Size.Y * Scale;
                float xrel = char_x + ch.Bearing.X * Scale;
                float yrel = (ch.Size.Y - ch.Bearing.Y) * Scale;

                // Now advance cursors for next glyph (note that advance is number of 1/64 pixels)
                char_x += (ch.Advance >> 6) * Scale; // Bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th pixels by 64 to get amount of pixels))

                Matrix4 scaleM = Matrix4.CreateScale(new Vector3(w, h, 1.0f));
                Matrix4 transRelM = Matrix4.CreateTranslation(new Vector3(xrel, yrel, 0.0f));

                Matrix4 modelM = scaleM * transRelM * rotateM * transOriginM; // OpenTK `*`-operator is reversed
                GL.UniformMatrix4(0, false, ref modelM);

                // Render glyph texture over quad
                GL.BindTexture(TextureTarget.Texture2D, ch.TextureID);

                // Render quad
                GL.DrawArrays(PrimitiveType.Triangles, 0, 6);
            }

            GL.BindVertexArray(0);
            GL.BindTexture(TextureTarget.Texture2D, 0);
            GL.Disable(EnableCap.Blend);
        }
    }
}
