#ifndef PW_VISUAL_SHADER_HPP
#define PW_VISUAL_SHADER_HPP

#include <pw/core/debug.hpp>
#include <pw/core/globals.hpp>
#include <pw/core/matrix.hpp>
#include <pw/core/vector.hpp>
#include <pw/visual/texture.hpp>

#include <map>
#include <variant>

namespace pw {

struct shader final {
    shader();
    ~shader();

    enum class code_type { vertex, fragment, geometry, compute };

    void set_source(code_type t, const std::string& c) { _source[t] = c; }
    std::string source(code_type t) { return _source[t]; }

    bool ready() const;

    using uniform_t = std::variant<bool, int, float, double, vector2f, vector3f,
                                   vector4f, matrix4x4f>;
    using uniform_entry_t = std::tuple<std::string, uniform_t, int>;


    shader& set_uniform_at_location(int location, float v);
    shader& set_uniform_at_location(int location, uint32_t v);
    shader& set_uniform_at_location(int location, int32_t v);
    shader& set_uniform_at_location(int location, matrix4x4f const& v);
    shader& set_uniform_at_location(int location, vector4f const& v);
    shader& set_uniform_at_location(int location, texture const& t);

    /**
     * @brief retrieves the position of a uniform
     * @param name of the uniform
     * @return position of the uniform or negative if it doesn't exist
     */
    int uniform_location(std::string const& name) const;

    /**
     * @brief check if a uniform with the given name exists
     * @param name of the uniform
     * @return true if found
     */
    bool has_uniform(std::string const& name) const {
        return uniform_location(name) >= 0;
    }

    /**
     * sets data of the
     */
    template <typename T>
    shader& set_uniform(std::string const& name, T&& value) {
        return set_uniform_at_location(uniform_location(name),
                                       std::forward<T>(value));
    }

    bool build();

    void use();


    using uniform_cache_t = std::vector<uniform_entry_t>;

    void set_uniforms(uniform_cache_t c);

    uint32_t native_handle() const;

  protected:
    std::unordered_map<code_type, std::string> _source;

    struct impl;
    std::unique_ptr<impl> _impl;
};

} // namespace pw

#endif
