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

namespace Quik.OpenGL
{
    public unsafe static partial class GL
    {
        private delegate int CreateShaderProc(GLEnum type);
        private delegate void ShaderSourceProc(int shader, int count, byte** strings, int* length);
        private delegate void CompileShaderProc(int shader);
        private delegate void GetShaderProc(int shader, GLEnum pname, int* value);
        private delegate void GetShaderInfoLogProc(int shader, int maxLength, int* length, byte* infoLog);
        private delegate void DeleteShaderProc(int id);

        private static CreateShaderProc _createShader;
        private static ShaderSourceProc _shaderSource;
        private static CompileShaderProc _compileShader;
        private static GetShaderProc _getShader;
        private static GetShaderInfoLogProc _getShaderInfoLog;
        private static DeleteShaderProc _deleteShader;

        private static void LoadShader()
        {
            _createShader = GetProcAddress<CreateShaderProc>("glCreateShader");
            _shaderSource = GetProcAddress<ShaderSourceProc>("glShaderSource");
            _compileShader = GetProcAddress<CompileShaderProc>("glCompileShader");
            _getShader = GetProcAddress<GetShaderProc>("glGetShaderiv");
            _getShaderInfoLog = GetProcAddress<GetShaderInfoLogProc>("glGetShaderInfoLog");
            _deleteShader = GetProcAddress<DeleteShaderProc>("glDeleteShader");
        }

        [MethodImpl(AggressiveInlining)]
        public static int CreateShader(GLEnum type) => _createShader(type);

        [MethodImpl(AggressiveInlining)]
        public static void ShaderSource(int shader, string source)
        {
            byte[] sourceUTF8 = Encoding.UTF8.GetBytes(source);
            int length = sourceUTF8.Length;

            fixed (byte* ptr = &sourceUTF8[0])
            {
                _shaderSource(shader, 1, &ptr, &length);
            }
        }

        [MethodImpl(AggressiveInlining)]
        public static void ShaderSource(int shader, string[] sources)
        {
            int     count = sources.Length;
            byte*[] pointers = new byte*[count];
            int[]   lengths = new int[count];

            for (int i = 0; i < count; i++)
            {
                byte[] decoded = Encoding.UTF8.GetBytes(sources[i]);
                int    length  = lengths[i] = decoded.Length;
                IntPtr memory  = Marshal.AllocHGlobal(decoded.Length);

                Marshal.Copy(decoded, 0, memory, length);
                pointers[i] = (byte*)memory;
            }

            try
            {
                fixed (byte** ptr = &pointers[0])
                fixed (int *  len = &lengths[0])
                {
                    _shaderSource(shader, count, ptr, len);
                }
            }
            finally
            {
                for (int i = 0; i < count; i++)
                {
                    Marshal.FreeHGlobal((IntPtr)pointers[i]);
                }
            }
        }

        [MethodImpl(AggressiveInlining)]
        public static void CompileShader(int shader) => _compileShader(shader);

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

        [MethodImpl(AggressiveInlining)]
        public static string GetShaderInfoLog(int shader)
        {
            GetShader(shader, GL_INFO_LOG_LENGTH, out int length);
            byte[] infoLog = new byte[length];

            fixed (byte *ptr = infoLog)
                _getShaderInfoLog(shader, length, &length, ptr);

            return Encoding.UTF8.GetString(infoLog);
        }

        [MethodImpl(AggressiveInlining)]
        public static void DeleteShader(int shader) => _deleteShader(shader);
    }
}