#ifndef GLSHADER_SHADER_HPP_
#define GLSHADER_SHADER_HPP_


#include <map>
#include <set>
#include <string>

#include <GL/glew.h>


class Shader
{
public:

    using Paths = std::set<std::string>;

    explicit Shader(Paths const & paths);
    virtual ~Shader();
    Shader(Shader &&) noexcept;
    Shader(Shader const &) = delete;
    Shader & operator=(Shader &&) = delete;
    Shader & operator=(Shader const &) = delete;

    using Defines = std::map<std::string, std::string>;
    using Locations = std::map<std::string, GLuint>;

    static void root(std::string const & root);
    static void defines(Defines const & defines);
    static void verts(Locations const & verts);
    static void frags(Locations const & frags);

    GLuint program() const;

    Shader & validate();
    Shader & use();

protected:

    void validate_() const;
    void current_(
        std::string const & error
    ) const;

    GLuint             program_;
    std::string        program_name_;
    std::string static root_;
    Defines     static defines_;
    Locations   static verts_;
    Locations   static frags_;
};


// Debug macros.

#ifndef NDEBUG
    #define GLSHADER_DEBUG_(...) __VA_ARGS__
#else
    #define GLSHADER_DEBUG_(...)
#endif // NDEBUG


// Inline definitions.

#define GLSHADER_SET_(TYPE, NAME) \
    inline void Shader::NAME(TYPE const & NAME) \
    { \
        NAME##_ = NAME; \
    }
GLSHADER_SET_(std::string, root)
GLSHADER_SET_(Defines, defines)
GLSHADER_SET_(Locations, verts)
GLSHADER_SET_(Locations, frags)

inline GLuint Shader::program() const
{
    return program_;
}

inline Shader & Shader::validate()
{
    GLSHADER_DEBUG_(validate_();)
    return *this;
}

inline Shader & Shader::use()
{
    glUseProgram(program_);
    return *this;
}


#endif // GLSHADER_SHADER_HPP_
