/// Includes


#include <glbase.hpp>

#include <algorithm>
#include <array>
#include <cstdio>
#include <fstream>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <unordered_set>

#define STR_EXCEPTION GLBase::Exception
#include <str.hpp>


/// Base


GLBASE_GLOBAL(GLBase::version_max_, {})


bool GLBase::supported(
    Version             version_min,
    std::string const & extension
)
{
    if (!extension.empty() && extension.rfind("GL_", 0) == std::string::npos)
        STR_THROW("Failed to parse extension \"" << extension << "\".");
    auto static thread_local version    = Version{};
    auto static thread_local extensions = std::unordered_set<std::string>{};
    if (version == Version{})
    {
        auto const * version_str = string(GL_VERSION);
        if (!version_str)
            return false;
        // NOLINTNEXTLINE
        if (std::sscanf(version_str, "%d.%d", &version[0], &version[1]) != 2)
            STR_THROW("Failed to parse version \"" << version_str << "\".");
        if (version[0] >= 3)
        {
            auto count = (GLuint)integer(GL_NUM_EXTENSIONS);
            for (auto index = GLuint{0}; index < count; ++index)
            {
                auto const * extension_str = string(GL_EXTENSIONS, index);
                if (extension_str)
                    extensions.insert(extension_str);
            }
        }
        else
        {
            auto const * extensions_str = string(GL_EXTENSIONS);
            if (!extensions_str)
                return false;
            auto istream = std::istringstream(extensions_str);
            using iterator = std::istream_iterator<std::string>;
            std::copy(
                iterator(istream),
                iterator(),
                std::inserter(extensions, extensions.end())
            );
        }
    }
    if (version_max() != Version{})
        if
        (
            version_min[0] > version_max()[0] ||
            (
                version_min[0] == version_max()[0] &&
                version_min[1] >  version_max()[1]
            )
        )
            return false;
    if (version_min != Version{})
        if
        (
            version[0] > version_min[0] ||
            (
                version[0] == version_min[0] &&
                version[1] >= version_min[1]
            )
        )
            return true;
    if (!extension.empty())
        if (extensions.find(extension) != extensions.end())
            return true;
    return false;
}


char const * GLBase::string(GLenum name)
try
{
    auto const * string = (char const *)glGetString(name);
    check_error_(glGetError());
    return string;
}
catch (...)
{
    fail_action_("get string", str_enum_(name));
}


char const * GLBase::string(GLenum name, GLuint index)
try
{
    // if (debug() >= 1)
    //     check_supported({3, 0});
    auto const * string = (char const *)glGetStringi(name, index);
    check_error_(glGetError());
    return string;
}
catch (...)
{
    fail_action_("get string", STR(str_enum_(name) << "[" << index << "]"));
}


GLint GLBase::integer(GLenum name)
try
{
    auto integer = GLint{};
    glGetIntegerv(name, &integer);
    check_error_(glGetError());
    return integer;
}
catch (...)
{
    fail_action_("get integer", str_enum_(name));
}


/// Path


GLBase::Path GLBase::path_prefix_(
    Path const & path,
    Path const & prefix
)
{
    check_path_(path);
    if (prefix.empty() || path[0] == '/')
        return path;
    return STR(prefix << "/" << path);
}


/// TGA


GLBase::TGA_::TGA_(Size size, Data data, Path const & path)
try
:
    size_{size},
    data_{std::move(data)}
{
    check_data_size_();
}
catch (...)
{
    fail_action_("create", name_(path));
}


GLBase::TGA_ GLBase::TGA_::read(Path const & path)
try
{
    auto istream = std::ifstream(path, std::ios::binary);
    auto header  = Header_({});
    istream.read((char *)header.data(), (std::streamsize)header.size());
    check_header_(header);
    auto size = header.tga_size();
    auto data = Data(4 * (std::size_t)size[0] * (std::size_t)size[1]);
    istream.read((char *)data.data(), (std::streamsize)data.size());
    if (!istream)
        STR_THROW_ERRNO();
    if (!istream.eof())
        STR_THROW("Garbage at end of file.");
    return TGA_(size, std::move(data), path);;
}
catch (...)
{
    fail_action_("read", name_(path));
}


void GLBase::TGA_::write(Path const & path) const
try
{
    auto header  = Header_(size());
    auto ostream = std::ofstream(path, std::ios::binary);
    ostream.write((char *)header.data(), (std::streamsize)header.size());
    ostream.write((char *)data_ .data(), (std::streamsize)data_ .size());
    if (!ostream)
        STR_THROW_ERRNO();
}
catch (...)
{
    fail_action_("write", name_(path));
}


