#ifndef _H_RENDERER_H
#define _H_RENDERER_H

#include <unordered_map>
#include <memory>
#include <variant>
#include <string>
#include <glm/glm.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "graphics/quad.h"
#include "graphics/postprocess.h"

class ResourceManager;
class Shader;

enum class RenderLayer {
    Background,
    Map,
    GameObjects,
    Effects,
    HUD,
    Menu
};

using UniformValue = std::variant<
    int,
    bool,
    float,
    double,
    glm::mat4,
    glm::vec2
>;

struct Uniform {
    std::string name;
    UniformValue value;
};

class Drawable
{
public:
    virtual void draw() = 0;
    const unsigned getShaderID() const { return shaderID; }
    const std::vector<Uniform>& getUniforms() { return uniforms; }
    const std::vector<Uniform>& getOneShotUniforms() { return oneShotUniforms; }
    void clearOneShot() { oneShotUniforms.clear(); }
    void clearUniforms() { uniforms.clear(); }
protected:
    unsigned shaderID;
    template <typename T>
    void editUniform(const std::string& name, T value)
    {
        uniforms.emplace_back(Uniform {name, value});
    }

    template <typename T>
    void editUniformOnce(const std::string& name, T value)
    {
        oneShotUniforms.emplace_back(Uniform {name, value});
    }
private:
    std::vector<Uniform> uniforms;
    std::vector<Uniform> oneShotUniforms;
};

class Renderer
{
public:
    Renderer(const std::shared_ptr<ResourceManager>&);

    void clear();

    void hookEventManager(const std::weak_ptr<EventManager> eventManager);

    void setProjAndViewMatrix(const glm::mat4& proj, const glm::mat4& view);
    void addDrawable(RenderLayer renderLayer, std::shared_ptr<Drawable> drawable);
    void removeDrawable(RenderLayer renderLayer, std::shared_ptr<Drawable> drawable);

    void render();

private:
    std::unordered_map<RenderLayer, std::vector<std::shared_ptr<Drawable>>> worldLayerPool;
    std::unordered_map<RenderLayer, std::vector<std::shared_ptr<Drawable>>> HUDLayerPool;
    std::vector<RenderLayer> renderingOrder = {
        RenderLayer::Background,
        RenderLayer::Map,
        RenderLayer::GameObjects,
        RenderLayer::Effects,
        RenderLayer::HUD,
        RenderLayer::Menu
    };

    void uploadUniforms(const unsigned shaderID, const std::vector<Uniform>& uniforms);

    glm::mat4 projMat;
    glm::mat4 viewMat;

    union FrameBuffer {
        unsigned int frame;
        unsigned int texture;
    }worldBuffer, hudBuffer;

    std::unique_ptr<ScreenQuad> screenQuad;

    std::shared_ptr<ResourceManager> resourceManager;

    void initFrameBuffers();

    std::unique_ptr<Postprocessor> postProcessor;

    void sortLayerPool(auto& layerPool);
    void renderPool(auto& layerPool);
};

#endif