#include <globject.hpp>

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

// NOLINTNEXTLINE
#define STR_EXCEPTION GLObject::Exception
#include <str.hpp>


/// Special member functions


GLObject::GLObject(
    GLGenObjects    gl_gen_objects,
    GLDeleteObjects gl_delete_objects,
    GLenum          object_type,
    // cppcheck-suppress passedByValue
    std::string     object_label
)
:
    gl_delete_objects_{gl_delete_objects},
    object_type_      {object_type},
    object_label_     {std::move(object_label)},
    object_           {0}
{
    try
    {
        if (gl_gen_objects)
            gl_gen_objects(1, &object_);
        else
            object_ = object_pseudo_;
        check_error_(glGetError());
        GLOBJECT_DEBUG_IF(1)
        {
            debug_label_();
            debug_objects_push_back_(this);
            debug_callback()("Create " + debug_name());
        }
    }
    catch (...)
    {
        if (gl_delete_objects_)
            gl_delete_objects_(1, &object_);
        fail_action_("create");
    }
}


GLObject::GLObject(GLObject && other) noexcept
:
    gl_delete_objects_{other.gl_delete_objects_},
    object_type_      {other.object_type_},
    object_label_     {std::move(other.object_label_)},
    object_           {other.object_}
{
    other.object_ = 0;
    GLOBJECT_DEBUG_IF(1)
    {
        debug_objects_erase_(&other);
        debug_objects_push_back_(this);
        debug_callback()("Move " + debug_name());
    }
}


GLObject::~GLObject()
{
    if (gl_delete_objects_)
        gl_delete_objects_(1, &object_);
    GLOBJECT_DEBUG_IF(1)
    {
        debug_objects_erase_(this);
        debug_callback()("Destroy " + debug_name());
    }
}


/// Core


bool GLObject::supported(
    Version version_min,
    std::string const & extension
)
{
    auto static version    = Version{0, 0};
    auto static extensions = std::unordered_set<std::string>{};
    if (extension.size() && extension.rfind("GL_", 0) == extension.npos)
        STR_THROW("Failed to parse extension \"" << extension << "\".");
    if (version == Version{0, 0})
    {
        auto const * version_str = (char const *)glGetString(GL_VERSION);
        if (!version_str)
            return false;
        // NOLINTNEXTLINE
        if (2 != std::sscanf(version_str, "%d.%d", &version[0], &version[1]))
            STR_THROW("Failed to parse version \"" << version_str << "\".");
        if (version[0] >= 3)
        {
            auto extension_count = get_integer(GL_NUM_EXTENSIONS);
            for (auto i = 0; i < extension_count; ++i)
                extensions.insert(
                    (char const *)glGetStringi(GL_EXTENSIONS, (GLuint)i)
                );
        }
        else
        {
            auto istream = std::istringstream(
                (char const *)glGetString(GL_EXTENSIONS)
            );
            std::copy(
                std::istream_iterator<std::string>(istream),
                std::istream_iterator<std::string>(),
                std::inserter(extensions, extensions.end())
            );
        }
    }
    if
    (
        (version_min != Version{0, 0}) &&
        (
            (version[0] > version_min[0]) ||
            (
                version[0] == version_min[0] &&
                version[1] >= version_min[1]
            )
        )
    )
        return true;
    if
    (
        extension.size() &&
        extensions.find(extension) != extensions.end()
    )
        return true;
    return false;
}


GLint GLObject::get_integer(GLenum name)
try
{
    auto value = GLint{};
    glGetIntegerv(name, &value);
    GLOBJECT_DEBUG_IF(1)
        check_error_(glGetError());
    return value;
}
catch (...)
{
    STR_THROW_NESTED("Failed to get integer " << str_enum_(name) << ":");
}


/// Path


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


/// TGA


GLObject::TGA::TGA(Size size, Data data)
try
:
    header_{size},
    data_{std::move(data)}
{
    check_data_();
}
catch (...)
{
    STR_THROW_NESTED("Failed to create TGA " << str_size_(size) << ":");
}


