/// Guards

#ifndef GLTRAITS_HPP_
#define GLTRAITS_HPP_


/// Includes

#include <array>
#include <cstddef>
#include <string>

#include <glbase.hpp>


/// GLTraits

class GLTraits
{

public:

    //// Value

    template<typename>
    struct Value;

    template<GLenum id>
    struct ValueID;

    //// Object

    template<GLenum object_type>
    struct Object;

    //// Texture

    template<std::size_t N>
    struct Texture;

    //// Member

    #if __cplusplus >= 201402L

    template<typename Value, std::size_t offset>
    struct Member
    {};

    template<typename T, typename... Member>
    struct Members
    {};

    template<typename T>
    static constexpr auto members()
    {
        static_assert(
            !std::is_empty<T>::value &&
            std::is_trivially_copyable<T>::value &&
            std::is_standard_layout<T>::value,
            "T must be a non-empty trivially copyable standard-layout type"
        );
        return members_<T>(MakeIndices<member_count<T>()>{});
    }

    #endif

private:

    //// Helpers

    // Stripped down version of C++14's `std::{,make_}index_sequence`.

    template<std::size_t... Is>
    struct Indices
    {};

    template<std::size_t N, std::size_t... Is>
    struct MakeIndices_ : MakeIndices_<N - 1, N - 1, Is...>
    {};

    template<std::size_t... Is>
    struct MakeIndices_<0, Is...>
    {
        using Indices_ = Indices<Is...>;
    };

    template<std::size_t N>
    using MakeIndices = typename MakeIndices_<N>::Indices_;

    //// Texture

    template <
        std::size_t N,
        typename    Indices,
        typename    CopySizeIndices,
        typename    CopyOffsetIndices
    >
    struct Texture_;

    //// Member

    // Stripped down version of Boost::PFR.

    #if __cplusplus >= 201402L

    struct MemberInfo
    {
        GLenum      id;
        std::size_t offset;
    };

    template<std::size_t N>
    struct MemberInfos
    {
        MemberInfo infos[N];
    };

    struct MemberAny
    {
        MemberInfo * info;

        constexpr static auto align(std::size_t align, std::size_t offset)
        {
            return ((offset - 1) / align + 1) * align;
        }

        template<typename Value>
        constexpr operator Value()
        {
            return (
                info[0].id     = GLTraits::Value<Value>::id,
                info[0].offset = align(alignof(Value), info[0].offset),
                info[1].offset = info[0].offset + sizeof(Value),
                Value{}
            );
        }
    };

    template<typename T, std::size_t I0, std::size_t... Is>
    static constexpr auto member_count_(Indices<I0, Is...>)
        -> decltype(T{(I0, MemberAny{}), (Is, MemberAny{})...}, std::size_t{})
    {
        return 1 + sizeof...(Is);
    }

    template<typename T, std::size_t... Is>
    static constexpr auto member_count_(Indices<Is...>)
    {
        return member_count_<T>(MakeIndices<sizeof...(Is) - 1>{});
    }

    template<typename T>
    static constexpr auto member_count()
    {
        return member_count_<T>(MakeIndices<sizeof(T)>{});
    }

    template<typename T, std::size_t... Is>
    static constexpr auto member_infos(MemberInfos<sizeof...(Is)+1> infos = {})
    {
        return ((void)T{MemberAny{&infos.infos[Is]}...}, infos);
    }

    template<typename T, std::size_t... Is>
    static constexpr auto members_(Indices<Is...>)
    {
        return Members<
            T,
            Member<
                typename ValueID<member_infos<T, Is...>().infos[Is].id>::Value,
                member_infos<T, Is...>().infos[Is].offset
            >...
        >{};
    }

    #endif

};

//// 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); \
        auto static constexpr id = \
            (glsl == GL_INT || glsl == GL_UNSIGNED_INT) && \
            sizeof(VALUE) < sizeof(GLint) \
                ? type \
                : glsl; \
        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) \
                ); \
        } \
    }; \
    template<> \
    struct GLTraits::ValueID<GLTraits::Value<VALUE>::id> \
    { \
        using Value = VALUE; \
    };

#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

//// Object

