/// Guards

#ifndef GLOBJECT_HPP_
#define GLOBJECT_HPP_


/// Includes

#include <string>

#include <glbase.hpp>
#include <gltraits.hpp>


/// GLObject

template<GLenum object_type>
class GLObject;

template<>
class GLObject<0> : protected GLBase
{

protected:

    //// Object

    std::string static label_(
        GLenum object_type,
        GLuint object
    );
    void static label_(
        GLenum              object_type,
        GLuint              object,
        std::string const & label
    );

    //// Name

    std::string static name_(
        GLenum              object_type,
        GLuint              object,
        std::string const & label = {}
    );

};

template<GLenum object_type>
class GLObject
:
    public    GLObject<0>,
    protected GLTraits::Object<object_type>
{

public:

    //// Special member functions

    template<typename... Args>
    explicit GLObject(std::string const & label, Args... args)
    try
    :
        object_{0}
    {
        if (debug() >= 2)
            this->debug_action_("construct", name_(object_type, 0, label));
        this->gen_objects(1, &object_, args...);
        try
        {
            // Strictly speaking `glObjectLabel` needs a *created* object.
            // `glGen*s` does not create objects (`glCreate*` does though), it
            // just reserves names for them. *Binding* that name for the first
            // time creates the object. Therefore, the below is not guaranteed
            // to work, but seems to on most drivers... Forcing our subclasses
            // to bind the name and call us back to set the label is deemed too
            // inconvenient so here we are.
            label_(object_type, object_, label);
            check_error_(glGetError());
        }
        catch (...)
        {
            this->delete_objects(1, &object_);
            object_ = 0;
            throw;
        }
    }
    catch (...)
    {
        this->fail_action_("construct", name_(object_type, 0, label));
    }

    ~GLObject()
    {
        if (debug() >= 2)
            debug_action_("destroy");
        this->delete_objects(1, &object_);
    }

    GLObject(GLObject && other) noexcept
    :
        object_{other.object_}
    {
        if (debug() >= 2)
            debug_action_("move construct");
        other.object_ = 0;
    }

    GLObject & operator=(GLObject && other) noexcept
    {
        if (debug() >= 2)
            debug_action_("move assign");
        if (&other != this)
        {
            this->delete_objects(1, &object_);
            object_ = other.object_;
            other.object_ = 0;
        }
        return *this;
    }

    GLObject(GLObject const &) = delete;
    GLObject & operator=(GLObject const &) = delete;

    //// Object

    GLBASE_GET(GLuint, object)

    operator GLuint() const { return object(); }

    //// Name

    std::string name() const
    {
        return name_(object_type, object_);
    }

protected:

    //// Debug

    void debug_action_(std::string const & action) const
    {
        return this->debug_action_(action, name());
    }

    //// Fail

    void fail_action_(std::string const & action) const
    {
        return this->fail_action_(action, name());
    }

private:

    //// Object

    GLuint object_;

};


/// Guards

#endif