using System;
using System.Runtime.CompilerServices;

namespace Quik.OpenGL
{
    public unsafe static partial class GL
    {
        private delegate void ActiveTextureProc(GLEnum unit);
        private delegate void PixelStoreiProc(GLEnum pname, int param);
        private delegate void PixelStorefProc(GLEnum pname, float param);
        private delegate void TexImage2DProc(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, void *data);
        private delegate void TexImage3DProc(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data);
        private delegate void TexSubImage2DProc(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, void *data);
        private delegate void TexSubImage3DProc(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data);
        private delegate void TexParameteriProc(GLEnum target, GLEnum pname, int value);
        private delegate void TexParameterfProc(GLEnum target, GLEnum pname, float value);
        private delegate void TexParameterivProc(GLEnum target, GLEnum pname, int* value);
        private delegate void TexParameterfvProc(GLEnum target, GLEnum pname, float* value);
        private delegate void GenerateMipmapProc(GLEnum target);

        private static GenObjectsProc _genTextures;
        private static GenObjectsProc _deleteTextures;
        private static BindSlottedProc _bindTexture;
        private static ActiveTextureProc _activeTexture;
        private static PixelStoreiProc _pixelStorei;
        private static PixelStorefProc _pixelStoref;
        private static TexImage2DProc _texImage2D;
        private static TexImage3DProc _texImage3D;
        private static TexSubImage2DProc _texSubImage2D;
        private static TexSubImage3DProc _texSubImage3D;
        private static TexParameteriProc _texParameteri;
        private static TexParameterfProc _texParameterf;
        private static TexParameterivProc _texParameteriv;
        private static TexParameterfvProc _texParameterfv;
        private static GenerateMipmapProc _generateMipmap;

        private static void LoadTexture()
        {
            _genTextures    = GetProcAddress<GenObjectsProc>("glGenTextures");
            _deleteTextures = GetProcAddress<GenObjectsProc>("glDeleteTextures");
            _bindTexture    = GetProcAddress<BindSlottedProc>("glBindTexture");
            _activeTexture  = GetProcAddress<ActiveTextureProc>("glActiveTexture");
            _pixelStorei    = GetProcAddress<PixelStoreiProc>("glPixelStorei");
            _pixelStoref    = GetProcAddress<PixelStorefProc>("glPixelStoref");
            _texImage2D     = GetProcAddress<TexImage2DProc>("glTexImage2D");
            _texImage3D     = GetProcAddress<TexImage3DProc>("glTexImage3D");
            _texSubImage2D  = GetProcAddress<TexSubImage2DProc>("glTexSubImage2D");
            _texSubImage3D  = GetProcAddress<TexSubImage3DProc>("glTexSubImage3D");
            _texParameteri  = GetProcAddress<TexParameteriProc>("glTexParameteri");
            _texParameterf  = GetProcAddress<TexParameterfProc>("glTexParameterf");
            _texParameteriv = GetProcAddress<TexParameterivProc>("glTexParameteriv");
            _texParameterfv = GetProcAddress<TexParameterfvProc>("glTexParameterfv");
            _generateMipmap = GetProcAddress<GenerateMipmapProc>("glGenerateMipmap");
        }

        [MethodImpl(AggressiveInlining)]
        public static void GenTextures(int count, out int textures)
        {
            fixed (int *ptr = &textures)
                _genTextures(count, ptr);
        }
        [MethodImpl(AggressiveInlining)]
        public static int GenTexture()
        {
            GenTextures(1, out int i);
            return i;
        }
        [MethodImpl(AggressiveInlining)]
        public static void GenTextures(int[] textures) => GenTextures(textures.Length, out textures[0]);

        [MethodImpl(AggressiveInlining)]
        public static void DeleteTextures(int count, in int textures)
        {
            fixed (int* ptr = &textures)
                _deleteTextures(count, ptr);
        }

        [MethodImpl(AggressiveInlining)]
        public static void DeleteTexture(int i) => DeleteTextures(1, i);

        [MethodImpl(AggressiveInlining)]
        public static void DeleteTextures(int[] textures) => DeleteTextures(textures.Length, in textures[0]);

        [MethodImpl(AggressiveInlining)]
        public static void BindTexture(GLEnum target, int texture) => _bindTexture(target, texture);

        [MethodImpl(AggressiveInlining)]
        public static void ActiveTexture(GLEnum unit) => _activeTexture(unit);

        [MethodImpl(AggressiveInlining)]
        public static void PixelStore(GLEnum pname, int value) => _pixelStorei(pname, value);

        [MethodImpl(AggressiveInlining)]
        public static void PixelStore(GLEnum pname, float value) => _pixelStoref(pname, value);

