#pragma once

#include <glm/mat4x4.hpp>
#include <map>
#include <string>
#include <render/core/object.h>
#include <common.h>

#define SHADER_VERTEX   0
#define SHADER_FRAGMENT 1
#define SHADER_GEOMETRY 2

namespace render{

class uniform;

class shader:_object
{
public:
    shader();
    ~shader();

    void bind() const;
    void unbind() const;

    bool attach(const std::string&, uint);
    bool attachSource(const char*, uint);
    bool linkProgram(bool binaryHint = 0);
    bool validate();

    bool loadBinary(const std::string&);
    bool saveBinary(const std::string&);

    bool load(const std::string&);

    virtual void free();

    uint getProgram() const;

    uniform &operator[](const std::string&) const;

    int getUniformLocation(const std::string&) const;

    void bindAttribLocation(const std::string&, uint);
    uint getAttribLocation(const std::string&);

    void loadProjectionMatrix(const float*) const;
    void loadModelMatrix(const float*) const;
    void loadViewMatrix(const float*) const;
    void loadNormalMatrix(const float*) const;

    void uniform1f(const std::string&, float);
    void uniform2f(const std::string&, float, float);
    void uniform3f(const std::string&, float, float, float);
    void uniform4f(const std::string&, float, float, float, float);

    void uniform1i(const std::string&, int);
    void uniform2i(const std::string&, int, int);
    void uniform3i(const std::string&, int, int, int);
    void uniform4i(const std::string&, int, int, int, int);

    void uniform4x4(const std::string&, uint, const float*) const;
    void uniform3x3(const std::string&, uint, const float*) const;
private:
    bool _linked;
    virtual void _alloc() override;
    static void _printSError(uint i);
    static void _printPError(uint i);
    uint shaders[3], program, _attachedShaders;

    typedef std::map<std::string, uniform*> _utype;

    bool _nMatr, _mMatr;

    _utype _uniforms;

    void _getAllUniforms();
};

class uniform
{
friend class shader;
public:
    void operator=(float) const noexcept;
    void operator=(const vec2f&) const noexcept;
    void operator=(const vec3f&) const noexcept;
    void operator=(const vec4f&) const noexcept;

    void operator=(const glm::mat4&) const noexcept;
private:
    uniform(uint id, shader *root);

    uint _id;
    shader *_root;

    int _iv;
    float _fv;
    vec2f _f2v;
    vec3f _f3v;
    vec4f _f4v;

    glm::mat4 _m4v;
};

namespace shaderManager
{

void init();
void free();

shader *getShader(const std::string &name);
shader *loadShader(const std::string &name);

}

}