GLBase::TGA_::Header_::Header_(Size size)
:
    std::array<GLubyte, 18>{                // NOLINT
        0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, // NOLINT
        (GLubyte)(size[0] >> 0),            // NOLINT
        (GLubyte)(size[0] >> 8),            // NOLINT
        (GLubyte)(size[1] >> 0),            // NOLINT
        (GLubyte)(size[1] >> 8),            // NOLINT
        32, 0,                              // NOLINT
    }
{
}


GLBase::TGA_::Size GLBase::TGA_::Header_::tga_size() const
{
    return {
        (GLsizei)((*this)[12]) << 0 | // NOLINT
        (GLsizei)((*this)[13]) << 8,  // NOLINT
        (GLsizei)((*this)[14]) << 0 | // NOLINT
        (GLsizei)((*this)[15]) << 8,  // NOLINT
    };
}


std::string GLBase::TGA_::name_(Path const & path)
{
    return STR("TGA" << " " << str_path_(path));
}


void GLBase::TGA_::check_header_(Header_ const & header)
{
    auto header_ = Header_(header.tga_size());
    if (header != header_)
        STR_THROW(
            "Expected TGA header"                      << " " <<
            "[" << STR_JOIN(", ", byte, byte, header_) << "]" << ", " <<
            "got"                                      << " " <<
            "[" << STR_JOIN(", ", byte, byte, header)  << "]" << "."
        );
}


void GLBase::TGA_::check_data_size_() const
{
    auto size = this->size();
    auto data_size = (std::size_t)(4 * size[0] * size[1]);
    if (data_size != data_.size())
        STR_THROW(
            "Expected TGA data size " << data_size    << ", " <<
            "got "                    << data_.size() << "."
        );
}


//// Debug


GLBASE_GLOBAL(GLBase::debug_,          {0})
GLBASE_GLOBAL(GLBase::debug_callback_, {[](std::string const & message) {
    std::cerr << message << std::endl;
}})


int GLBase::debug(int debug)
{
    auto debug_old = debug_;
    debug_         = debug;
    if (supported({4, 3}, "GL_KHR_debug"))
    {
        if (debug_old && !debug)
            glDisable(GL_DEBUG_OUTPUT);
        if (!debug_old && debug)
        {
            glEnable(GL_DEBUG_OUTPUT);
            glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
            glDebugMessageControl(
                GL_DONT_CARE, GL_DONT_CARE, GL_DONT_CARE,
                0, nullptr,
                GL_TRUE
            );
        }
        if (debug_old >= 2 && debug < 2)
            glDebugMessageControl(
                GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION,
                0, nullptr,
                GL_FALSE
            );
        if (debug_old < 2 && debug >= 2)
            glDebugMessageControl(
                GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION,
                0, nullptr,
                GL_TRUE
            );
    }
    return debug_old;
}


void GLBase::debug_action_(
    std::string const & action,
    std::string const & name
)
{
    debug_message(STR("Trying to " << action << " " << name << "."));
}


/// Check


void GLBase::check_path_(Path const & path)
{
    if (path.empty())
        STR_THROW(
            "Expected " << "non-empty path" << ", " <<
            "got "      << str_path_(path)  << "."
        );
}


void GLBase::check_error_(GLenum error)
{
    if (error != GL_NO_ERROR)
        STR_THROW(
            "Expected " << "no error"        << ", " <<
            "got "      << str_error_(error) << "."
        );
}


void GLBase::check_supported(
    Version             version_min,
    std::string const & extension
)
{
    if (!supported(version_min, extension))
    {
        auto const * version_str = string(GL_VERSION);
        STR_THROW(
            "Expected OpenGL version >=" <<
                STR_JOIN(".", it, it, version_min) <<
                (
                    !extension.empty()
                        ? STR(" or extension " << extension)
                        : ""
                ) <<
            ", " <<
            "got " <<
                (
                    version_str
                        ? version_str
                        : "none (no current context?)"
                ) <<
            "."
        );
    }
}


void GLBase::check_type_(
    GLenum type,
    GLenum type_expected
)
{
    if (type != type_expected)
        STR_THROW(
            "Expected type " << str_type_(type_expected) << ", " <<
            "got "           << str_type_(type)          << "."
        );
}


void GLBase::check_format_(
    GLenum format,
    GLenum format_expected
)
{
    if (format != format_expected)
        STR_THROW(
            "Expected format " << str_format_(format_expected) << ", " <<
            "got "             << str_format_(format)          << "."
        );
}


