#ifndef GLTEXTUREBUFFER_HPP_
#define GLTEXTUREBUFFER_HPP_


#include <string>
#include <utility>

#include <gltexturend.hpp>


/// Class

template<typename Data>
class GLTextureBuffer : public GLTextureND<1>
{
public:

    /// Special member functions

    explicit GLTextureBuffer(
        std::string object_label,
        Size        size,
        GLenum      usage           = GL_STATIC_DRAW,
        GLenum      internal_format = DataTraits<Data>::internal_format
    );
    GLTextureBuffer(GLTextureBuffer && other) noexcept;
    virtual ~GLTextureBuffer();

    /// Core

    GLTextureBuffer static const & empty();

    GLOBJECT_GET(GLuint, buffer)

protected:

    /// Core

    void data_(
        void const * data,
        GLenum       target,
        GLenum       format,
        GLenum       type
    ) override;

    GLsizeiptr buffer_size_() const;

private:

    /// Core

    GLuint buffer_;
};


/// Special member functions

template<typename Data>
inline GLTextureBuffer<Data>::GLTextureBuffer(
    std::string object_label,
    Size        size,
    GLenum      usage,
    GLenum      internal_format
)
:
    GLTextureND(
        std::move(object_label),
        GL_TEXTURE_BUFFER,
        GL_TEXTURE_BINDING_BUFFER,
        GL_MAX_TEXTURE_BUFFER_SIZE,
        size,
        internal_format
    ),
    buffer_{0}
{
    try
    {
        check_supported_({3, 1}, "GL_ARB_texture_buffer_object");
        switch (internal_format)
        {
            case GL_RGB32F:
            case GL_RGB32I:
            case GL_RGB32UI:
                check_supported_({4, 0}, "GL_ARB_texture_buffer_object_rgb32");
        }
        glGenBuffers(1, &buffer_);
        glBindBuffer(GL_TEXTURE_BUFFER, buffer_);
        glBufferData(
            GL_TEXTURE_BUFFER,
            buffer_size_(),
            nullptr,
            usage
        );
        glTexBuffer(target_, internal_format, buffer_);
        check_error_(glGetError());
    }
    catch (...)
    {
        glDeleteBuffers(1, &buffer_);
        fail_action_("create");
    }
}

template<typename Data>
inline GLTextureBuffer<Data>::GLTextureBuffer(
    GLTextureBuffer && other
) noexcept
:
    buffer_{other.buffer_}
{
    other.buffer_ = 0;
}

template<typename Data>
inline GLTextureBuffer<Data>::~GLTextureBuffer()
{
    glDeleteBuffers(1, &buffer_);
}

/// Core

template<typename Data>
inline GLTextureBuffer<Data> const & GLTextureBuffer<Data>::empty()
{
    return empty_<GLTextureBuffer>();
}

template<typename Data>
inline void GLTextureBuffer<Data>::data_(
    void const * data,
    GLenum       target,
    GLenum       format,
    GLenum       type
)
{
    check_format_(format, DataTraits<Data>::format);
    check_type_  (type,   DataTraits<Data>::type);
    glBufferSubData(
        target,
        0,
        buffer_size_(),
        data
    );
}

template<typename Data>
inline GLsizeiptr GLTextureBuffer<Data>::buffer_size_() const
{
    return (GLsizeiptr)(data_size_() * sizeof(Data));
}


#endif // GLTEXTUREBUFFER_HPP_
