#ifndef GL_H
#define GL_H

namespace GL {
    typedef unsigned int Shader;
    typedef unsigned int Program;
    typedef unsigned int ShaderType;
    typedef unsigned int Texture;
    typedef unsigned int Framebuffer;
    typedef unsigned int ColorAttachment;
    typedef unsigned int VertexArray;
    typedef unsigned int Buffer;

    extern ShaderType VERTEX_SHADER;
    extern ShaderType FRAGMENT_SHADER;
    extern ShaderType GEOMETRY_SHADER;

    class Attribute final {
        int count;
        int size;
        int type;
        bool normalized;

        Attribute(int count, int size, int type, bool normalized);

        friend void vertexAttribPointer(int index, const Attribute& a,
                                        int stride, int offset);

    public:
        bool isDummy() const;
        int getSize() const;

        static Attribute newFloat(int count);
        static Attribute newColor(int count);
        static Attribute newDummy();
    };

    class TextureFormat final {
        int internalformat;
        int format;
        int type;

        TextureFormat(int internalformat, int format, int type);

        friend void texImage2D(const TextureFormat& format, int width,
                               int height, const void* data, int level);

    public:
        static TextureFormat color8(int channels);
        static TextureFormat float16(int channels);
        static TextureFormat float32(int channels);
        static TextureFormat depth16();
        static TextureFormat depth32();
        static TextureFormat unknown();
    };

    bool printError(const char* message);
    void enableDepthTesting();
    void disableDepthTesting();
    void bindMainFramebuffer();
    void clear();
    void enableBlending();
    void disableBlending();
    void setViewport(int width, int height);
    void vertexAttribPointer(int index, const Attribute& a, int stride,
                             int offset);
    Program createProgram();
    void attachShader(Program p, Shader s);
    void linkProgram(Program p);
    bool logLinkerError(Program p);
    void deleteShader(Shader s);
    void deleteProgram(Program p);
    Shader createShader(ShaderType type);
    void compileShader(Shader s, const char* code);
    bool logCompileError(Shader s);
    void useProgram(Program p);
    void setMatrix(Program p, const char* name, const float* data);
    void setInt(Program p, const char* name, int data);
    void setFloat(Program p, const char* name, float data);
    void set2Float(Program p, const char* name, const float* data);
    void set3Float(Program p, const char* name, const float* data);
    void set4Float(Program p, const char* name, const float* data);
    void texImage2D(const TextureFormat& format, int width, int height,
                    const void* data, int level = 0);
    Texture genTexture();
    void deleteTexture(Texture t);
    void setNearFilter2D();
    void setMipMapNearFilter2D();
    void setLinearFilter2D();
    void setMipMapLinearFilter2D();
    void setClampWrap2D();
    void setRepeatWrap2D();
    void setMipMapLinearFilter2D();
    void bindTexture2D(Texture t);
    void activeTexture(int index);
    void generateMipmap2D(int maxLevels);
    void deleteFramebuffers(Framebuffer fb);
    Framebuffer genFramebuffer();
    void bindFramebuffer(Framebuffer fb);
    void framebufferDepthTexture2D(Texture t);
    ColorAttachment framebufferColorTexture2D(Texture t, int index);
    void drawBuffers(int length, ColorAttachment* c);
    bool printFramebufferError();
    VertexArray genVertexArray();
    Buffer genBuffer();
    void deleteBuffer(Buffer b);
    void deleteVertexArray(VertexArray va);
    void bindVertexArray(VertexArray va);
    void bindBuffer(Buffer b);
    void bufferDataStatic(int size, const void* data);
    void bufferDataStream(int size, const void* data);
    void bufferDataDynamic(int size, const void* data);
    void bufferSubData(int offset, int size, const void* data);
    void drawTriangles(int offset, int vertices);
    void drawTriangleStrip(int offset, int vertices);
    void drawPoints(int offset, int vertices);
}

#endif