void GLBase::check_internal_format_(GLenum internal_format)
{
    switch (internal_format)
    {
        case GL_RED:
        case GL_RGB:
        case GL_RGBA:
        case GL_DEPTH_COMPONENT:
        case GL_STENCIL_INDEX:
            check_supported({1, 0});
            return;
        case GL_R3_G3_B2:
            check_supported({1, 1});
            return;
        case GL_RGB4:
        case GL_RGB5:
        case GL_RGB8:
        case GL_RGB10:
        case GL_RGB12:
        case GL_RGB16:
        case GL_RGB5_A1:
        case GL_RGB10_A2:
        case GL_RGBA2:
        case GL_RGBA4:
        case GL_RGBA8:
        case GL_RGBA12:
        case GL_RGBA16:
            check_supported({1, 1}, "GL_EXT_texture");
            return;
        case GL_RGB2_EXT:
            check_supported({}, "GL_EXT_texture");
            return;
        case GL_COMPRESSED_RGB:
        case GL_COMPRESSED_RGBA:
            check_supported({1, 3}, "GL_ARB_texture_compression");
            return;
        case GL_DEPTH_COMPONENT16:
        case GL_DEPTH_COMPONENT24:
        case GL_DEPTH_COMPONENT32:
            check_supported({1, 4}, "GL_ARB_depth_texture");
            return;
        case GL_SRGB:
        case GL_SRGB8:
        case GL_SRGB_ALPHA:
        case GL_SRGB8_ALPHA8:
        case GL_COMPRESSED_SRGB:
        case GL_COMPRESSED_SRGB_ALPHA:
            check_supported({2, 1}, "GL_EXT_texture_sRGB");
            return;
        case GL_SR8_EXT:
            check_supported({}, "GL_EXT_texture_sRGB_R8");
            return;
        case GL_SRG8_EXT:
            check_supported({}, "GL_EXT_texture_sRGB_RG8");
            return;
        case GL_COMPRESSED_RGB_S3TC_DXT1_EXT:
        case GL_COMPRESSED_RGBA_S3TC_DXT1_EXT:
        case GL_COMPRESSED_RGBA_S3TC_DXT3_EXT:
        case GL_COMPRESSED_RGBA_S3TC_DXT5_EXT:
            check_supported({}, "GL_EXT_texture_compression_s3tc");
            return;
        case GL_COMPRESSED_SRGB_S3TC_DXT1_EXT:
        case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT:
        case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT:
        case GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT:
            check_supported({}, "GL_EXT_texture_sRGB");
            check_supported({}, "GL_EXT_texture_compression_s3tc");
            return;
        case GL_COMPRESSED_RED:
        case GL_COMPRESSED_RG:
            check_supported({3, 0});
            return;
        case GL_COMPRESSED_RED_RGTC1:
        case GL_COMPRESSED_RG_RGTC2:
        case GL_COMPRESSED_SIGNED_RED_RGTC1:
        case GL_COMPRESSED_SIGNED_RG_RGTC2:
            check_supported({3, 0}, "GL_ARB_texture_compression_rgtc");
            return;
        case GL_RGB16F:
        case GL_RGB32F:
        case GL_RGBA16F:
        case GL_RGBA32F:
            check_supported({3, 0}, "GL_ARB_texture_float");
            return;
        case GL_RGB8I:
        case GL_RGB8UI:
        case GL_RGB16I:
        case GL_RGB16UI:
        case GL_RGB32I:
        case GL_RGB32UI:
        case GL_RGBA8I:
        case GL_RGBA8UI:
        case GL_RGBA16I:
        case GL_RGBA16UI:
        case GL_RGBA32I:
        case GL_RGBA32UI:
            check_supported({3, 0}, "GL_EXT_texture_integer");
            return;
        case GL_R8:
        case GL_R8I:
        case GL_R8UI:
        case GL_R16:
        case GL_R16I:
        case GL_R16UI:
        case GL_R32I:
        case GL_R32UI:
        case GL_R16F:
        case GL_R32F:
        case GL_RG:
        case GL_RG8:
        case GL_RG8I:
        case GL_RG8UI:
        case GL_RG16:
        case GL_RG16I:
        case GL_RG16UI:
        case GL_RG32I:
        case GL_RG32UI:
        case GL_RG16F:
        case GL_RG32F:
            check_supported({3, 0}, "GL_ARB_texture_rg");
            return;
        case GL_R11F_G11F_B10F:
            check_supported({3, 0}, "GL_EXT_packed_float");
            return;
        case GL_RGB9_E5:
            check_supported({3, 0}, "GL_EXT_texture_shared_exponent");
            return;
        case GL_DEPTH_STENCIL:
        case GL_DEPTH24_STENCIL8:
            check_supported({3, 0}, "GL_EXT_packed_depth_stencil");
            return;
        case GL_DEPTH32F_STENCIL8:
        case GL_DEPTH_COMPONENT32F:
            check_supported({3, 0}, "GL_ARB_depth_buffer_float");
            return;
        case GL_STENCIL_INDEX1:
        case GL_STENCIL_INDEX4:
        case GL_STENCIL_INDEX8:
        case GL_STENCIL_INDEX16:
            check_supported({3, 0}, "GL_ARB_framebuffer_object");
            return;
        case GL_R8_SNORM:
        case GL_R16_SNORM:
        case GL_RG8_SNORM:
        case GL_RG16_SNORM:
        case GL_RGB8_SNORM:
        case GL_RGB16_SNORM:
        case GL_RGBA8_SNORM:
        case GL_RGBA16_SNORM:
            check_supported({3, 1}, "GL_EXT_texture_snorm");
            return;
        case GL_RGB10_A2UI:
            check_supported({3, 3}, "GL_ARB_texture_rgb10_a2ui");
            return;
        case GL_RGB565:
            check_supported({4, 1}, "GL_ARB_ES2_compatibility");
            return;
        case GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT:
        case GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT:
        case GL_COMPRESSED_RGBA_BPTC_UNORM:
        case GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM:
            check_supported({4, 2}, "GL_ARB_texture_compression_bptc");
            return;
        case GL_COMPRESSED_RGB8_ETC2:
        case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
        case GL_COMPRESSED_SRGB8_ETC2:
        case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
        case GL_COMPRESSED_RGBA8_ETC2_EAC:
        case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
        case GL_COMPRESSED_R11_EAC:
        case GL_COMPRESSED_RG11_EAC:
        case GL_COMPRESSED_SIGNED_R11_EAC:
        case GL_COMPRESSED_SIGNED_RG11_EAC:
            check_supported({4, 3}, "GL_ARB_ES3_compatibility");
            return;
        case GL_COMPRESSED_RGBA_ASTC_4x4_KHR:
        case GL_COMPRESSED_RGBA_ASTC_5x4_KHR:
        case GL_COMPRESSED_RGBA_ASTC_5x5_KHR:
        case GL_COMPRESSED_RGBA_ASTC_6x5_KHR:
        case GL_COMPRESSED_RGBA_ASTC_6x6_KHR:
        case GL_COMPRESSED_RGBA_ASTC_8x5_KHR:
        case GL_COMPRESSED_RGBA_ASTC_8x6_KHR:
        case GL_COMPRESSED_RGBA_ASTC_8x8_KHR:
        case GL_COMPRESSED_RGBA_ASTC_10x5_KHR:
        case GL_COMPRESSED_RGBA_ASTC_10x6_KHR:
        case GL_COMPRESSED_RGBA_ASTC_10x8_KHR:
        case GL_COMPRESSED_RGBA_ASTC_10x10_KHR:
        case GL_COMPRESSED_RGBA_ASTC_12x10_KHR:
        case GL_COMPRESSED_RGBA_ASTC_12x12_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR:
        case GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR:
            check_supported({}, "GL_KHR_texture_compression_astc_ldr");
            return;
        default:
            STR_THROW(
                "Expected " << "internal format"                     << ", " <<
                "got "      << str_internal_format_(internal_format) << "."
            );
    }
}


