using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Quik.OpenGL
{
    public delegate IntPtr GetProcAddressProc(string procName);

    public unsafe static partial class GL
    {
        private delegate void GenObjectsProc(int count, int *ids);
        private delegate void BindObjectProc(int id);
        private delegate void BindSlottedProc(GLEnum target, int id);
        private delegate void GLEnum1Proc(GLEnum x);
        private delegate void GLEnum2Proc(GLEnum x, GLEnum y);
        private delegate void GLI4Proc(int x, int y, int z, int w);
        private delegate void GLF4Proc(float x, float y, float z, float w);
        private delegate void DrawElementsProc(GLEnum primitive, int size, GLEnum type, void *offset);
        private delegate void DrawArraysProc(GLEnum primitive, int first, int offset);
        private delegate void GetIntegervProc(GLEnum pname, int *data);
        private delegate void GetFloatvProc(GLEnum pname, float *data);
        private delegate byte* GetStringProc(GLEnum pname);

        private const short AggressiveInlining = (short)MethodImplOptions.AggressiveInlining;

        private static GetProcAddressProc _getProcAddress;
        private static GLEnum1Proc _enable;
        private static GLEnum1Proc _disable;
        private static GLEnum2Proc _blendFunc;
        private static GLEnum1Proc _depthFunc;
        private static GLEnum1Proc _clear;
        private static GLI4Proc _viewport;
        private static GLF4Proc _clearColor;
        private static DrawElementsProc _drawElements;
        private static DrawArraysProc _drawArrays;
        private static GetIntegervProc _getIntegerv;
        private static GetFloatvProc _getFloatv;
        private static GetStringProc _getString;

        private static T GetProcAddress<T>(string procName)
            where T : Delegate
        {
            IntPtr funcptr = _getProcAddress(procName);
            return Marshal.GetDelegateForFunctionPointer<T>(funcptr);
        }

        public static void LoadBindings(GetProcAddressProc getProcAddress)
        {
            _getProcAddress = getProcAddress;

            _enable = GetProcAddress<GLEnum1Proc>("glEnable");
            _disable = GetProcAddress<GLEnum1Proc>("glDisable");
            _blendFunc = GetProcAddress<GLEnum2Proc>("glBlendFunc");
            _depthFunc = GetProcAddress<GLEnum1Proc>("glDepthFunc");
            _clear = GetProcAddress<GLEnum1Proc>("glClear");
            _viewport = GetProcAddress<GLI4Proc>("glViewport");
            _clearColor = GetProcAddress<GLF4Proc>("glClearColor");
            _drawElements = GetProcAddress<DrawElementsProc>("glDrawElements");
            _drawArrays = GetProcAddress<DrawArraysProc>("glDrawArrays");
            _getIntegerv = GetProcAddress<GetIntegervProc>("glGetIntegerv");
            _getFloatv = GetProcAddress<GetFloatvProc>("glGetFloatv");
            _getString = GetProcAddress<GetStringProc>("glGetString");

            LoadBuffer();
            LoadProgram();
            LoadShader();
            LoadTexture();
            LoadUniform();
            LoadVertexArrays();
        }

        [MethodImpl(AggressiveInlining)]
        public static void Enable(GLEnum cap) => _enable(cap);
        [MethodImpl(AggressiveInlining)]
        public static void Disable(GLEnum cap) => _disable(cap);
        [MethodImpl(AggressiveInlining)]
        public static void BlendFunc(GLEnum src, GLEnum dst) => _blendFunc(src, dst);
        [MethodImpl(AggressiveInlining)]
        public static void DepthFunc(GLEnum func) => _depthFunc(func);
        [MethodImpl(AggressiveInlining)]
        public static void Clear(GLEnum buffer_bits) => _clear(buffer_bits);
        [MethodImpl(AggressiveInlining)]
        public static void Viewport(int x, int y, int w, int h) => _viewport(x, y, w, h);
        [MethodImpl(AggressiveInlining)]
        public static void ClearColor(float r, float g, float b, float a) => _clearColor(r, g, b, a);

        [MethodImpl(AggressiveInlining)]
        public static void DrawElements(GLEnum primitive, int count, GLEnum type, int offset) => _drawElements(primitive, count, type, (void*)offset);

        [MethodImpl(AggressiveInlining)]
        public static void DrawArrays(GLEnum primitive, int offset, int count) => _drawArrays(primitive, offset, count);

        [MethodImpl(AggressiveInlining)]
        public static void Get(GLEnum pname, out int value)
        {
            value = default;
            fixed(int* ptr = &value)
            {
                _getIntegerv(pname, ptr);
            }
        }

        [MethodImpl(AggressiveInlining)]
        public static void Get(GLEnum pname, out float value)
        {
            value = default;
            fixed (float* ptr = &value)
            {
                _getFloatv(pname, ptr);
            }
        }

        [MethodImpl(AggressiveInlining)]
        public static string GetString(GLEnum pname)
        {
            int length;
            byte* str = _getString(pname);

            for (length = 0; str[length] == 0 || length < 256; length++);

            return System.Text.Encoding.UTF8.GetString(str, length);
        }
    }
}