/// Guards

#ifndef GLBASE_HPP_
#define GLBASE_HPP_


/// Includes

#include <array>
#include <functional>
#include <stdexcept>
#include <string>
#include <utility>
#include <vector>

#ifndef GLBASE_INCLUDE
#define GLBASE_INCLUDE <GL/glew.h>
#endif
#include GLBASE_INCLUDE // IWYU pragma: export


/// Accessors

#define GLBASE_GET_(TYPE, NAME, STATIC, CONST) \
    TYPE STATIC const & NAME() CONST \
    { \
        return NAME##_; \
    }

#define GLBASE_SET_(TYPE, NAME, STATIC, CONST) \
    template<typename Type> \
    TYPE STATIC NAME(Type && NAME) \
    { \
        auto NAME##_old = std::move   (NAME##_); \
        NAME##_         = std::forward(NAME); \
        return NAME##_old; \
    }

#define GLBASE_GET(       TYPE, NAME) GLBASE_GET_(TYPE, NAME,, const)
#define GLBASE_SET(       TYPE, NAME) GLBASE_SET_(TYPE, NAME,, const)
#define GLBASE_GET_GLOBAL(TYPE, NAME) GLBASE_GET_(TYPE, NAME, static,)
#define GLBASE_SET_GLOBAL(TYPE, NAME) GLBASE_SET_(TYPE, NAME, static,)

#define GLBASE_ACCESS(TYPE, NAME) \
    GLBASE_GET(TYPE, NAME) \
    GLBASE_SET(TYPE, NAME)

#define GLBASE_ACCESS_GLOBAL(TYPE, NAME) \
    GLBASE_GET_GLOBAL(TYPE, NAME) \
    GLBASE_SET_GLOBAL(TYPE, NAME)

#define GLBASE_GLOBAL(NAME, INIT) \
    decltype(NAME) thread_local NAME INIT;


/// GLBase

class GLBase
{

public:

    //// Base

    using Version = std::array<GLint, 2>;

    GLBASE_ACCESS_GLOBAL(Version, version_max)

    bool static supported(
        Version             version_min,
        std::string const & extension = {}
    );

    char  static const * string (GLenum name);
    char  static const * string (GLenum name, GLuint index);
    GLint static         integer(GLenum name);

    //// Path

    using Path  = std::string;
    using Paths = std::vector<Path>;

    //// Exceptions

    struct Exception : std::runtime_error
    {
        using std::runtime_error::runtime_error;
    };

    //// Debug

    using DebugCallback = std::function<void (std::string const & message)>;

    GLBASE_GET_GLOBAL(int, debug)
    int static debug(int debug);

    GLBASE_ACCESS_GLOBAL(DebugCallback, debug_callback)

    void static debug_message(std::string const & message)
    {
        if (debug_callback())
            debug_callback()(message);
    }

    //// Check

    void static check_supported(
        Version             version_min,
        std::string const & extension = {}
    );

protected:

    //// Special member functions

    explicit GLBase() = default;
    ~GLBase() = default;

    //// Path

    Path static path_prefix_(
        Path const & path,
        Path const & prefix
    );

    //// TGA

    class TGA_
    {
    public:
        using Size = std::array<GLsizei, 2>;
        using Data = std::vector<GLubyte>;
        explicit TGA_(Size size, Data data, Path const & path = {});
        TGA_ static read  (Path const & path);
        void        write (Path const & path) const;
        GLBASE_GET(Size, size);
        GLBASE_GET(Data, data);
    protected:
        struct Header_ : std::array<GLubyte, 18>
        {
            explicit Header_(Size size);
            Size tga_size() const;
        };
        std::string static name_(Path const & path);
        void static check_header_(Header_ const & header);
        void        check_data_size_() const;
    private:
        Size size_;
        Data data_;
    };

    //// Debug

    void static debug_action_(
        std::string const & action,
        std::string const & name
    );

    //// Check

    void static check_path_(Path const & path);
    void static check_error_(GLenum error);
    void static check_type_(
        GLenum type,
        GLenum type_expected
    );
    void static check_format_(
        GLenum format,
        GLenum format_expected
    );
    void static check_internal_format_(
        GLenum internal_format
    );

    //// Fail

    [[noreturn]]
    void static fail_action_(
        std::string const & action,
        std::string const & name
    );

    //// String

    std::string static str_path_           (Path  const & path);
    std::string static str_paths_          (Paths const & paths);
    std::string static str_enum_           (GLenum name);
    std::string static str_error_          (GLenum error);
    std::string static str_object_type_    (GLenum object_type);
    std::string static str_glsl_           (GLenum glsl);
    std::string static str_format_         (GLenum format);
    std::string static str_type_           (GLenum type);
    std::string static str_internal_format_(GLenum internal_format);

private:

    //// Base

    Version static thread_local version_max_;

    //// Debug

    int           static thread_local debug_;
    DebugCallback static thread_local debug_callback_;

};


/// Guards

#endif