#define GLTRAITS_OBJECT_( \
    OBJECT_TYPE, OBJECT, \
    GEN_OBJECTS, DELETE_OBJECTS, \
    INFO_LOG_ARGS, \
    V1, V2, EXT \
) \
    template<> \
    struct GLTraits::Object<GL_##OBJECT_TYPE> \
    { \
        auto static constexpr name = "GL_" #OBJECT_TYPE; \
        template<typename... Args> \
        void static gen_objects(GLsizei n, GLuint * objects, Args... args) \
        { \
            if (GLBase::debug() >= 1) \
                GLBase::check_supported({V1, V2}, EXT); \
            GEN_OBJECTS; \
        } \
        void static delete_objects(GLsizei n, GLuint const * objects) \
        { \
            if (GLBase::debug() >= 1) \
                GLBase::check_supported({V1, V2}, EXT); \
            DELETE_OBJECTS; \
        } \
        GLTRAITS_##INFO_LOG_ARGS( \
        std::string static info_log(GLuint object) \
        { \
            if (GLBase::debug() >= 1) \
                GLBase::check_supported({V1, V2}, EXT); \
            auto length = GLint{}; \
            glGet##OBJECT##iv(object, GL_INFO_LOG_LENGTH, &length); \
            if (length != 0) \
                --length; \
            auto info_log = std::string((std::size_t)length, char{}); \
            glGet##OBJECT##InfoLog( \
                object, \
                length+1, \
                nullptr, \
                &info_log[0] \
            ); \
            return info_log; \
        } \
        ) \
    };

#define GLTRAITS_OBJECT(OBJECT_TYPE, OBJECT, OBJECTS, ...) \
    GLTRAITS_OBJECT_( \
        OBJECT_TYPE, OBJECT, \
        glGen##OBJECTS(n, objects, args...), \
        glDelete##OBJECTS(n, objects), \
        __VA_ARGS__ \
    )

#define GLTRAITS_OBJECT_GLSL(OBJECT_TYPE, OBJECT, OBJECTS, ...) \
    GLTRAITS_OBJECT_( \
        OBJECT_TYPE, OBJECT, \
        while (n--) objects[n] = glCreate##OBJECT(args...), \
        while (n--) glDelete##OBJECT(objects[n]), \
        __VA_ARGS__ \
    )

GLTRAITS_OBJECT(     TEXTURE,            Texture,           Textures,           OMIT, 1, 1, {})
GLTRAITS_OBJECT(     BUFFER,             Buffer,            Buffers,            OMIT, 1, 5, {})
GLTRAITS_OBJECT(     QUERY,              Query,             Queries,            OMIT, 1, 5, {})
GLTRAITS_OBJECT_GLSL(PROGRAM,            Program,           Programs,           KEEP, 2, 0, {})
GLTRAITS_OBJECT_GLSL(SHADER,             Shader,            Shaders,            KEEP, 2, 0, {})
GLTRAITS_OBJECT(     VERTEX_ARRAY,       VertexArray,       VertexArrays,       OMIT, 3, 0, "GL_ARB_vertex_array_object")
GLTRAITS_OBJECT(     FRAMEBUFFER,        Framebuffer,       Framebuffers,       OMIT, 3, 0, "GL_ARB_framebuffer_object")
GLTRAITS_OBJECT(     RENDERBUFFER,       Renderbuffer,      Renderbuffers,      OMIT, 3, 0, "GL_ARB_framebuffer_object")
GLTRAITS_OBJECT(     SAMPLER,            Sampler,           Samplers,           OMIT, 3, 3, "GL_ARB_sampler_objects")
GLTRAITS_OBJECT(     TRANSFORM_FEEDBACK, TransformFeedback, TransformFeedbacks, OMIT, 4, 0, "GL_ARB_transform_feedback2")
GLTRAITS_OBJECT(     PROGRAM_PIPELINE,   ProgramPipeline,   ProgramPipelines,   KEEP, 4, 1, "GL_ARB_separate_shader_objects")

//// Texture

template<>
struct GLTraits::Texture<0>
{
protected:
    void static defaults_(
        GLenum & internal_format,
        GLenum & format,
        GLenum & type,
        GLenum   default_internal_format,
        GLenum   default_format,
        GLenum   default_type
    )
    {
        if (!internal_format) internal_format = default_internal_format;
        switch (internal_format)
        {
            case GL_DEPTH_STENCIL:
            case GL_DEPTH24_STENCIL8:
            case GL_DEPTH32F_STENCIL8:
                if (!format) format = GL_DEPTH_STENCIL;
                if (!type)   type   = GL_UNSIGNED_INT_24_8;
                break;
            case GL_DEPTH_COMPONENT:
            case GL_DEPTH_COMPONENT16:
            case GL_DEPTH_COMPONENT24:
            case GL_DEPTH_COMPONENT32:
            case GL_DEPTH_COMPONENT32F:
                if (!format) format = GL_DEPTH_COMPONENT;
                if (!type)   type   = GL_UNSIGNED_INT;
                break;
            case GL_STENCIL_INDEX:
            case GL_STENCIL_INDEX1:
            case GL_STENCIL_INDEX4:
            case GL_STENCIL_INDEX8:
            case GL_STENCIL_INDEX16:
                if (!format) format = GL_STENCIL_INDEX;
                if (!type)   type   = GL_UNSIGNED_BYTE;
                break;
            default:
                if (!format) format = default_format;
                if (!type)   type   = default_type;
        }
    }
};

