#pragma once

#include <optional>
#include <cassert>
#include "./module_types.hpp"

namespace world::module {

class Module;
class Registry {
public:
    static _FORCE_INLINE_ Registry *Get() {
        assert(sInstance != nullptr && "Uninitialized registry");
        return sInstance;
    }

    /// Load mods and populate registry
    static void Load();
    static void Unload();

    std::optional<module_id> findModuleId(const std::string& mod) const;

    void reserveMaterials(size_t n);
    std::optional<material_id> findMaterialId(module_id mod_id, const std::string& mat) const;
    std::optional<material_id> findMaterialId(const std::string& mod, const std::string& mat) const;
    std::optional<texture_id> findTextureId(const std::string& path) const;
    std::optional<texture_id> findTextureId(material_id mat_id) const;
    material_id addMaterial(module_id mod_id, const material&);
    std::optional<model_id> findModelId(module_id module_id, const std::string& model) const;
    std::optional<model_id> findModelId(const std::string& module, const std::string& model) const;
    model_id addModel(module_id mod_id, const model&);
    generator_id addGenerator(module_id mod_id, const generator&);
    std::optional<generator_id> findGeneratorId(module_id mod_id, const std::string& gen) const;
    std::optional<generator_id> findGeneratorId(const std::string& mod, const std::string& gen) const;
    owner<world::generator::Abstract*> createGenerator(generator_id id, generator::params) const;

    struct Loader {
        const module_id id;
        Registry *const reg;

        void reserveMaterials(size_t n) { reg->reserveMaterials(n + reg->getMaterials().size()); }
        material_id addMaterial(const material &m) { return reg->addMaterial(id, m); }
        model_id addModel(const model &m) { return reg->addModel(id, m); }
        generator_id addGenerator(const generator &g) { return reg->addGenerator(id, g); }
    };

    using textures = std::vector<std::string>;
    _FORCE_INLINE_ const textures& getTextures() {
        return _textures;
    }
    _FORCE_INLINE_ const materials& getMaterials() {
        return _materials;
    }

    using models = std::vector<registered_model>;
    template<typename F>
    void extractModels(const F& f) {
        for (auto& model: _models)
            f(model);
    }

    using generators = std::vector<registered_generator>;

private:
    Registry();
    ~Registry() {}

    static owner<Registry*> sInstance;

    struct ModHandle {
        ModHandle(void* h, Module* i): handle(h), instance(i) { }
        ~ModHandle();
        owner<void*> handle;
        owner<Module *const> instance;
    };
    std::vector<ModHandle> modules;

    materials _materials;
    textures _textures;
    models _models;
    generators _generators;
};

class Module {
public:
    // default constructor
    virtual ~Module(){};

    /// Module unique name
    virtual const char* getName() const = 0;
    /// List or required modules (zero separated)
    virtual const char *getRequirements() const = 0;
    virtual void setup(Registry::Loader loader) = 0;

    /// Signature of loadModule static function
    typedef Module* load_fn();
};
}
