#include "STDInc.hpp"

namespace Components
{
	std::unordered_map<std::string, int> GSC::ScriptMainHandles;
	std::unordered_map<std::string, int> GSC::ScriptInitHandles;

	std::unordered_map<std::string, Game::scr_function_t> GSC::CustomScrFunctions;
	std::unordered_map<std::string, Game::scr_method_t> GSC::CustomScrMethods;

	void GSC::AddFunction(const char* name, Game::xfunction_t func, int type)
	{
		Game::scr_function_t toAdd;
		toAdd.function = func;
		toAdd.developer = type;

		GSC::CustomScrFunctions.insert_or_assign(Utils::String::ToLower(name), toAdd);
	}

	void GSC::AddMethod(const char* name, Game::xmethod_t method, int developer)
	{
		Game::scr_method_t toAdd;
		toAdd.function = method;
		toAdd.developer = developer;

		GSC::CustomScrMethods.insert_or_assign(Utils::String::ToLower(name), toAdd);
	}

	void GSC::AddMethods()
	{
		GSC::AddMethod("allowFire", [](Game::scr_entref_t entref)
		{
			const auto* ent = Game::GetPlayerEntity(entref);

			if (Game::Scr_GetInt(0))
			{
				Dvars::p_allowFire->current.enabled = true;
				Dvars::p_allowFire->latched.enabled = true;
			}
			else
			{
				Dvars::p_allowFire->current.enabled = false;
				Dvars::p_allowFire->latched.enabled = false;
			}
		}, false);

		GSC::AddMethod("IsReloading", [](Game::scr_entref_t entref)
		{
			const auto* ent = Game::GetPlayerEntity(entref);

			if (ent->client->ps.weaponstate >= Game::WEAPON_RELOADING && ent->client->ps.weaponstate <= Game::WEAPON_RELOAD_END)
				return Game::Scr_AddInt(true);
			else
				return Game::Scr_AddInt(false);
		}, false);

		GSC::AddMethod("IsSprinting", [](Game::scr_entref_t entref)
		{
			const auto* ent = Game::GetPlayerEntity(entref);
			if (ent->client->ps.pm_flags & Game::PMF_SPRINTING)
				return Game::Scr_AddInt(true);
			else
				return Game::Scr_AddInt(false);
		}, false);

		GSC::AddMethod("IsUsingNVG", [](Game::scr_entref_t entref)
		{
			const auto* ent = Game::GetPlayerEntity(entref);
			if (ent->client->ps.weaponstate >= Game::WEAPON_NIGHTVISION_WEAR && ent->client->ps.weaponstate <= Game::WEAPON_NIGHTVISION_REMOVE)
				return Game::Scr_AddInt(true);
			else
				return Game::Scr_AddInt(false);
		}, false);

		GSC::AddMethod("isMantling", [](Game::scr_entref_t entref)
		{
			const auto* ent = Game::GetPlayerEntity(entref);
			if (ent->client->ps.pm_flags & Game::PMF_MANTLE)
				return Game::Scr_AddInt(true);
			else
				return Game::Scr_AddInt(false);
		}, false);

		GSC::AddMethod("isSwapping", [](Game::scr_entref_t entref)
		{
			const auto* ent = Game::GetPlayerEntity(entref);
			if (ent->client->ps.weaponstate >= Game::WEAPON_DROPPING && ent->client->ps.weaponstate <= Game::WEAPON_DROPPING_QUICK)
				return Game::Scr_AddInt(true);
			else
				return Game::Scr_AddInt(false);
		}, false);
	}

	void GSC::AddFunctions()
	{
		GSC::AddFunction("exec", []
		{
			const auto str = Game::Scr_GetString(0);

			if (str == nullptr)
			{
				Game::Scr_Error("^1Exec: Illegal parameter!\n");
				return;
			}

			Command::Execute(str);
		}, false);

		GSC::AddFunction("ToUpper", []
		{
			const auto str = Game::Scr_GetString(0);

			if (str == nullptr)
			{
				Game::Scr_Error("^1Scr_GetString: Illegal parameter!\n");
				return;
			}

			return Game::Scr_AddString(Utils::String::ToUpper(str).c_str());
		}, false);
	}

	Game::xmethod_t GSC::Player_GetMethod_Stub(const char** name)
	{
		auto got = GSC::CustomScrMethods.find(*name);

		if (got == GSC::CustomScrMethods.end())
			return Game::Player_GetMethod(name);

		return got->second.function;
	}

	Game::xfunction_t GSC::Scr_GetFunction_Stub(const char** name, int* isDev)
	{
		auto got = GSC::CustomScrFunctions.find(*name);

		if (got == GSC::CustomScrFunctions.end())
			return Game::Scr_GetFunction(name, isDev);

		*isDev = got->second.developer;
		return got->second.function;
	}

