#pragma once

#include "glm/ext/vector_float3.hpp"
#include "graphics/opengl/shader.hpp"

#include <glad/gl.h>

#include <glm/mat3x3.hpp>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>

#include <cstdint>
#include <initializer_list>
#include <memory>

namespace kuiper
{

namespace gl
{

    class program {
      public:
        using s_ptr = std::shared_ptr<program>;

        explicit program(GLuint id)
            : m_id(id) {}
        ~program() {
            destroy();
        }

        program(const program& other)                = delete;
        program& operator=(const program& other)     = delete;
        program(program&& other) noexcept            = default;
        program& operator=(program&& other) noexcept = default;

        static s_ptr make() noexcept;
        static s_ptr from_shaders(std::initializer_list<shader::s_ptr> shaders);

      public:
        GLuint id() const noexcept {
            return m_id;
        }

        void destroy() noexcept;
        void use() const noexcept;
        void attach_shader(const shader::s_ptr& shader) noexcept;
        bool link() noexcept;

        // Uniforms

        GLint uniform_location(const char* name) const noexcept;
        void  set_uint(const char* uniform, std::uint32_t value) noexcept;
        void  set_int(const char* uniform, std::int32_t value) noexcept;
        void  set_int_array(const char* uniform, const std::int32_t* value_arr, std::size_t len) noexcept;
        void  set_float(const char* uniform, float value) noexcept;
        void  set_vec3(const char* uniform, const glm::vec3& value) noexcept;
        void  set_uvec3(const char* uniform, const glm::uvec3& value) noexcept;
        void  set_uvec2(const char* uniform, const glm::uvec2& value) noexcept;
        void  set_mat3(const char* uniform, const glm::mat3& value) noexcept;
        void  set_mat4(const char* uniform, const glm::mat4& value) noexcept;

      private:
        GLuint m_id {0};
    };

} // namespace gl

} // namespace kuiper
