using System;
using System.Numerics;
using System.Text;
using System.Linq;
using ImGuiNET;
using OpenTK.Graphics.OpenGL;
using OpenTK.Windowing.GraphicsLibraryFramework;

namespace Pow3r
{
    internal sealed unsafe partial class Program
    {
        private int _glFontTexture;

        private void InitOpenGL()
        {
            if (GL.GetString(StringName.Extensions).Split(' ').Contains("GL_ARB_debug_output"))
                GL.Arb.DebugMessageCallback(GLDebugCallbackDelegate, (nint) 0x0105);

            GL.Enable(EnableCap.ScissorTest);
            GL.Enable(EnableCap.Blend);
            GL.BlendEquation(BlendEquationMode.FuncAdd);
            GL.BlendFuncSeparate(
                BlendingFactorSrc.SrcAlpha,
                BlendingFactorDest.OneMinusSrcAlpha,
                BlendingFactorSrc.One,
                BlendingFactorDest.OneMinusSrcAlpha);

            var io = ImGui.GetIO();

            io.Fonts.GetTexDataAsRGBA32(out byte* pixels, out var width, out var height, out _);

            _glFontTexture = GL.GenTexture();
            GL.BindTexture(TextureTarget.Texture2D, _glFontTexture);
            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, width, height, 0, PixelFormat.Bgra, PixelType.UnsignedByte, (nint) pixels);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int) TextureMagFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int) TextureMinFilter.Nearest);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int) TextureWrapMode.ClampToEdge);
            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int) TextureWrapMode.ClampToEdge);

            /*
            GL.TextureParameter(_fontTexture, TextureParameterName.TextureSwizzleR, 1);
            GL.TextureParameter(_fontTexture, TextureParameterName.TextureSwizzleG, 1);
            GL.TextureParameter(_fontTexture, TextureParameterName.TextureSwizzleB, 1);
            GL.TextureParameter(_fontTexture, TextureParameterName.TextureSwizzleA, (int) All.Red);*/

            io.Fonts.SetTexID((nint) _glFontTexture);
            io.Fonts.ClearTexData();
        }

        private void RenderOpenGL()
        {
            GLFW.GetFramebufferSize(_window.WindowPtr, out var fbW, out var fbH);
            GL.Viewport(0, 0, fbW, fbH);
            GL.Disable(EnableCap.ScissorTest);
            GL.ClearColor(0, 0, 0, 1);
            GL.Clear(ClearBufferMask.ColorBufferBit);
            GL.Enable(EnableCap.ScissorTest);
            GL.Enable(EnableCap.Texture2D);

            var drawData = ImGui.GetDrawData();

            var l = drawData.DisplayPos.X;
            var r = drawData.DisplayPos.X + drawData.DisplaySize.X;
            var t = drawData.DisplayPos.Y;
            var b = drawData.DisplayPos.Y + drawData.DisplaySize.Y;

            var matrix = Matrix4x4.CreateOrthographicOffCenter(l, r, b, t, -1, 1);

            GL.MatrixMode(MatrixMode.Projection);
            GL.LoadMatrix((float*) &matrix);

            var clipOff = drawData.DisplayPos;
            var clipScale = drawData.FramebufferScale;

            GL.EnableClientState(ArrayCap.VertexArray);
            GL.EnableClientState(ArrayCap.TextureCoordArray);
            GL.EnableClientState(ArrayCap.ColorArray);

            for (var n = 0; n < drawData.CmdListsCount; n++)
            {
                var drawList = drawData.CmdListsRange[n];

                for (var cmdI = 0; cmdI < drawList.CmdBuffer.Size; cmdI++)
                {
                    var cmd = drawList.CmdBuffer[cmdI];

                    GL.BindTexture(TextureTarget.Texture2D, (uint) cmd.TextureId);

                    Vector4 clipRect = default;
                    clipRect.X = (cmd.ClipRect.X - clipOff.X) * clipScale.X;
                    clipRect.Y = (cmd.ClipRect.Y - clipOff.Y) * clipScale.Y;
                    clipRect.Z = (cmd.ClipRect.Z - clipOff.X) * clipScale.X;
                    clipRect.W = (cmd.ClipRect.W - clipOff.Y) * clipScale.Y;

                    GL.Scissor((int) clipRect.X, (int) (fbH - clipRect.W), (int) (clipRect.Z - clipRect.X),
                        (int) (clipRect.W - clipRect.Y));

                    IntPtr adjustedVB = drawList.VtxBuffer.Data + (nint) (sizeof(ImDrawVert) * cmd.VtxOffset);

                    GL.VertexPointer(2, VertexPointerType.Float, sizeof(ImDrawVert), adjustedVB);
                    GL.TexCoordPointer(2, TexCoordPointerType.Float, sizeof(ImDrawVert), adjustedVB + 8);
                    GL.ColorPointer(4, ColorPointerType.UnsignedByte, sizeof(ImDrawVert), adjustedVB + 16);

                    GL.DrawElements(PrimitiveType.Triangles, (int) cmd.ElemCount,
                        DrawElementsType.UnsignedShort,
                        drawList.IdxBuffer.Data + (nint) (cmd.IdxOffset * 2));
                }
            }

            _window.SwapBuffers();
        }

        private static readonly DebugProcArb GLDebugCallbackDelegate = GLDebugCallback;

        private static void GLDebugCallback(DebugSource source, DebugType type, int id, DebugSeverity severity,
            int length, IntPtr message, IntPtr userParam)
        {
            var msg = Encoding.UTF8.GetString((byte*) message, length);

            if (severity == DebugSeverity.DebugSeverityNotification)
                return;

            Console.WriteLine($"[{type}][{severity}] {source}: {msg}");
        }
    }
}