#define GLTRAITS_TEXTURE_IMAGE( \
    N, \
    TEXTURE, TEXTURE_LOWER, \
    TYPE, NAME, \
    V1, V2, EXT \
) \
    template<typename Value = GLubyte> \
    void static TEXTURE_LOWER##_image( \
        TYPE          NAME, \
        Size          size, \
        GLenum        internal_format = 0, \
        Value const * data            = nullptr, \
        GLenum        format          = 0, \
        GLenum        type            = 0, \
        GLint         level           = 0, \
        GLint         border          = 0 \
    ) \
    { \
        if (GLBase::debug() >= 1) \
            GLBase::check_supported({V1, V2}, EXT); \
        defaults_( \
            internal_format, \
            format, \
            type, \
            GLTraits::Value<Value>::internal_format, \
            GLTraits::Value<Value>::format, \
            GLTraits::Value<Value>::type \
        ); \
        gl##TEXTURE##Image##N##D( \
            NAME, \
            level, \
            (GLint)internal_format, \
            std::get<Is>(size)..., \
            border, \
            format, \
            type, \
            data \
        ); \
    }

#define GLTRAITS_TEXTURE_STORAGE( \
    N, \
    TEXTURE, TEXTURE_LOWER, \
    TYPE, NAME, \
    V1, V2, EXT \
) \
    template<typename Value = GLubyte> \
    void static TEXTURE_LOWER##_storage( \
        TYPE    NAME, \
        Size    size, \
        GLenum  internal_format = 0, \
        GLsizei levels          = 1 \
    ) \
    { \
        if (GLBase::debug() >= 1) \
            GLBase::check_supported({V1, V2}, EXT); \
        if (!internal_format) \
            internal_format = GLTraits::Value<Value>::internal_format; \
        gl##TEXTURE##Storage##N##D( \
            NAME, \
            levels, \
            internal_format, \
            std::get<Is>(size)... \
        ); \
    }

#define GLTRAITS_TEXTURE_SUB_IMAGE( \
    N, \
    TEXTURE, TEXTURE_LOWER, \
    TYPE, NAME, \
    V1, V2, EXT \
) \
    template<typename Value = GLubyte> \
    void static TEXTURE_LOWER##_sub_image( \
        TYPE          NAME, \
        Size          size, \
        Value const * data, \
        GLenum        format = 0, \
        GLenum        type   = 0, \
        Offset        offset = {}, \
        GLint         level  = 0 \
    ) \
    { \
        if (GLBase::debug() >= 1) \
            GLBase::check_supported({V1, V2}, EXT); \
        if (!format) format = GLTraits::Value<Value>::format; \
        if (!type)   type   = GLTraits::Value<Value>::type; \
        gl##TEXTURE##SubImage##N##D( \
            NAME, \
            level, \
            std::get<Is>(offset)..., \
            std::get<Is>(size)  ..., \
            format, \
            type, \
            data \
        ); \
    }

#define GLTRAITS_TEXTURE_COPY_SUB_IMAGE( \
    N, \
    TEXTURE, TEXTURE_LOWER, \
    TYPE, NAME, \
    V1, V2, EXT \
) \
    void static copy_##TEXTURE_LOWER##_sub_image( \
        TYPE       NAME, \
        CopySize   copy_size, \
        CopyOffset copy_offset = {}, \
        Offset     offset      = {}, \
        GLint      level       = 0 \
    ) \
    { \
        if (GLBase::debug() >= 1) \
            GLBase::check_supported({V1, V2}, EXT); \
        glCopy##TEXTURE##SubImage##N##D( \
            NAME, \
            level, \
            std::get<Is>          (offset)     ..., \
            std::get<CopyOffsetIs>(copy_offset)..., \
            std::get<CopySizeIs>  (copy_size)  ... \
        ); \
    }

