/// Guards

#ifndef GLTRAITS_HPP_
#define GLTRAITS_HPP_


/// Includes

#include <cstddef>

#include <glbase.hpp>


/// GLTraits

class GLTraits
{

public:

    //// Value

    template<typename>
    struct Value;

};

//// Helpers

#define GLTRAITS_KEEP(...) __VA_ARGS__
#define GLTRAITS_OMIT(...)

//// Value

#define GLTRAITS_VALUE_SRED  GL_SR8_EXT
#define GLTRAITS_VALUE_SRG   GL_SRG8_EXT
#define GLTRAITS_VALUE_SRGB  GL_SRGB8
#define GLTRAITS_VALUE_SRGBA GL_SRGB8_ALPHA8

#define GLTRAITS_VALUE_COMPRESSED_SRED  GL_COMPRESSED_SRGB
#define GLTRAITS_VALUE_COMPRESSED_SRG   GL_COMPRESSED_SRGB
#define GLTRAITS_VALUE_COMPRESSED_SRGB  GL_COMPRESSED_SRGB
#define GLTRAITS_VALUE_COMPRESSED_SRGBA GL_COMPRESSED_SRGB_ALPHA

#define GLTRAITS_VALUE( \
    VALUE, \
    COLUMNS, ROWS, \
    GLSL, FORMAT, TYPE, INTERNAL_FORMAT, \
    OFFSET_ARGS, PTR, \
    V, UNIFORM, SUFFIX, ATTRIB, \
    INTEGER, \
    NORMALIZE_ARGS, \
    V1U, V2U, \
    V1A, V2A, \
    EXTU, EXTA, \
    ... \
) \
    template<> \
    struct GLTraits::Value<VALUE> \
    { \
        auto static constexpr name                            = #VALUE; \
        auto static constexpr columns                         = GLint{COLUMNS}; \
        auto static constexpr rows                            = GLint{ROWS}; \
        auto static constexpr glsl                            = GLenum{GL_##GLSL}; \
        auto static constexpr format                          = GLenum{GL_##FORMAT##INTEGER}; \
        auto static constexpr type                            = GLenum{GL_##TYPE}; \
        auto static constexpr internal_format                 = GLenum{GL_##INTERNAL_FORMAT}; \
        auto static constexpr internal_format_srgb            = GLenum{GLTRAITS_VALUE_S##FORMAT}; \
        auto static constexpr internal_format_compressed      = GLenum{GL_COMPRESSED_##FORMAT}; \
        auto static constexpr internal_format_compressed_srgb = GLenum{GLTRAITS_VALUE_COMPRESSED_S##FORMAT}; \
        auto static constexpr integer                         = bool(*#INTEGER); \
        void static uniform(GLint location, VALUE const & value) \
        { \
            if (GLBase::debug() >= 1) \
                GLBase::check_supported({V1U, V2U}, EXTU); \
            glUniform##UNIFORM##ROWS##SUFFIX##V( \
                location, \
                __VA_ARGS__ \
                PTR(value) \
            ); \
        } \
        void static vertex_attrib(GLint location, VALUE const & value) \
        { \
            if (GLBase::debug() >= 1) \
                GLBase::check_supported({V1A, V2A}, EXTA); \
            if (location == -1) \
                return; \
            for (auto column = GLuint{0}; column < columns; ++column) \
                glVertexAttrib##ATTRIB##ROWS##SUFFIX##V( \
                    (GLuint)location + column, \
                    PTR(value) GLTRAITS_##OFFSET_ARGS(+ rows * column) \
                ); \
        } \
        void static vertex_attrib_pointer( \
            GLint       location, \
            std::size_t offset = 0, \
            std::size_t stride = sizeof(VALUE) \
        ) \
        { \
            if (GLBase::debug() >= 1) \
                GLBase::check_supported({V1A, V2A}, EXTA); \
            if (location == -1) \
                return; \
            auto constexpr sizeof_column = sizeof(VALUE) / columns; \
            for (auto column = GLuint{0}; column < columns; ++column) \
                glVertexAttrib##ATTRIB##Pointer( \
                    (GLuint)location + column, \
                    rows, \
                    type, \
                    GLTRAITS_##NORMALIZE_ARGS(GL_FALSE,) \
                    (GLsizei)stride, \
                    (void const *)(offset + sizeof_column * column) \
                ); \
        } \
    };

#define GLTRAITS_VALUE_SCALAR(                  VALUE, TYPE, GLSL,   INTERNAL, ...) GLTRAITS_VALUE(VALUE,          1, 1, GLSL,                RED,    TYPE, R     ##INTERNAL, OMIT, ,    ,  ,             __VA_ARGS__,)
#define GLTRAITS_VALUE_VECTOR_N(  N,    FORMAT, VALUE, TYPE, PTR,    INTERNAL, ...) GLTRAITS_VALUE(VALUE##N,       1, N, TYPE##_VEC##N,       FORMAT, TYPE, FORMAT##INTERNAL, KEEP, PTR, v, ,             __VA_ARGS__, 1,)
#define GLTRAITS_VALUE_MATRIX_N(  N,    FORMAT, VALUE, TYPE, PTR, T, INTERNAL, ...) GLTRAITS_VALUE(VALUE##N,       N, N, TYPE##_MAT##N,       FORMAT, TYPE, FORMAT##INTERNAL, KEEP, PTR, v, Matrix,       __VA_ARGS__, 1, T,)
#define GLTRAITS_VALUE_MATRIX_N_M(N, M, FORMAT, VALUE, TYPE, PTR, T, INTERNAL, ...) GLTRAITS_VALUE(VALUE##N##x##M, N, M, TYPE##_MAT##N##x##M, FORMAT, TYPE, FORMAT##INTERNAL, KEEP, PTR, v, Matrix##N##x, __VA_ARGS__, 1, T,)

#define GLTRAITS_VALUE_VECTOR(...) \
    GLTRAITS_VALUE_VECTOR_N(  2,    RG,   __VA_ARGS__) \
    GLTRAITS_VALUE_VECTOR_N(  3,    RGB,  __VA_ARGS__) \
    GLTRAITS_VALUE_VECTOR_N(  4,    RGBA, __VA_ARGS__)

#define GLTRAITS_VALUE_MATRIX_(SUPPORT_ARGS, ...) \
    GLTRAITS_VALUE_MATRIX_N(  2,    RG,   __VA_ARGS__ GLTRAITS_##SUPPORT_ARGS(, 2, 0, 2, 0, {}, {})) \
    GLTRAITS_VALUE_MATRIX_N_M(2, 3, RGB,  __VA_ARGS__ GLTRAITS_##SUPPORT_ARGS(, 2, 1, 2, 0, {}, {})) \
    GLTRAITS_VALUE_MATRIX_N_M(2, 4, RGBA, __VA_ARGS__ GLTRAITS_##SUPPORT_ARGS(, 2, 1, 2, 0, {}, {})) \
    GLTRAITS_VALUE_MATRIX_N_M(3, 2, RG,   __VA_ARGS__ GLTRAITS_##SUPPORT_ARGS(, 2, 1, 2, 0, {}, {})) \
    GLTRAITS_VALUE_MATRIX_N(  3,    RGB,  __VA_ARGS__ GLTRAITS_##SUPPORT_ARGS(, 2, 0, 2, 0, {}, {})) \
    GLTRAITS_VALUE_MATRIX_N_M(3, 4, RGBA, __VA_ARGS__ GLTRAITS_##SUPPORT_ARGS(, 2, 1, 2, 0, {}, {})) \
    GLTRAITS_VALUE_MATRIX_N_M(4, 2, RG,   __VA_ARGS__ GLTRAITS_##SUPPORT_ARGS(, 2, 1, 2, 0, {}, {})) \
    GLTRAITS_VALUE_MATRIX_N_M(4, 3, RGB,  __VA_ARGS__ GLTRAITS_##SUPPORT_ARGS(, 2, 1, 2, 0, {}, {})) \
    GLTRAITS_VALUE_MATRIX_N(  4,    RGBA, __VA_ARGS__ GLTRAITS_##SUPPORT_ARGS(, 2, 0, 2, 0, {}, {}))

#define GLTRAITS_VALUE_MATRIXS(...) GLTRAITS_VALUE_MATRIX_(KEEP, __VA_ARGS__)
#define GLTRAITS_VALUE_MATRIX(...)  GLTRAITS_VALUE_MATRIX_(OMIT, __VA_ARGS__)

GLTRAITS_VALUE_SCALAR( GLfloat,   FLOAT,          FLOAT,                    32F,  f,  ,  ,         KEEP, 2, 0, 2, 0, {}, {})
GLTRAITS_VALUE_SCALAR( bool,      BYTE,           BOOL,                     8I,   i,  I, _INTEGER, OMIT, 2, 0, 3, 0, {}, {})
GLTRAITS_VALUE_SCALAR( GLbyte,    BYTE,           INT,                      8I,   i,  I, _INTEGER, OMIT, 2, 0, 3, 0, {}, {})
GLTRAITS_VALUE_SCALAR( GLshort,   SHORT,          INT,                      16I,  i,  I, _INTEGER, OMIT, 2, 0, 3, 0, {}, {})
GLTRAITS_VALUE_SCALAR( GLint,     INT,            INT,                      32I,  i,  I, _INTEGER, OMIT, 2, 0, 3, 0, {}, {})
GLTRAITS_VALUE_SCALAR( GLubyte,   UNSIGNED_BYTE,  UNSIGNED_INT,             8UI,  ui, I, _INTEGER, OMIT, 3, 0, 3, 0, {}, {})
GLTRAITS_VALUE_SCALAR( GLushort,  UNSIGNED_SHORT, UNSIGNED_INT,             16UI, ui, I, _INTEGER, OMIT, 3, 0, 3, 0, {}, {})
GLTRAITS_VALUE_SCALAR( GLuint,    UNSIGNED_INT,   UNSIGNED_INT,             32UI, ui, I, _INTEGER, OMIT, 3, 0, 3, 0, {}, {})
GLTRAITS_VALUE_SCALAR( GLdouble,  DOUBLE,         DOUBLE,                   ED,   d,  L, ,         OMIT, 4, 0, 4, 1, "GL_ARB_gpu_shader_fp64", "GL_ARB_vertex_attrib_64bit")

#ifdef GLM_VERSION
#include <glm/gtc/type_ptr.hpp>
GLTRAITS_VALUE_VECTOR( glm::vec,  FLOAT,          glm::value_ptr,           32F,  f,  ,  ,         KEEP, 2, 0, 2, 0, {}, {})
GLTRAITS_VALUE_MATRIXS(glm::mat,  FLOAT,          glm::value_ptr, GL_FALSE, 32F,  f,  ,  ,         KEEP)
GLTRAITS_VALUE_VECTOR( glm::ivec, INT,            glm::value_ptr,           32I,  i,  I, _INTEGER, OMIT, 2, 0, 3, 0, {}, {})
GLTRAITS_VALUE_VECTOR( glm::uvec, UNSIGNED_INT,   glm::value_ptr,           32UI, ui, I, _INTEGER, OMIT, 3, 0, 3, 0, {}, {})
GLTRAITS_VALUE_VECTOR( glm::dvec, DOUBLE,         glm::value_ptr,           ,     d,  L, ,         OMIT, 4, 0, 4, 1, "GL_ARB_gpu_shader_fp64", "GL_ARB_vertex_attrib_64bit")
GLTRAITS_VALUE_MATRIX( glm::dmat, DOUBLE,         glm::value_ptr, GL_FALSE, ,     d,  L, ,         OMIT, 4, 0, 4, 1, "GL_ARB_gpu_shader_fp64", "GL_ARB_vertex_attrib_64bit")
#endif


/// Guards

#endif