#include "pluginmanager.h"

#include <regex>
#include "plugins.h"
#include "config/profile.h"
#include "core/convar/concommand.h"

PluginManager* g_pPluginManager;

const std::vector<Plugin>& PluginManager::GetLoadedPlugins() const
{
	return this->plugins;
}

const std::optional<Plugin> PluginManager::GetPlugin(HMODULE handle) const
{
	for (const Plugin& plugin : GetLoadedPlugins())
		if (plugin.m_handle == handle)
			return plugin;
	return std::nullopt;
}

void PluginManager::LoadPlugin(fs::path path, bool reloaded)
{
	Plugin plugin = Plugin(path.string());

	if (!plugin.IsValid())
	{
		NS::log::PLUGINSYS->warn("Unloading invalid plugin '{}'", path.string());
		plugin.Unload();
		return;
	}

	plugins.push_back(plugin);
	plugin.Init(reloaded);
}

inline void FindPlugins(fs::path pluginPath, std::vector<fs::path>& paths)
{
	// ensure dirs exist
	if (!fs::exists(pluginPath) || !fs::is_directory(pluginPath))
	{
		return;
	}

	for (const fs::directory_entry& entry : fs::directory_iterator(pluginPath))
	{
		if (fs::is_regular_file(entry) && entry.path().extension() == ".dll")
			paths.emplace_back(entry.path());
	}
}

bool PluginManager::LoadPlugins(bool reloaded)
{
	if (strstr(GetCommandLineA(), "-noplugins") != NULL)
	{
		NS::log::PLUGINSYS->warn("-noplugins detected; skipping loading plugins");
		return false;
	}

	fs::create_directories(GetThunderstoreModFolderPath());

	std::vector<fs::path> paths;

	pluginPath = GetNorthstarPrefix() + "\\plugins";

	fs::path libPath = fs::absolute(pluginPath + "\\lib");
	if (fs::exists(libPath) && fs::is_directory(libPath))
		AddDllDirectory(libPath.wstring().c_str());

	FindPlugins(pluginPath, paths);

	// Special case for Thunderstore mods dir
	std::filesystem::directory_iterator thunderstoreModsDir = fs::directory_iterator(GetThunderstoreModFolderPath());
	// Set up regex for `AUTHOR-MOD-VERSION` pattern
	std::regex pattern(R"(.*\\([a-zA-Z0-9_]+)-([a-zA-Z0-9_]+)-(\d+\.\d+\.\d+))");
	for (fs::directory_entry dir : thunderstoreModsDir)
	{
		fs::path pluginsDir = dir.path() / "plugins";
		// Use regex to match `AUTHOR-MOD-VERSION` pattern
		if (!std::regex_match(dir.path().string(), pattern))
		{
			spdlog::warn("The following directory did not match 'AUTHOR-MOD-VERSION': {}", dir.path().string());
			continue; // skip loading package that doesn't match
		}

		fs::path libDir = fs::absolute(pluginsDir / "lib");
		if (fs::exists(libDir) && fs::is_directory(libDir))
			AddDllDirectory(libDir.wstring().c_str());

		FindPlugins(pluginsDir, paths);
	}

	if (paths.empty())
	{
		NS::log::PLUGINSYS->warn("Could not find any plugins. Skipped loading plugins");
		return false;
	}

	for (fs::path path : paths)
	{
		LoadPlugin(path, reloaded);
	}

	InformAllPluginsInitialized();

	return true;
}

void PluginManager::ReloadPlugins()
{
	for (const Plugin& plugin : this->GetLoadedPlugins())
	{
		plugin.Unload();
	}

	this->plugins.clear();
	this->LoadPlugins(true);
}

void PluginManager::RemovePlugin(HMODULE handle)
{
	for (size_t i = 0; i < plugins.size(); i++)
	{
		Plugin* plugin = &plugins[i];
		if (plugin->m_handle == handle)
		{
			plugins.erase(plugins.begin() + i);
			return;
		}
	}
}

void PluginManager::InformAllPluginsInitialized() const
{
	for (const Plugin& plugin : GetLoadedPlugins())
	{
		plugin.Finalize();
	}
}

void PluginManager::InformSqvmCreated(CSquirrelVM* sqvm) const
{
	for (const Plugin& plugin : GetLoadedPlugins())
	{
		plugin.OnSqvmCreated(sqvm);
	}
}

void PluginManager::InformSqvmDestroying(CSquirrelVM* sqvm) const
{
	for (const Plugin& plugin : GetLoadedPlugins())
	{
		plugin.OnSqvmDestroying(sqvm);
	}
}

void PluginManager::InformDllLoad(HMODULE module, fs::path path) const
{
	std::string fn = path.filename().string(); // without this the string gets freed immediately lmao
	const char* filename = fn.c_str();
	for (const Plugin& plugin : GetLoadedPlugins())
	{
		plugin.OnLibraryLoaded(module, filename);
	}
}

void PluginManager::RunFrame() const
{
	for (const Plugin& plugin : GetLoadedPlugins())
	{
		plugin.RunFrame();
	}
}

void ConCommand_reload_plugins(const CCommand& args)
{
	g_pPluginManager->ReloadPlugins();
}

ON_DLL_LOAD_RELIESON("engine.dll", PluginManager, ConCommand, (CModule module))
{
	RegisterConCommand("reload_plugins", ConCommand_reload_plugins, "reloads plugins", FCVAR_NONE);
}