/// Fail


void GLBase::fail_action_(
    std::string const & action,
    std::string const & name
)
{
    STR_RETHROW("Failed to " << action << " " << name << ":\n");
}


/// String


std::string GLBase::str_path_(Path const & path)
{
    return STR("\"" << path << "\"");
}


std::string GLBase::str_paths_(Paths const & paths)
{
    return STR_JOIN(", ", path, str_path_(path), paths);
}


std::string GLBase::str_enum_(GLenum name)
{
    return STR(std::hex << std::showbase << std::uppercase << name);
}


std::string GLBase::str_error_(GLenum error)
{
    switch (error)
    {
        STR_CASE(GL_NO_ERROR)
        STR_CASE(GL_INVALID_ENUM)
        STR_CASE(GL_INVALID_VALUE)
        STR_CASE(GL_INVALID_OPERATION)
        STR_CASE(GL_INVALID_FRAMEBUFFER_OPERATION)
        STR_CASE(GL_OUT_OF_MEMORY)
        STR_CASE(GL_STACK_OVERFLOW)
        STR_CASE(GL_STACK_UNDERFLOW)
        STR_CASE(GL_CONTEXT_LOST) // GL_KHR_robustness
        default:
            return str_enum_(error);
    }
}


std::string GLBase::str_object_type_(GLenum object_type)
{
    switch (object_type)
    {
        STR_CASE(GL_TEXTURE)
        STR_CASE(GL_BUFFER)
        STR_CASE(GL_SHADER)
        STR_CASE(GL_PROGRAM)
        STR_CASE(GL_PROGRAM_PIPELINE)
        STR_CASE(GL_FRAMEBUFFER)
        STR_CASE(GL_RENDERBUFFER)
        STR_CASE(GL_VERTEX_ARRAY)
        STR_CASE(GL_TRANSFORM_FEEDBACK)
        STR_CASE(GL_SAMPLER)
        STR_CASE(GL_QUERY)
        default:
            return str_enum_(object_type);
    }
}