#define GLTRAITS_TEXTURE_COMPRESSED_IMAGE( \
    N, \
    TEXTURE, TEXTURE_LOWER, \
    TYPE, NAME, \
    V1, V2, EXT \
) \
    void static compressed_##TEXTURE_LOWER##_image( \
        TYPE         NAME, \
        Size         size, \
        GLenum       internal_format, \
        GLsizei      data_size = 0, \
        void const * data      = nullptr, \
        GLint        level     = 0, \
        GLint        border    = 0 \
    ) \
    { \
        if (GLBase::debug() >= 1) \
            GLBase::check_supported({V1, V2}, EXT); \
        glCompressed##TEXTURE##Image##N##D( \
            NAME, \
            level, \
            internal_format, \
            std::get<Is>(size)..., \
            border, \
            data_size, \
            data \
        ); \
    }

#define GLTRAITS_TEXTURE_COMPRESSED_SUB_IMAGE( \
    N, \
    TEXTURE, TEXTURE_LOWER, \
    TYPE, NAME, \
    V1, V2, EXT \
) \
    void static compressed_##TEXTURE_LOWER##_sub_image( \
        TYPE         NAME, \
        Size         size, \
        GLsizei      data_size, \
        void const * data, \
        GLenum       internal_format, \
        Offset       offset = {}, \
        GLint        level  = 0 \
    ) \
    { \
        if (GLBase::debug() >= 1) \
            GLBase::check_supported({V1, V2}, EXT); \
        glCompressed##TEXTURE##SubImage##N##D( \
            NAME, \
            level, \
            std::get<Is>(offset)..., \
            std::get<Is>(size)  ..., \
            internal_format, \
            data_size, \
            data \
        ); \
    }

#define GLTRAITS_TEXTURE( \
    N, \
    V1_1, V2_1, \
    V1_2, V2_2 \
) \
    template< \
        std::size_t... Is, \
        std::size_t... CopySizeIs, \
        std::size_t... CopyOffsetIs \
    > \
    struct GLTraits::Texture_< \
        N, \
        GLTraits::Indices<Is...>, \
        GLTraits::Indices<CopySizeIs...>, \
        GLTraits::Indices<CopyOffsetIs...> \
    > \
    : \
        GLTraits::Texture<0> \
    { \
        using Size       = std::array<GLsizei, sizeof...(Is)>; \
        using Offset     = std::array<GLsizei, sizeof...(Is)>; \
        using CopySize   = std::array<GLsizei, sizeof...(CopySizeIs)>; \
        using CopyOffset = std::array<GLsizei, sizeof...(CopyOffsetIs)>; \
        auto static constexpr default_target  = GL_TEXTURE_##N##D; \
        auto static constexpr default_binding = GL_TEXTURE_BINDING_##N##D; \
        GLTRAITS_TEXTURE_IMAGE(               N, Tex,     tex,     GLenum, target,  V1_1, V2_1, {}) \
        GLTRAITS_TEXTURE_SUB_IMAGE(           N, Tex,     tex,     GLenum, target,  V1_2, V2_2, {}) \
        GLTRAITS_TEXTURE_COPY_SUB_IMAGE(      N, Tex,     tex,     GLenum, target,  V1_2, V2_2, {}) \
        GLTRAITS_TEXTURE_COMPRESSED_IMAGE(    N, Tex,     tex,     GLenum, target,  1,    3,    {}) \
        GLTRAITS_TEXTURE_COMPRESSED_SUB_IMAGE(N, Tex,     tex,     GLenum, target,  1,    3,    {}) \
        GLTRAITS_TEXTURE_STORAGE(             N, Tex,     tex,     GLenum, target,  4,    2,    "GL_ARB_texture_storage") \
        GLTRAITS_TEXTURE_STORAGE(             N, Texture, texture, GLuint, texture, 4,    5,    "GL_ARB_direct_state_access") \
        GLTRAITS_TEXTURE_SUB_IMAGE(           N, Texture, texture, GLuint, texture, 4,    5,    "GL_ARB_direct_state_access") \
        GLTRAITS_TEXTURE_COPY_SUB_IMAGE(      N, Texture, texture, GLuint, texture, 4,    5,    "GL_ARB_direct_state_access") \
        GLTRAITS_TEXTURE_COMPRESSED_SUB_IMAGE(N, Texture, texture, GLuint, texture, 4,    5,    "GL_ARB_direct_state_access") \
    }; \
    template<> \
    struct GLTraits::Texture<N> \
    : \
        Texture_< \
            N, \
            MakeIndices<N>, \
            MakeIndices<N < 2 ? N : 2>, \
            MakeIndices<2> \
        > \
    {};

GLTRAITS_TEXTURE(1, 1, 0, 1, 1)
GLTRAITS_TEXTURE(2, 1, 0, 1, 1)
GLTRAITS_TEXTURE(3, 1, 2, 1, 2)


/// Guards

#endif