GLObject::TGA GLObject::TGA::read(Path const & path)
try
{
    auto tga = TGA({}, {});
    auto istream = std::ifstream(path, std::ios::binary);
    istream.read(
        (char *)         tga.header_.data(),
        (std::streamsize)tga.header_.size()
    );
    tga.check_header_();
    tga.data_.resize((std::size_t)(4 * tga.size()[0] * tga.size()[1]));
    istream.read(
        (char *)         tga.data_.data(),
        (std::streamsize)tga.data_.size()
    );
    istream.close();
    if (!istream)
        // NOLINTNEXTLINE
        STR_THROW(std::strerror(errno));
    return tga;
}
catch (...)
{
    STR_THROW_NESTED("Failed to read TGA " << str_path_(path) << ":");
}


void GLObject::TGA::write(Path const & path) const
try
{
    check_data_();
    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());
    ostream.close();
    if (!ostream)
        // NOLINTNEXTLINE
        STR_THROW(std::strerror(errno));
}
catch (...)
{
    STR_THROW_NESTED("Failed to write TGA " << str_path_(path) << ":");
}


GLObject::TGA::Data const & GLObject::TGA::data()
{
    return data_;
}


GLObject::TGA::Size GLObject::TGA::size() const
{
    return {
        (GLsizei)(header_[12]) << 0 | // NOLINT
        (GLsizei)(header_[13]) << 8,  // NOLINT
        (GLsizei)(header_[14]) << 0 | // NOLINT
        (GLsizei)(header_[15]) << 8,  // NOLINT
    };
}


GLObject::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, 8,                              // NOLINT
    }
{
}


std::string GLObject::TGA::str_size_(Size size)
{
    return STR("{" << STR_JOIN(", ", it, it, size) << "}");
}


void GLObject::TGA::check_header_() const
{
    auto header = Header(size());
    if (header_ != header)
        STR_THROW(
            "Expected TGA header"                             << " "  <<
            "[" << STR_JOIN(", ", byte, byte, header)  << "]" << ", " <<
            "got"                                             << " "  <<
            "[" << STR_JOIN(", ", byte, byte, header_) << "]" << "."
        );
}