std::string GLBase::str_glsl_(GLenum glsl)
{
    switch (glsl)
    {
        STR_CASE(GL_FLOAT)
        STR_CASE(GL_FLOAT_VEC2)
        STR_CASE(GL_FLOAT_VEC3)
        STR_CASE(GL_FLOAT_VEC4)
        STR_CASE(GL_FLOAT_MAT2)
        STR_CASE(GL_FLOAT_MAT2x3)
        STR_CASE(GL_FLOAT_MAT2x4)
        STR_CASE(GL_FLOAT_MAT3x2)
        STR_CASE(GL_FLOAT_MAT3)
        STR_CASE(GL_FLOAT_MAT3x4)
        STR_CASE(GL_FLOAT_MAT4x2)
        STR_CASE(GL_FLOAT_MAT4x3)
        STR_CASE(GL_FLOAT_MAT4)
        STR_CASE(GL_INT)
        STR_CASE(GL_INT_VEC2)
        STR_CASE(GL_INT_VEC3)
        STR_CASE(GL_INT_VEC4)
        STR_CASE(GL_UNSIGNED_INT)
        STR_CASE(GL_UNSIGNED_INT_VEC2)
        STR_CASE(GL_UNSIGNED_INT_VEC3)
        STR_CASE(GL_UNSIGNED_INT_VEC4)
        STR_CASE(GL_DOUBLE)
        STR_CASE(GL_DOUBLE_VEC2)
        STR_CASE(GL_DOUBLE_VEC3)
        STR_CASE(GL_DOUBLE_VEC4)
        STR_CASE(GL_DOUBLE_MAT2)
        STR_CASE(GL_DOUBLE_MAT2x3)
        STR_CASE(GL_DOUBLE_MAT2x4)
        STR_CASE(GL_DOUBLE_MAT3x2)
        STR_CASE(GL_DOUBLE_MAT3)
        STR_CASE(GL_DOUBLE_MAT3x4)
        STR_CASE(GL_DOUBLE_MAT4x2)
        STR_CASE(GL_DOUBLE_MAT4x3)
        STR_CASE(GL_DOUBLE_MAT4)
        STR_CASE(GL_BOOL)
        STR_CASE(GL_BOOL_VEC2)
        STR_CASE(GL_BOOL_VEC3)
        STR_CASE(GL_BOOL_VEC4)
        STR_CASE(GL_SAMPLER_1D)
        STR_CASE(GL_SAMPLER_1D_SHADOW)
        STR_CASE(GL_SAMPLER_1D_ARRAY)
        STR_CASE(GL_SAMPLER_1D_ARRAY_SHADOW)
        STR_CASE(GL_SAMPLER_2D)
        STR_CASE(GL_SAMPLER_2D_SHADOW)
        STR_CASE(GL_SAMPLER_2D_ARRAY)
        STR_CASE(GL_SAMPLER_2D_ARRAY_SHADOW)
        STR_CASE(GL_SAMPLER_2D_RECT)
        STR_CASE(GL_SAMPLER_2D_RECT_SHADOW)
        STR_CASE(GL_SAMPLER_2D_MULTISAMPLE)
        STR_CASE(GL_SAMPLER_2D_MULTISAMPLE_ARRAY)
        STR_CASE(GL_SAMPLER_3D)
        STR_CASE(GL_SAMPLER_CUBE)
        STR_CASE(GL_SAMPLER_CUBE_SHADOW)
        STR_CASE(GL_SAMPLER_CUBE_MAP_ARRAY)
        STR_CASE(GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW)
        STR_CASE(GL_SAMPLER_BUFFER)
        STR_CASE(GL_INT_SAMPLER_1D)
        STR_CASE(GL_INT_SAMPLER_1D_ARRAY)
        STR_CASE(GL_INT_SAMPLER_2D)
        STR_CASE(GL_INT_SAMPLER_2D_ARRAY)
        STR_CASE(GL_INT_SAMPLER_2D_RECT)
        STR_CASE(GL_INT_SAMPLER_2D_MULTISAMPLE)
        STR_CASE(GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY)
        STR_CASE(GL_INT_SAMPLER_3D)
        STR_CASE(GL_INT_SAMPLER_CUBE)
        STR_CASE(GL_INT_SAMPLER_CUBE_MAP_ARRAY)
        STR_CASE(GL_INT_SAMPLER_BUFFER)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_1D)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_1D_ARRAY)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_2D)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_2D_ARRAY)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_2D_RECT)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_3D)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_CUBE)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY)
        STR_CASE(GL_UNSIGNED_INT_SAMPLER_BUFFER)
        STR_CASE(GL_IMAGE_1D)
        STR_CASE(GL_IMAGE_1D_ARRAY)
        STR_CASE(GL_IMAGE_2D)
        STR_CASE(GL_IMAGE_2D_ARRAY)
        STR_CASE(GL_IMAGE_2D_RECT)
        STR_CASE(GL_IMAGE_2D_MULTISAMPLE)
        STR_CASE(GL_IMAGE_2D_MULTISAMPLE_ARRAY)
        STR_CASE(GL_IMAGE_3D)
        STR_CASE(GL_IMAGE_CUBE)
        STR_CASE(GL_IMAGE_CUBE_MAP_ARRAY)
        STR_CASE(GL_IMAGE_BUFFER)
        STR_CASE(GL_INT_IMAGE_1D)
        STR_CASE(GL_INT_IMAGE_1D_ARRAY)
        STR_CASE(GL_INT_IMAGE_2D)
        STR_CASE(GL_INT_IMAGE_2D_ARRAY)
        STR_CASE(GL_INT_IMAGE_2D_RECT)
        STR_CASE(GL_INT_IMAGE_2D_MULTISAMPLE)
        STR_CASE(GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY)
        STR_CASE(GL_INT_IMAGE_3D)
        STR_CASE(GL_INT_IMAGE_CUBE)
        STR_CASE(GL_INT_IMAGE_CUBE_MAP_ARRAY)
        STR_CASE(GL_INT_IMAGE_BUFFER)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_1D)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_1D_ARRAY)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_2D)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_2D_ARRAY)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_2D_RECT)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_3D)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_CUBE)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY)
        STR_CASE(GL_UNSIGNED_INT_IMAGE_BUFFER)
        // GL_ARB_gpu_shader_int64
        STR_CASE(GL_INT64_ARB)
        STR_CASE(GL_INT64_VEC2_ARB)
        STR_CASE(GL_INT64_VEC3_ARB)
        STR_CASE(GL_INT64_VEC4_ARB)
        STR_CASE(GL_UNSIGNED_INT64_ARB) // GL_ARB_bindless_texture
        STR_CASE(GL_UNSIGNED_INT64_VEC2_ARB)
        STR_CASE(GL_UNSIGNED_INT64_VEC3_ARB)
        STR_CASE(GL_UNSIGNED_INT64_VEC4_ARB)
        default:
            return str_enum_(glsl);
    }
}