        [MethodImpl(AggressiveInlining)]
        public static void TexImage2D(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, IntPtr data) =>
            _texImage2D(target, level, internalFormat, width, height, border, format, pixelType, (void*)data);

        [MethodImpl(AggressiveInlining)]
        public static void TexImage2D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, in T data) where T : unmanaged
        {
            fixed(T *ptr = &data)
                _texImage2D(target, level, internalFormat, width, height, border, format, pixelType, ptr);
        }

        [MethodImpl(AggressiveInlining)]
        public static void TexImage2D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int border, GLEnum format, GLEnum pixelType, T[] data) where T : unmanaged =>
            TexImage2D(target, level, internalFormat, width, height, border, format, pixelType, in data[0]);

        [MethodImpl(AggressiveInlining)]
        public static void TexImage3D(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data) =>
            _texImage3D(target, level, internalFormat, width, height, depth, border, format, pixelType, data);

        [MethodImpl(AggressiveInlining)]
        public static void TexImage3D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, in T data)
            where T : unmanaged
        {
            fixed (T* ptr = &data)
                _texImage3D(target, level, internalFormat, width, height, depth, border, format, pixelType, ptr);
        }

        [MethodImpl(AggressiveInlining)]
        public static void TexImage3D<T>(GLEnum target, int level, GLEnum internalFormat, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, T[] data)
            where T : unmanaged =>
                TexImage3D(target, level, internalFormat, width, height, depth, border, format, pixelType, in data[0]);

        [MethodImpl(AggressiveInlining)]
        public static void TexSubImage2D(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, IntPtr data) =>
            _texSubImage2D(target, level, x, y, width, height,format, pixelType, (void*)data);

        [MethodImpl(AggressiveInlining)]
        public static void TexSubImage2d<T>(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, in T data) where T : unmanaged
        {
            fixed(T *ptr = &data)
                _texSubImage2D(target, level, x, y, width, height, format, pixelType, ptr);
        }

        [MethodImpl(AggressiveInlining)]
        public static void TexSubImage2D<T>(GLEnum target, int level, int x, int y, int width, int height, GLEnum format, GLEnum pixelType, T[] data) where T : unmanaged =>
            TexSubImage2d<T>(target, level, x, y, width, height, format, pixelType, in data[0]);

        [MethodImpl(AggressiveInlining)]
        public static void TexSubImage3D(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, void* data) =>
            _texSubImage3D(target, level, x, y, z, width, height, depth, border, format, pixelType, data);

        [MethodImpl(AggressiveInlining)]
        public static void TexSubImage3D<T>(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, in T data)
            where T : unmanaged
        {
            fixed (T* ptr = &data)
                _texSubImage3D(target, level, x, y, z, width, height, depth, border, format, pixelType, ptr);
        }

        [MethodImpl(AggressiveInlining)]
        public static void TexSubImage3D<T>(GLEnum target, int level, int x, int y, int z, int width, int height, int depth, int border, GLEnum format, GLEnum pixelType, T[] data)
            where T : unmanaged =>
                TexSubImage3D(target, level, x, y, z, width, height, depth, border, format, pixelType, in data[0]);

        [MethodImpl(AggressiveInlining)]
        public static void TexParameter(GLEnum target, GLEnum pname, int value) => _texParameteri(target, pname, value);
        [MethodImpl(AggressiveInlining)]
        public static void TexParameter(GLEnum target, GLEnum pname, GLEnum value) => _texParameteri(target, pname, (int)value);
        [MethodImpl(AggressiveInlining)]
        public static void TexParameter(GLEnum target, GLEnum pname, float value) => _texParameterf(target, pname, value);
        [MethodImpl(AggressiveInlining)]
        public static void TexParameter(GLEnum target, GLEnum pname, ref int values)
        {
            fixed (int *ptr = &values)
                _texParameteriv(target, pname, ptr);
        }
        [MethodImpl(AggressiveInlining)]
        public static void TexParameter(GLEnum target, GLEnum pname, int[] values) => TexParameter(target, pname, ref values[0]);

        [MethodImpl(AggressiveInlining)]
        public static void TexParameter(GLEnum target, GLEnum pname, ref float values)
        {
            fixed (float *ptr = &values)
                _texParameterfv(target, pname, ptr);
        }
        [MethodImpl(AggressiveInlining)]
        public static void TexParameter(GLEnum target, GLEnum pname, float[] values) => TexParameter(target, pname, ref values[0]);

        [MethodImpl(AggressiveInlining)]
        public static void GenerateMipmap(GLEnum target) => _generateMipmap(target);
    }
}