#pragma once
#include <functional>

#include "../util/logger.h"
#include "hotkeys.h"
#include "Module.h"
#include "impl/AimbotModule.h"
#include "impl/ChamsModule.h"
#include "impl/ConfigModule.h"
#include "impl/EspModule.h"
#include "impl/MapEditorModule.h"
#include "impl/MinimapModule.h"
#include "impl/ItemModule.h"
#include "impl/PlayerModule.h"
#include "impl/QuestModule.h"
#include "impl/AmmoHudModule.h"
#include "impl/WatermarkModule.h"
#include "impl/WeaponModule.h"
#include "impl/WorldModule.h"

class ModuleManager;
inline static ModuleManager* instance = nullptr;

class ModuleManager
{
public:
    ~ModuleManager()
    {
        for (auto module : modules)
        {
            delete static_cast<Module*>(module);
        }
    }

    ModuleManager()
        = default;

    void init() const
    {
        auto* self = const_cast<ModuleManager*>(this);
        hotkeys::clear_bindings();
#ifdef INCLUDE_AIMBOT
        hotkeys::register_aimbot(self->aimbot_module);
#endif
        hotkeys::register_esp(self->esp_module);
        hotkeys::register_minimap(self->minimap_module);
        hotkeys::register_player(self->player_module);
        hotkeys::register_weapon(self->weapon_module);
        hotkeys::register_world(self->world_module);
        hotkeys::register_ammo_hud(self->ammo_hud_module);

        for (auto module : modules)
        {
            auto* m = static_cast<Module*>(module);
            logger::info("  init: " + m->name);
            m->init();
            logger::info("  done: " + m->name);
        }
    }

    std::vector<PVOID> modules = std::vector<PVOID>();

    void apply(const std::function<void(Module*)>& function) const
    {
        for (auto module : modules)
        {
            function(static_cast<Module*>(module));
        }
    }

    template <typename T>
    T* module(T* value)
    {
        modules.push_back(static_cast<PVOID>(value));
        return value;
    }

    std::vector<Module*> get_modules() const
    {
        auto res = std::vector<Module*>();
        for (auto module : modules)
        {
            res.push_back(static_cast<Module*>(module));
        }
        return res;
    }

    // register modules here

    // aimbot
#ifdef INCLUDE_AIMBOT
    AimbotModule* aimbot_module = module(new AimbotModule());
#endif

    // visuals
    EspModule* esp_module = module(new EspModule());
    ChamsModule* chams_module = module(new ChamsModule());
    MapEditorModule* map_editor_module = module(new MapEditorModule());
    MinimapModule* minimap_module = module(new MinimapModule());

    // exploit
    PlayerModule* player_module = module(new PlayerModule());
    WorldModule* world_module = module(new WorldModule());
    WeaponModule* weapon_module = module(new WeaponModule());
    ItemModule* item_module = module(new ItemModule());
    QuestModule* quest_module = module(new QuestModule());

    // misc
    AmmoHudModule* ammo_hud_module = module(new AmmoHudModule());
    WatermarkModule* watermark_module = module(new WatermarkModule());

    // this has to get instantiated last
    ConfigModule* config_module = module(new ConfigModule(get_modules()));

    static ModuleManager* get_instance()
    {
        if (!instance)
        {
            instance = new ModuleManager();
        }
        return instance;
    }

    bool has_module(const std::string& name) const {
        for (auto module : modules) {
            if (static_cast<Module*>(module)->name == name) return true;
        }
        return false;
    }
};
