using System.Runtime.CompilerServices;
using System.Text;
using static Quik.OpenGL.GLEnum;

namespace Quik.OpenGL
{
    public unsafe static partial class GL
    {
        private delegate int CreateProgramProc();
        private delegate void UseProgramProc(int program);
        private delegate void AttachShaderProc(int program, int shader);
        private delegate void DetachShaderProc(int program, int shader);
        private delegate void LinkProgramProc(int program);
        private delegate void GetProgramProc(int program, GLEnum pname, int *value);
        private delegate void GetProgramInfoLogProc(int program, int maxLength, int * length, byte *infoLog);
        private delegate void DeleteProgramProc(int program);
        private delegate int GetShaderLocationProc(int program, byte *name);

        private static CreateProgramProc _createProgram;
        private static UseProgramProc _useProgram;
        private static AttachShaderProc _attachShader;
        private static DetachShaderProc _detachShader;
        private static LinkProgramProc _linkProgram;
        private static GetProgramProc _getProgram;
        private static GetProgramInfoLogProc _getProgramInfoLog;
        private static DeleteProgramProc _deleteProgram;
        private static GetShaderLocationProc _getUniformLocation;
        private static GetShaderLocationProc _getAttribLocation;

        private static void LoadProgram()
        {
            _createProgram = GetProcAddress<CreateProgramProc>("glCreateProgram");
            _useProgram    = GetProcAddress<UseProgramProc>("glUseProgram");
            _attachShader  = GetProcAddress<AttachShaderProc>("glAttachShader");
            _detachShader  = GetProcAddress<DetachShaderProc>("glDetachShader");
            _linkProgram   = GetProcAddress<LinkProgramProc>("glLinkProgram");
            _getProgram    = GetProcAddress<GetProgramProc>("glGetProgramiv");
            _getProgramInfoLog = GetProcAddress<GetProgramInfoLogProc>("glGetProgramInfoLog");
            _deleteProgram = GetProcAddress<DeleteProgramProc>("glDeleteProgram");
            _getUniformLocation = GetProcAddress<GetShaderLocationProc>("glGetUniformLocation");
            _getAttribLocation = GetProcAddress<GetShaderLocationProc>("glGetAttribLocation");
        }

        [MethodImpl(AggressiveInlining)]
        public static int CreateProgram() => _createProgram();

        [MethodImpl(AggressiveInlining)]
        public static void UseProgram(int program) => _useProgram(program);

        [MethodImpl(AggressiveInlining)]
        public static void AttachShader(int program, int shader) => _attachShader(program, shader);

        [MethodImpl(AggressiveInlining)]
        public static void DetachShader(int program, int shader) => _detachShader(program, shader);

        [MethodImpl(AggressiveInlining)]
        public static void LinkProgram(int program) => _linkProgram(program);

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

        [MethodImpl(AggressiveInlining)]
        public static string GetProgramInfoLog(int program)
        {
            GetProgram(program, GL_INFO_LOG_LENGTH, out int length);
            byte[] infoLog = new byte[length];

            fixed (byte *ptr = infoLog)
                _getProgramInfoLog(program, length, &length, ptr);

            return Encoding.UTF8.GetString(infoLog);
        }

        [MethodImpl(AggressiveInlining)]
        public static void DeleteProgram(int program) => _deleteProgram(program);

        [MethodImpl(AggressiveInlining)]
        public static int GetUniformLocation(int program, string name)
        {
            fixed(byte* ptr = Encoding.UTF8.GetBytes(name))
                return _getUniformLocation(program, ptr);
        }

        [MethodImpl(AggressiveInlining)]
        public static int GetAttribLocation(int program, string name)
        {
            fixed(byte* ptr = Encoding.UTF8.GetBytes(name))
                return _getAttribLocation(program, ptr);
        }
    }
}