#pragma once

#include "graphics/opengl/shader.hpp"
#include "graphics/opengl/texture.hpp"
#include "resources/material.hpp"
#include "resources/model.hpp"
#include "utils/xoshiro256plusplus.hpp"

#include <absl/container/flat_hash_map.h>

#include <concepts>
#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <utility>

namespace kuiper
{

using resource_id_t = std::uint32_t;

enum class resource_type : std::uint8_t {
    none = 0,
    texture,
    shader,
    model
};

struct resource_metadata {
    resource_id_t id {0};
    resource_type type {resource_type::none};
    std::string   name {};
};

class resource_manager {
  public:
    using metadata_map_t = absl::flat_hash_map<resource_id_t, resource_metadata>;
    using texture_map_t  = absl::flat_hash_map<resource_id_t, gl::texture::s_ptr>;
    using shader_map_t   = absl::flat_hash_map<resource_id_t, gl::shader::s_ptr>;
    using model_map_t    = absl::flat_hash_map<resource_id_t, resource::model>;
    using material_map_t = absl::flat_hash_map<resource_id_t, resource::material>;

    template<typename T>
    resource_metadata add(T&& resource, std::string_view name = {}) {
        const auto    new_id = static_cast<resource_id_t>(m_prng.next());
        resource_type type   = resource_type::none;

        if constexpr (std::same_as<T, resource::model>) {
            type = resource_type::model;
            m_models.insert({new_id, std::forward<T>(resource)});
        } else {
            static_assert(!"non-exhaustive if constexpr");
        }

        resource_metadata metadata {new_id, type, std::string(name)};
        m_metadata.insert({new_id, metadata});
        return metadata;
    }

    template<typename T>
    std::optional<T> get(resource_id_t id) {
        if constexpr (std::same_as<T, resource::model>) {
            if (m_models.contains(id)) {
                return m_models.at(id);
            } else {
                return std::nullopt;
            }
        } else {
            static_assert(!"non-exhaustive if constexpr");
        }

        return std::nullopt;
    }

    std::optional<resource_metadata> import_from_path(const std::filesystem::path& path);

    const metadata_map_t& metadata() const noexcept {
        return m_metadata;
    }

    const model_map_t& models() const noexcept {
        return m_models;
    }

  private:
    random::xoshiro256pp m_prng {random::xoshiro256pp::from_random_device()};

    metadata_map_t m_metadata {};
    texture_map_t  m_textures {};
    shader_map_t   m_shaders {};
    model_map_t    m_models {};
    material_map_t m_materials {};
};

} // namespace kuiper
