#ifndef GLTEXTURE_HPP_
#define GLTEXTURE_HPP_


#include <cstddef>
#include <string>
#include <vector>

#include <globject.hpp>


/// Class

class GLTexture : public GLObject<GL_TEXTURE, glGenTextures, glDeleteTextures>
{
public:

    /// Special member functions

    explicit GLTexture(
        std::string object_label,
        GLenum      target,
        GLenum      binding,
        GLenum      internal_format = 0,
        GLenum      wrap            = 0,
        GLenum      min_filter      = 0,
        GLenum      mag_filter      = 0
    );

    /// Core

    GLOBJECT_ACCESS_THREAD(GLfloat, anisotropy)

    GLint unit(bool force_active = false) const;

    template<typename Data = GLubyte>
    std::vector<Data> data(
        GLenum target    = 0,
        GLenum format    = DataTraits<Data>::format,
        GLint  alignment = 1
    ) const;

    template<typename Data = GLubyte>
    GLTexture & data(
        std::vector<Data> const & data,
        GLenum                    target    = 0,
        GLenum                    format    = DataTraits<Data>::format,
        GLint                     alignment = 1
    );

    template<typename Data = GLubyte>
    GLTexture & clear(
        Data   value     = Data{},
        GLenum target    = 0,
        GLenum format    = DataTraits<Data>::format,
        GLint  alignment = 1
    );

    /// Path

    GLOBJECT_ACCESS_THREAD(Path, prefix)

protected:

    /// Core

    template<typename GLTextureDerived>
    GLTextureDerived static const & empty_();

    bool min_filter_mipmap_() const;

    std::size_t virtual data_size_() const = 0;

    void virtual data_(
        void const * data,
        GLenum       target,
        GLenum       format,
        GLenum       type
    ) = 0;

    /// Check

    void check_unit_active_() const;
    void check_unit_texture_() const;
    void check_data_size_(std::size_t data_size) const;

    /// String

    std::string static str_target_(GLenum target);

protected:

    GLenum const target_;

private:

    /// Core

    GLfloat static thread_local anisotropy_;

    GLenum const   binding_;
    GLenum const   min_filter_;
    GLuint mutable unit_;

    /// Path

    Path static thread_local prefix_;
};


/// Core

template<typename Data>
inline std::vector<Data> GLTexture::data(
    GLenum target,
    GLenum format,
    GLint  alignment
) const
try
{
    if (!target)
        target = target_;
    auto data = std::vector<Data>(data_size_());
    unit(true);
    glPixelStorei(GL_PACK_ALIGNMENT, alignment);
    glGetTexImage(target, 0, format, DataTraits<Data>::type, data.data());
    GLOBJECT_DEBUG_IF(1)
        check_error_(glGetError());
    return data;
}
catch (...)
{
    fail_action_("get data of");
}

template<typename Data>
inline GLTexture & GLTexture::data(
    std::vector<Data> const & data,
    GLenum                    target,
    GLenum                    format,
    GLint                     alignment
)
try
{
    if (!target)
        target = target_;
    check_data_size_(data.size());
    unit(true);
    glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
    data_(data.data(), target, format, DataTraits<Data>::type);
    GLOBJECT_DEBUG_IF(1)
        check_error_(glGetError());
    if (min_filter_mipmap_())
        if (supported({3, 0}, "GL_ARB_framebuffer_object"))
            glGenerateMipmap(target);
    return *this;
}
catch (...)
{
    fail_action_("set data of");
}

template<typename Data>
GLTexture & GLTexture::clear(
    Data   value,
    GLenum target,
    GLenum format,
    GLint  alignment
)
{
    auto static clear = std::vector<Data>{};
    clear.resize(data_size_());
    std::fill(clear.begin(), clear.end(), value);
    data(clear, target, format, alignment);
    return *this;
}

template<typename GLTextureDerived>
inline GLTextureDerived const & GLTexture::empty_()
{
    auto static const empty = []()
    {
        auto size = typename GLTextureDerived::Size{};
        size.fill(1);
        auto tmp = GLTextureDerived("empty", size);
        tmp.clear();
        return tmp;
    }();
    return empty;
}


#endif // GLTEXTURE_HPP_