void GLObject::TGA::check_data_() const
{
    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


GLOBJECT_THREAD(GLObject::debug_,          {1})
GLOBJECT_THREAD(GLObject::debug_objects_,  {})
GLOBJECT_THREAD(GLObject::debug_callback_, {[](std::string const & debug)
{
    std::cerr << debug << std::endl;
}})


std::string GLObject::debug_name() const
{
    return STR_JOIN(" ", it, it, {
        object_type_  == 0              ? "<GLObject>" :
                                          str_object_type_(object_type_),
        object_label_,
        object_       == 0              ? "<INVALID>" :
        object_       == object_pseudo_ ? "<PSEUDO>"  :
                                          STR(object_),
    });
}


void GLObject::debug_label_() const
{
    if
    (
        object_type_ &&
        object_label_.size() &&
        object_
    )
    {
        if (supported({4, 3}, "GL_KHR_debug"))
        {
            auto static object_label_length_max = get_integer(
                GL_MAX_LABEL_LENGTH
            );
            auto object_label_length = std::min(
                (GLsizei)object_label_length_max,
                (GLsizei)object_label_.length()
            );
            glObjectLabel(
                object_type_,
                object_,
                object_label_length,
                object_label_.c_str()
            );
        }
    }
}


std::string static debug_objects_data_(
    std::string (GLObject::*debug_data)() const
)
{
    auto const & objects = GLObject::debug_objects();
    auto header = STR("GLObjects: " << objects.size());
    auto data   = STR_JOIN(
        "\n",
        object,
        "  " << (object->*debug_data)(),
        objects
    );
    return STR_JOIN("\n", it, it, {header, data});
}


std::string GLObject::debug_objects_name()
{
    return debug_objects_data_(&GLObject::debug_name);
}


std::string GLObject::debug_objects_info()
{
    return debug_objects_data_(&GLObject::debug_info);
}


std::string GLObject::debug_info() const
{
    return debug_name();
}


void GLObject::debug_objects_push_back_(GLObject * debug_object)
{
    debug_objects_.push_back(debug_object);
}


void GLObject::debug_objects_erase_(GLObject * debug_object)
{
    debug_objects_.erase(
        std::remove(
            debug_objects_.begin(),
            debug_objects_.end(),
            debug_object
        ),
        debug_objects_.end()
    );
}


/// Check


void GLObject::check_path_(std::string const & path)
{
    if (!path.size())
        STR_THROW(
            "Expected " << "non-empty path"     << ", " <<
            "got "      << "\"" << path << "\"" << "."
        );
}


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


void GLObject::check_supported_(
    Version version_min,
    std::string const & extension
)
{
    if (!supported(version_min, extension))
        STR_THROW(
            "Expected GL version " <<
            STR_JOIN(".", it, it, version_min) <<
            (extension.size() ? STR(" or extension " << extension) : "") << " "
            "got " << glGetString(GL_VERSION)                            << "."
        );
}


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


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


void GLObject::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:
        case GL_R3_G3_B2:
            check_supported_({1, 0});
            return;
        case GL_RGB4:
        case GL_RGB5:
        case GL_RGB8:
        case GL_RGB10:
        case GL_RGB12:
        case GL_RGB16:
        case GL_RGBA2:
        case GL_RGBA4:
        case GL_RGBA8:
        case GL_RGBA12:
        case GL_RGBA16:
        case GL_RGB5_A1:
        case GL_RGB10_A2:
            check_supported_({1, 1}, "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;
        // TODO(rcrnstn): Update GLEW headers.
        // 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_SIGNED_RED_RGTC1:
        case GL_COMPRESSED_RG_RGTC2:
        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_RGB16I:
        case GL_RGB32I:
        case GL_RGB8UI:
        case GL_RGB16UI:
        case GL_RGB32UI:
        case GL_RGBA8I:
        case GL_RGBA16I:
        case GL_RGBA32I:
        case GL_RGBA8UI:
        case GL_RGBA16UI:
        case GL_RGBA32UI:
            check_supported_({3, 0}, "GL_EXT_texture_integer");
            return;
        case GL_R16F:
        case GL_R32F:
        case GL_RG16F:
        case GL_RG32F:
        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_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:
            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_INDEX8:
            check_supported_({3, 0}, "GL_ARB_texture_stencil8");
            return;
        case GL_STENCIL_INDEX1:
        case GL_STENCIL_INDEX4:
        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_SIGNED_FLOAT:
        case GL_COMPRESSED_RGB_BPTC_UNSIGNED_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_R11_EAC:
        case GL_COMPRESSED_RG11_EAC:
        case GL_COMPRESSED_SIGNED_R11_EAC:
        case GL_COMPRESSED_SIGNED_RG11_EAC:
        case GL_COMPRESSED_RGB8_ETC2:
        case GL_COMPRESSED_SRGB8_ETC2:
        case GL_COMPRESSED_RGBA8_ETC2_EAC:
        case GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC:
        case GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2:
        case GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2:
            check_supported_({4, 2}, "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_({4, 2}, "GL_KHR_texture_compression_astc_ldr");
            return;
        default:
            STR_THROW(
                "Expected " << "supported internal format"           << ", "
                "got "      << str_internal_format_(internal_format) << "."
            );
    }
}


/// Fail


void GLObject::fail_action_(std::string const & action) const
{
    STR_THROW_NESTED("Failed to " << action << " " << debug_name() << ":");
}


/// String


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


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


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


std::string GLObject::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_UNDERFLOW)
        STR_CASE(GL_STACK_OVERFLOW)
        default:
            return str_enum_(error);
    }
}


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


std::string GLObject::str_format_(GLenum format)
{
    switch (format)
    {
        STR_CASE(GL_RED)
        STR_CASE(GL_R)
        STR_CASE(GL_RG)
        STR_CASE(GL_RGB)
        STR_CASE(GL_RGBA)
        STR_CASE(GL_BGR)
        STR_CASE(GL_BGRA)
        STR_CASE(GL_DEPTH_COMPONENT)
        STR_CASE(GL_STENCIL_INDEX)
        default:
            return str_enum_(format);
    }
}


std::string GLObject::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)
        default:
            return str_enum_(type);
    }
}


