using OpenTK.Graphics.OpenGL4;
using OpenTK.Mathematics;
using SharpFont;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;

namespace Updater
{
    internal class RenderObject : IRenderObject
    {
        public static Shader DefaultShader = new(
@"
#version 330 core
layout (location = 0) in vec3 aPos;
uniform vec4 objColor;
out vec4 vertexColor;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    vertexColor = objColor;
}
",
@"
#version 330 core
out vec4 FragColor;
  
in vec4 vertexColor;

void main()
{
    FragColor = vertexColor;
} 

");

        public RenderObject(float[] Points, uint[] Indexs, Vector4 Color, BufferUsageHint Hint = BufferUsageHint.StaticDraw, Image<Rgba32>? image = null, bool Invert = true)
        {
            BufferObject = GL.GenBuffer();
            GL.BindBuffer(BufferTarget.ArrayBuffer, BufferObject);
            ArrayObject = GL.GenVertexArray();
            GL.BindVertexArray(ArrayObject);
            int add = 3;
            if (image is not null) add = 5;
            GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, add * sizeof(float), 0);
            if (image is not null)
            {
                img = true;
                if (Invert) image.Mutate(x => x.Flip(FlipMode.Vertical));
                var pixels = new List<byte>(4 * image.Width * image.Height);

                for (int y = 0; y < image.Height; y++)
                {
                    Span<Rgba32> row = new Span<Rgba32>();

                    for (int x = 0; x < image.Width; x++)
                    {
                        pixels.Add(row[x].R);
                        pixels.Add(row[x].G);
                        pixels.Add(row[x].B);
                        pixels.Add(row[x].A);
                    }
                }
                Shader = new Shader(
                    @"
            #version 330
            layout(location = 0) in vec3 aPosition;
            layout(location = 1) in vec2 aTexCoord;
            out vec2 texCoord;
            void main(void)
            {
                texCoord = aTexCoord;
                gl_Position = vec4(aPosition, 1.0);
            }",
                    @"
            #version 330
            out vec4 outputColor;
            in vec2 texCoord;
            uniform sampler2D texture0;
            void main(void)
            {
                outputColor = texture(texture0, texCoord);
            }
            "
                    );
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
                GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
                GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, image.Width, image.Height, 0, PixelFormat.Rgba, PixelType.UnsignedByte, pixels.ToArray());
                int texCoordLocation = Shader.GetAttribLocation("aTexCoord");
                GL.EnableVertexAttribArray(texCoordLocation);
                GL.VertexAttribPointer(texCoordLocation, 2, VertexAttribPointerType.Float, false, 5 * sizeof(float), 3 * sizeof(float));


            }
            GL.EnableVertexAttribArray(0);
            GL.BindBuffer(BufferTarget.ArrayBuffer, BufferObject);
            GL.BufferData(BufferTarget.ArrayBuffer, Points.Length * sizeof(float), Points, Hint);
            GL.BindVertexArray(ArrayObject);
            ElementBufferObject = GL.GenBuffer();
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, ElementBufferObject);
            GL.BufferData(BufferTarget.ElementArrayBuffer, Indexs.Length * sizeof(uint), Indexs, Hint);
            this.Points_ = Points;
            this.Indexs = Indexs;
            this.Hint = Hint;
            this.Color = Color;
        }

        public Vector4 Color { get; set; }

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

        public void Draw()
        {
            if (Visible)
            {
                Shader.Use();
                GL.Uniform4(0, Color);
                if (img)
                {
                    GL.Enable(EnableCap.Blend);
                    GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
                    GL.BlendFunc(0, BlendingFactorSrc.SrcAlpha, BlendingFactorDest.OneMinusSrcAlpha);
                }
                GL.BindVertexArray(ArrayObject);
                GL.DrawElements(PrimitiveType.Triangles, Indexs.Length, DrawElementsType.UnsignedInt, 0);
                if (img) GL.Disable(EnableCap.Blend);
            }
        }

        public void Clean()
        {
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.DeleteBuffer(BufferObject);
        }

        ~RenderObject()
        {
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            GL.DeleteBuffer(BufferObject);
        }

        readonly bool img = false;

        public Shader Shader { get; set; } = DefaultShader;
        public int ElementBufferObject { get; }
        public int BufferObject { get; }
        public int ArrayObject { get; }
        private float[] Points_;
        public float[] Points 
        {
            get
            {
                return Points_;
            } 
            set 
            {
                Points_ = value;
                try
                {
                    int add = 3;
                    if (img) add = 5;
                    GL.BindBuffer(BufferTarget.ArrayBuffer, BufferObject);
                    GL.BindVertexArray(ArrayObject);
                    GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, add * sizeof(float), 0);
                    GL.EnableVertexAttribArray(0);
                    GL.BindBuffer(BufferTarget.ArrayBuffer, BufferObject);
                    GL.BufferData(BufferTarget.ArrayBuffer, Points_.Length * sizeof(float), Points_, Hint);
                    GL.BindVertexArray(ArrayObject);
                    GL.BindBuffer(BufferTarget.ElementArrayBuffer, ElementBufferObject);
                    GL.BufferData(BufferTarget.ElementArrayBuffer, Indexs.Length * sizeof(uint), Indexs, Hint);
                }
                catch (AccessViolationException v)
                {
                    Console.WriteLine(v.Message);
                }
            } 
        }

        public uint[] Indexs { get; set; }
        public BufferUsageHint Hint { get; set; } = BufferUsageHint.StaticDraw;
    }
}