	void GSC::LoadScript(const std::string& path)
	{
		if (!Game::Scr_LoadScript(path.data()))
		{
			Game::Com_Printf(0, Utils::String::VA("The script %s encountered an error during the loading process.\n", path.data()));
			return;
		}

		Game::Com_Printf(0, Utils::String::VA("The script %s has been loaded successfully.\n", path.data()));

		const auto initHandle = Game::Scr_GetFunctionHandle(path.data(), "init");
		if (initHandle != 0)
		{
			ScriptInitHandles.insert_or_assign(path, initHandle);
		}

		const auto mainHandle = Game::Scr_GetFunctionHandle(path.data(), "main");
		if (mainHandle != 0)
		{
			ScriptMainHandles.insert_or_assign(path, mainHandle);
		}
	}

	void GSC::LoadScriptsFromFastFiles()
	{
		// Clear handles (from previous GSC loading session)
		ScriptMainHandles.clear();
		ScriptInitHandles.clear();

		//Support exclusively loading scripts from fast files.
		FastFiles::EnumAssets(Game::ASSET_TYPE_RAWFILE, [](Game::XAssetHeader header)
		{
			std::string name = header.rawfile->name;

			if (name.ends_with(".gsc") && name.starts_with("scripts/"))
			{
				// Scr_LoadScriptInternal will add the '.gsc' suffix so we remove it
				const std::string path = name.substr(0, name.size() - 4);
				std::string middlePath = path.substr(path.rfind('/', path.find_last_of('/') - 1) + 1, path.find_last_of('/') - path.rfind('/', path.find_last_of('/') - 1) - 1);

				// Scripts folder:
				// 'scripts/[name_script].gsc' - global scripts
				// 'scripts/[level_name]/[name_script].gsc' - scripts for the specific level.
				if (name.contains("scripts/"s + middlePath + "/"))
				{
					if (middlePath == Dvars::Functions::Dvar_FindVar("mapname")->current.string)
					{
						LoadScript(path);
						//Game::Com_Printf(0, "^2The script %s is intended for the current map.\n", path.data());
					}
					else
					{
						//Game::Com_Printf(0, "^3The script %s has been loaded in the ff file, but it is not intended for the current level.\n", path.data());
					}
				}
				else
				{
					//Game::Com_Printf(0, "^1The script %s is meant for global use.\n", name.data());
					LoadScript(path);
				}
			}

		}, false);
	}

	void __declspec(naked) GSC::GScr_LoadScripts_Stub()
	{
		const static uint32_t GScr_LoadScriptsForEntities_func = 0x4C6680;
		const static uint32_t retn_addr = 0x4C6AF8;
		__asm
		{
			pushad;
			call	GSC::LoadScriptsFromFastFiles;
			popad;

			call	GScr_LoadScriptsForEntities_func;
			jmp		retn_addr;
		}
	}

	void GSC::G_RunFrame_Stub(int extent, int timeCap)
	{
		for (const auto& handle : ScriptInitHandles)
		{
			const auto thread = Game::Scr_ExecThread(handle.second);
			Game::Scr_FreeThread(static_cast<std::uint16_t>(thread));
		}

		Utils::Hook::Call<int(int, int)>(0x4BAB40)(extent, timeCap); //G_RunFrame
	}

	void GSC::Scr_LoadLevel_Stub()
	{
		for (const auto& handle : ScriptMainHandles)
		{
			const auto thread = Game::Scr_ExecThread(handle.second);
			Game::Scr_FreeThread(static_cast<std::uint16_t>(thread));
		}

		Utils::Hook::Call<void()>(0x4F2F50)(); //Path_AutoDisconnectPaths
	}

	GSC::GSC()
	{
		Utils::Hook(0x4DB7D7, Player_GetMethod_Stub, HOOK_CALL).install()->quick(); // Player_GetMethod
		Utils::Hook(0x5435F1, Scr_GetFunction_Stub, HOOK_CALL).install()->quick();  // Scr_GetFunction

		// Additional the folder 'scripts' for .GSC
		// Load handles
		Utils::Hook(0x4C6AF3, GScr_LoadScripts_Stub, HOOK_JUMP).install()->quick();
		// Exec handles
		Utils::Hook(0x4BA564, Scr_LoadLevel_Stub, HOOK_CALL).install()->quick();
		Utils::Hook(0x4BA58A, G_RunFrame_Stub, HOOK_CALL).install()->quick();

		GSC::AddFunctions();
		GSC::AddMethods();
	}


	GSC::~GSC()
	{
		CustomScrMethods.clear();
		CustomScrFunctions.clear();
	}
}