std::string GLBase::str_format_(GLenum format)
{
    switch (format)
    {
        STR_CASE(GL_RED)
        STR_CASE(GL_RED_INTEGER)
        STR_CASE(GL_GREEN)
        STR_CASE(GL_GREEN_INTEGER)
        STR_CASE(GL_BLUE)
        STR_CASE(GL_BLUE_INTEGER)
        STR_CASE(GL_ALPHA)
        STR_CASE(GL_ALPHA_INTEGER)
        STR_CASE(GL_RG)
        STR_CASE(GL_RG_INTEGER)
        STR_CASE(GL_RGB)
        STR_CASE(GL_RGB_INTEGER)
        STR_CASE(GL_RGBA)
        STR_CASE(GL_RGBA_INTEGER)
        STR_CASE(GL_BGR)
        STR_CASE(GL_BGR_INTEGER)
        STR_CASE(GL_BGRA)
        STR_CASE(GL_BGRA_INTEGER)
        STR_CASE(GL_ABGR_EXT) // GL_EXT_abgr
        STR_CASE(GL_DEPTH_STENCIL)
        STR_CASE(GL_DEPTH_COMPONENT)
        STR_CASE(GL_STENCIL_INDEX)
        default:
            return str_enum_(format);
    }
}


std::string GLBase::str_type_(GLenum type)
{
    switch (type)
    {
        STR_CASE(GL_FLOAT)
        STR_CASE(GL_BYTE)
        STR_CASE(GL_SHORT)
        STR_CASE(GL_INT)
        STR_CASE(GL_UNSIGNED_BYTE)
        STR_CASE(GL_UNSIGNED_SHORT)
        STR_CASE(GL_UNSIGNED_INT)
        STR_CASE(GL_UNSIGNED_BYTE_3_3_2)
        STR_CASE(GL_UNSIGNED_BYTE_2_3_3_REV)
        STR_CASE(GL_UNSIGNED_SHORT_5_6_5)
        STR_CASE(GL_UNSIGNED_SHORT_5_6_5_REV)
        STR_CASE(GL_UNSIGNED_SHORT_4_4_4_4)
        STR_CASE(GL_UNSIGNED_SHORT_4_4_4_4_REV)
        STR_CASE(GL_UNSIGNED_SHORT_5_5_5_1)
        STR_CASE(GL_UNSIGNED_SHORT_1_5_5_5_REV)
        STR_CASE(GL_UNSIGNED_INT_8_8_8_8)
        STR_CASE(GL_UNSIGNED_INT_8_8_8_8_REV)
        STR_CASE(GL_UNSIGNED_INT_10_10_10_2)
        STR_CASE(GL_UNSIGNED_INT_2_10_10_10_REV)
        STR_CASE(GL_UNSIGNED_INT_10F_11F_11F_REV)
        STR_CASE(GL_UNSIGNED_INT_5_9_9_9_REV)
        STR_CASE(GL_UNSIGNED_INT_24_8)
        STR_CASE(GL_FLOAT_32_UNSIGNED_INT_24_8_REV)
        STR_CASE(GL_DOUBLE)
        STR_CASE(GL_HALF_FLOAT)
        STR_CASE(GL_FIXED)
        // GL_ARB_gpu_shader_int64
        STR_CASE(GL_INT64_ARB)
        STR_CASE(GL_UNSIGNED_INT64_ARB) // GL_ARB_bindless_texture
        default:
            return str_enum_(type);
    }
}