std::string GLObject::str_internal_format_(GLenum internal_format)
{
    switch (internal_format)
    {
        STR_CASE(GL_RED)
        STR_CASE(GL_RGB)
        STR_CASE(GL_RGBA)
        STR_CASE(GL_DEPTH_COMPONENT)
        STR_CASE(GL_STENCIL_INDEX)
        STR_CASE(GL_R3_G3_B2)
        STR_CASE(GL_RGB4)
        STR_CASE(GL_RGB5)
        STR_CASE(GL_RGB8)
        STR_CASE(GL_RGB10)
        STR_CASE(GL_RGB12)
        STR_CASE(GL_RGB16)
        STR_CASE(GL_RGBA2)
        STR_CASE(GL_RGBA4)
        STR_CASE(GL_RGBA8)
        STR_CASE(GL_RGBA12)
        STR_CASE(GL_RGBA16)
        STR_CASE(GL_RGB5_A1)
        STR_CASE(GL_RGB10_A2)
        STR_CASE(GL_COMPRESSED_RGB)
        STR_CASE(GL_COMPRESSED_RGBA)
        STR_CASE(GL_DEPTH_COMPONENT16)
        STR_CASE(GL_DEPTH_COMPONENT24)
        STR_CASE(GL_DEPTH_COMPONENT32)
        STR_CASE(GL_SRGB)
        STR_CASE(GL_SRGB8)
        STR_CASE(GL_SRGB_ALPHA)
        STR_CASE(GL_SRGB8_ALPHA8)
        STR_CASE(GL_COMPRESSED_SRGB)
        STR_CASE(GL_COMPRESSED_SRGB_ALPHA)
        // TODO(rcrnstn): Update GLEW headers.
        // STR_CASE(GL_SR8_EXT)
        // STR_CASE(GL_SRG8_EXT)
        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)
        STR_CASE(GL_COMPRESSED_RG)
        STR_CASE(GL_COMPRESSED_RED_RGTC1)
        STR_CASE(GL_COMPRESSED_SIGNED_RED_RGTC1)
        STR_CASE(GL_COMPRESSED_RG_RGTC2)
        STR_CASE(GL_COMPRESSED_SIGNED_RG_RGTC2)
        STR_CASE(GL_RGB16F)
        STR_CASE(GL_RGB32F)
        STR_CASE(GL_RGBA16F)
        STR_CASE(GL_RGBA32F)
        STR_CASE(GL_RGB8I)
        STR_CASE(GL_RGB16I)
        STR_CASE(GL_RGB32I)
        STR_CASE(GL_RGB8UI)
        STR_CASE(GL_RGB16UI)
        STR_CASE(GL_RGB32UI)
        STR_CASE(GL_RGBA8I)
        STR_CASE(GL_RGBA16I)
        STR_CASE(GL_RGBA32I)
        STR_CASE(GL_RGBA8UI)
        STR_CASE(GL_RGBA16UI)
        STR_CASE(GL_RGBA32UI)
        STR_CASE(GL_R16F)
        STR_CASE(GL_R32F)
        STR_CASE(GL_RG16F)
        STR_CASE(GL_RG32F)
        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_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_R11F_G11F_B10F)
        STR_CASE(GL_RGB9_E5)
        STR_CASE(GL_DEPTH_STENCIL)
        STR_CASE(GL_DEPTH24_STENCIL8)
        STR_CASE(GL_DEPTH32F_STENCIL8)
        STR_CASE(GL_DEPTH_COMPONENT32F)
        STR_CASE(GL_STENCIL_INDEX8)
        STR_CASE(GL_STENCIL_INDEX1)
        STR_CASE(GL_STENCIL_INDEX4)
        STR_CASE(GL_STENCIL_INDEX16)
        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_RGB10_A2UI)
        STR_CASE(GL_RGB565)
        STR_CASE(GL_COMPRESSED_RGB_BPTC_SIGNED_FLOAT)
        STR_CASE(GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT)
        STR_CASE(GL_COMPRESSED_RGBA_BPTC_UNORM)
        STR_CASE(GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM)
        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_RGB8_ETC2)
        STR_CASE(GL_COMPRESSED_SRGB8_ETC2)
        STR_CASE(GL_COMPRESSED_RGBA8_ETC2_EAC)
        STR_CASE(GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC)
        STR_CASE(GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2)
        STR_CASE(GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2)
        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);
    }
}
