using System;
using System.IO;
using System.Reflection;
using Quik.OpenGL;
using Quik.VertexGenerator;
using Matrix4 = OpenTK.Mathematics.Matrix4;
using static Quik.OpenGL.GLEnum;

namespace Quik.OpenTK
{
    public class GL30Driver : IDisposable
    {
        public GL30Driver()
        {
            Assembly asm = typeof(GL30Driver).Assembly;

            using (StreamReader vert = new StreamReader(asm.GetManifestResourceStream("Quik.OpenTK.glsl.glsl130.vert")))
            using (StreamReader frag = new StreamReader(asm.GetManifestResourceStream("Quik.OpenTK.glsl.glsl130.frag")))
            {
                int vs;
                int fs;

                vs = GL.CreateShader(GL_VERTEX_SHADER);
                fs = GL.CreateShader(GL_FRAGMENT_SHADER);

                _sp = GL.CreateProgram();

                GL.ShaderSource(vs, vert.ReadToEnd());
                GL.ShaderSource(fs, frag.ReadToEnd());

                GL.CompileShader(vs);
                GL.CompileShader(fs);

            #if DEBUG
                int status;

                GL.GetShader(vs, GL_COMPILE_STATUS, out status);
                if (status == 0)
                {
                    throw new Exception(GL.GetShaderInfoLog(vs));
                }

                GL.GetShader(fs, GL_COMPILE_STATUS, out status);
                if (status == 0)
                {
                    throw new Exception(GL.GetShaderInfoLog(fs));
                }
            #endif

                GL.AttachShader(_sp, vs);
                GL.AttachShader(_sp, fs);

                GL.LinkProgram(_sp);

            #if DEBUG
                GL.GetProgram(_sp, GL_LINK_STATUS, out status);
                if (status == 0)
                {
                    throw new Exception(GL.GetProgramInfoLog(_sp));
                }
            #endif

                GL.DetachShader(_sp, vs);
                GL.DetachShader(_sp, fs);
                GL.DeleteShader(vs);
                GL.DeleteShader(fs);
            }

            LoadUniform(_nameM4View,                    out _locM4View);
            LoadUniform(_nameM4Model,                   out _locM4Model);
            LoadUniform(_nameIFlags,                    out _locIFlags);
            LoadUniform(_nameFSdfThreshold,             out _locFSdfThreshold);
            LoadUniform(_nameFSdfAuxilliaryThreshold,   out _locFSdfAuxilliaryThreshold);
            LoadUniform(_nameV4SdfAuxilliaryColor,      out _locV4SdfAuxilliaryColor);
            LoadUniform(_nameX2Texture,                 out _locX2Texture);
            LoadUniform(_nameV2TextureOffset,           out _locV2TextureOffset);

            LoadAttribute(_nameV2Postion,   out _locV2Position);
            LoadAttribute(_nameV2Texture,   out _locV2Texture);
            LoadAttribute(_nameV4Color,     out _locV4Color);

            void LoadUniform(string name, out int location)
            {
                location = GL.GetUniformLocation(_sp, name);
            }

            void LoadAttribute(string name, out int location)
            {
                location = GL.GetAttribLocation(_sp, name);
            }

            _vbo = GL.GenBuffer();
            _ebo = GL.GenBuffer();
            _vao = GL.GenVertexArray();
        }

        private readonly int _sp;
        private readonly int _vao;
        private readonly int _vbo;
        private readonly int _ebo;

        private const string _nameM4View = "m4View";
        private readonly int _locM4View;
        private const string _nameM4Model = "m4Model";
        private readonly int _locM4Model;
        private const string _nameV2Postion = "v2Position";
        private readonly int _locV2Position;
        private const string _nameV2Texture = "v2Texture";
        private readonly int _locV2Texture;
        private const string _nameV4Color = "v4Color";
        private readonly int _locV4Color;
        private const string _nameIFlags = "iFlags";
        private readonly int _locIFlags;
        private const string _nameFSdfThreshold = "fSdfThreshold";
        private readonly int _locFSdfThreshold;
        private const string _nameFSdfAuxilliaryThreshold = "fSdfAuxilliaryThreshold";
        private readonly int _locFSdfAuxilliaryThreshold;
        private const string _nameV4SdfAuxilliaryColor = "v4SdfAuxilliaryColor";
        private readonly int _locV4SdfAuxilliaryColor;
        private const string _nameX2Texture = "x2Texture";
        private readonly int _locX2Texture;
        private const string _nameV2TextureOffset = "v2TextureOffset";
        private readonly int _locV2TextureOffset;

        [Flags]
        private enum Flags : int
        {
            Texture = 1 << 0,
            DiscardEnable = 1 << 1,
            Sdf = 1 << 2,
            SdfAuxEnable = 1 << 3,
        }

        public void Draw(DrawQueue queue)
        {
            GL.UseProgram(_sp);
            GL.BindVertexArray(_vao);

            GL.BindBuffer(GL_ARRAY_BUFFER, _vbo);
            GL.BufferData(GL_ARRAY_BUFFER, queue.VertexCount * QuikVertex.Stride, queue.VertexArray, GL_STREAM_DRAW);
            GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _ebo);
            GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, queue.ElementCount * sizeof(int), queue.ElementArray, GL_STREAM_DRAW);

            GL.VertexAttribPointer(_locV2Position, 2, GL_FLOAT,         false, QuikVertex.Stride, QuikVertex.PositionOffset);
            GL.VertexAttribPointer(_locV2Texture,  2, GL_FLOAT,         false, QuikVertex.Stride, QuikVertex.TextureCoordinatesOffset);
            GL.VertexAttribPointer(_locV4Color,    4, GL_UNSIGNED_BYTE, false, QuikVertex.Stride, QuikVertex.ColorOffset);
            GL.EnableVertexAttribArray(_locV2Position);
            GL.EnableVertexAttribArray(_locV2Texture);
            GL.EnableVertexAttribArray(_locV4Color);

            GL.Enable(GL_BLEND);
            GL.BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

            Matrix4 m = Matrix4.Identity;
            GL.UniformMatrix4(_locM4Model, false, ref m.Row0.X);

            foreach (DrawCall call in queue)
            {
                GL.BindTexture(GL_TEXTURE_2D, 0);
                m = Matrix4.CreateOrthographicOffCenter(0, call.Bounds.Right, 0, call.Bounds.Top, 1.0f, -1.0f);
                GL.UniformMatrix4(_locM4View, false, ref m.Row0.X);
                GL.DrawElements(GL_TRIANGLES, call.Count, GL_UNSIGNED_INT, call.Start);
            }
        }

        private bool _isDisposed = false;
        private void Dispose(bool disposing)
        {
            if (_isDisposed) return;

            if (disposing)
            {
                GL.DeleteProgram(_sp);
            }
            else
            {
                throw new Exception("OpenGL resource is leaked. Dispose unreferenced OpenGL objects in the context thread.");
            }

            _isDisposed = true;
        }

        public void Dispose() => Dispose(true);
        ~GL30Driver()
        {
            Dispose(false);
        }
    }
}