std::string GLBase::str_internal_format_(GLenum internal_format)
{
    switch (internal_format)
    {
        STR_CASE(GL_RED)
        STR_CASE(GL_R8)
        STR_CASE(GL_R8I)
        STR_CASE(GL_R8UI)
        STR_CASE(GL_R16)
        STR_CASE(GL_R16I)
        STR_CASE(GL_R16UI)
        STR_CASE(GL_R32I)
        STR_CASE(GL_R32UI)
        STR_CASE(GL_R16F)
        STR_CASE(GL_R32F)
        STR_CASE(GL_RG)
        STR_CASE(GL_RG8)
        STR_CASE(GL_RG8I)
        STR_CASE(GL_RG8UI)
        STR_CASE(GL_RG16)
        STR_CASE(GL_RG16I)
        STR_CASE(GL_RG16UI)
        STR_CASE(GL_RG32I)
        STR_CASE(GL_RG32UI)
        STR_CASE(GL_RG16F)
        STR_CASE(GL_RG32F)
        STR_CASE(GL_RGB)
        STR_CASE(GL_RGB2_EXT) // GL_EXT_texture
        STR_CASE(GL_R3_G3_B2)
        STR_CASE(GL_RGB4)
        STR_CASE(GL_RGB5)
        STR_CASE(GL_RGB565)
        STR_CASE(GL_RGB8)
        STR_CASE(GL_RGB8I)
        STR_CASE(GL_RGB8UI)
        STR_CASE(GL_RGB10)
        STR_CASE(GL_RGB12)
        STR_CASE(GL_RGB16)
        STR_CASE(GL_RGB16I)
        STR_CASE(GL_RGB16UI)
        STR_CASE(GL_RGB32I)
        STR_CASE(GL_RGB32UI)
        STR_CASE(GL_RGB16F)
        STR_CASE(GL_RGB32F)
        STR_CASE(GL_R11F_G11F_B10F)
        STR_CASE(GL_RGB9_E5)
        STR_CASE(GL_RGBA)
        STR_CASE(GL_RGBA2)
        STR_CASE(GL_RGBA4)
        STR_CASE(GL_RGBA8)
        STR_CASE(GL_RGBA8I)
        STR_CASE(GL_RGBA8UI)
        STR_CASE(GL_RGBA12)
        STR_CASE(GL_RGBA16)
        STR_CASE(GL_RGBA16I)
        STR_CASE(GL_RGBA16UI)
        STR_CASE(GL_RGBA32I)
        STR_CASE(GL_RGBA32UI)
        STR_CASE(GL_RGB5_A1)
        STR_CASE(GL_RGB10_A2)
        STR_CASE(GL_RGB10_A2UI)
        STR_CASE(GL_RGBA16F)
        STR_CASE(GL_RGBA32F)
        STR_CASE(GL_R8_SNORM)
        STR_CASE(GL_R16_SNORM)
        STR_CASE(GL_RG8_SNORM)
        STR_CASE(GL_RG16_SNORM)
        STR_CASE(GL_RGB8_SNORM)
        STR_CASE(GL_RGB16_SNORM)
        STR_CASE(GL_RGBA8_SNORM)
        STR_CASE(GL_RGBA16_SNORM)
        STR_CASE(GL_SR8_EXT)  // GL_EXT_texture_sRGB_R8
        STR_CASE(GL_SRG8_EXT) // GL_EXT_texture_sRGB_RG8
        STR_CASE(GL_SRGB)
        STR_CASE(GL_SRGB8)
        STR_CASE(GL_SRGB_ALPHA)
        STR_CASE(GL_SRGB8_ALPHA8)
        STR_CASE(GL_DEPTH_STENCIL)
        STR_CASE(GL_DEPTH24_STENCIL8)
        STR_CASE(GL_DEPTH32F_STENCIL8)
        STR_CASE(GL_DEPTH_COMPONENT)
        STR_CASE(GL_DEPTH_COMPONENT16)
        STR_CASE(GL_DEPTH_COMPONENT24)
        STR_CASE(GL_DEPTH_COMPONENT32)
        STR_CASE(GL_DEPTH_COMPONENT32F)
        STR_CASE(GL_STENCIL_INDEX)
        STR_CASE(GL_STENCIL_INDEX1)
        STR_CASE(GL_STENCIL_INDEX4)
        STR_CASE(GL_STENCIL_INDEX8)
        STR_CASE(GL_STENCIL_INDEX16)
        STR_CASE(GL_COMPRESSED_RED)
        STR_CASE(GL_COMPRESSED_RG)
        STR_CASE(GL_COMPRESSED_RGB)
        STR_CASE(GL_COMPRESSED_RGBA)
        STR_CASE(GL_COMPRESSED_SRGB)
        STR_CASE(GL_COMPRESSED_SRGB_ALPHA)
        STR_CASE(GL_COMPRESSED_RGB_S3TC_DXT1_EXT)
        STR_CASE(GL_COMPRESSED_RGBA_S3TC_DXT1_EXT)
        STR_CASE(GL_COMPRESSED_RGBA_S3TC_DXT3_EXT)
        STR_CASE(GL_COMPRESSED_RGBA_S3TC_DXT5_EXT)
        STR_CASE(GL_COMPRESSED_SRGB_S3TC_DXT1_EXT)
        STR_CASE(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT)
        STR_CASE(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT)
        STR_CASE(GL_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT)
        STR_CASE(GL_COMPRESSED_RED_RGTC1)
        STR_CASE(GL_COMPRESSED_RG_RGTC2)
        STR_CASE(GL_COMPRESSED_SIGNED_RED_RGTC1)
        STR_CASE(GL_COMPRESSED_SIGNED_RG_RGTC2)
        STR_CASE(GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT)
        STR_CASE(GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT)
        STR_CASE(GL_COMPRESSED_RGBA_BPTC_UNORM)
        STR_CASE(GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM)
        STR_CASE(GL_COMPRESSED_RGB8_ETC2)
        STR_CASE(GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2)
        STR_CASE(GL_COMPRESSED_SRGB8_ETC2)
        STR_CASE(GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2)
        STR_CASE(GL_COMPRESSED_RGBA8_ETC2_EAC)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC)
        STR_CASE(GL_COMPRESSED_R11_EAC)
        STR_CASE(GL_COMPRESSED_RG11_EAC)
        STR_CASE(GL_COMPRESSED_SIGNED_R11_EAC)
        STR_CASE(GL_COMPRESSED_SIGNED_RG11_EAC)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_4x4_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_5x4_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_5x5_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_6x5_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_6x6_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_8x5_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_8x6_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_8x8_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_10x5_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_10x6_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_10x8_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_10x10_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_12x10_KHR)
        STR_CASE(GL_COMPRESSED_RGBA_ASTC_12x12_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_4x4_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x4_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_5x5_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x5_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x5_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x6_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_8x8_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x8_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR)
        default:
            return str_enum_(internal_format);
    }
}
