#include <STDInclude.hpp>
#include "ConfigStrings.hpp"

namespace Components
{
	// Patch game state
	// The structure below is our own implementation of the gameState_t structure
	static struct ReallocatedGameState_t
	{
		int stringOffsets[ConfigStrings::MAX_CONFIGSTRINGS];
		char stringData[131072]; // MAX_GAMESTATE_CHARS
		int dataCount;
	} cl_gameState{};

	static short sv_configStrings[ConfigStrings::MAX_CONFIGSTRINGS]{};

	// New mapping (extra data)
	constexpr auto EXTRA_WEAPONS_FIRST = ConfigStrings::BASEGAME_MAX_CONFIGSTRINGS;
	constexpr auto EXTRA_WEAPONS_LAST = EXTRA_WEAPONS_FIRST + Weapon::ADDED_WEAPONS - 1;

	constexpr auto EXTRA_MODELCACHE_FIRST = EXTRA_WEAPONS_LAST + 1;
	constexpr auto EXTRA_MODELCACHE_LAST = EXTRA_MODELCACHE_FIRST + ModelCache::ADDITIONAL_GMODELS;

	constexpr auto RUMBLE_FIRST = EXTRA_MODELCACHE_LAST + 1;
	constexpr auto RUMBLE_LAST = RUMBLE_FIRST + 31; // TODO

	void ConfigStrings::PatchConfigStrings()
	{
		// bump clientstate fields
		Utils::Hook::Set<DWORD>(0x4347A7, MAX_CONFIGSTRINGS);
		Utils::Hook::Set<DWORD>(0x4982F4, MAX_CONFIGSTRINGS);
		Utils::Hook::Set<DWORD>(0x4F88B6, MAX_CONFIGSTRINGS); // Save file
		Utils::Hook::Set<DWORD>(0x5A1FA7, MAX_CONFIGSTRINGS);
		Utils::Hook::Set<DWORD>(0x5A210D, MAX_CONFIGSTRINGS); // Game state
		Utils::Hook::Set<DWORD>(0x5A840E, MAX_CONFIGSTRINGS);
		Utils::Hook::Set<DWORD>(0x5A853C, MAX_CONFIGSTRINGS);
		Utils::Hook::Set<DWORD>(0x5AC392, MAX_CONFIGSTRINGS);
		Utils::Hook::Set<DWORD>(0x5AC3F5, MAX_CONFIGSTRINGS);
		Utils::Hook::Set<DWORD>(0x5AC542, MAX_CONFIGSTRINGS); // Game state
		Utils::Hook::Set<DWORD>(0x5EADF0, MAX_CONFIGSTRINGS);
		Utils::Hook::Set<DWORD>(0x625388, MAX_CONFIGSTRINGS);
		Utils::Hook::Set<DWORD>(0x625516, MAX_CONFIGSTRINGS);

		Utils::Hook::Set(0x405B72, sv_configStrings);
		Utils::Hook::Set(0x468508, sv_configStrings);
		Utils::Hook::Set(0x47FD7C, sv_configStrings);
		Utils::Hook::Set(0x49830E, sv_configStrings);
		Utils::Hook::Set(0x498371, sv_configStrings);
		Utils::Hook::Set(0x4983D5, sv_configStrings);
		Utils::Hook::Set(0x4A74AD, sv_configStrings);
		Utils::Hook::Set(0x4BAE7C, sv_configStrings);
		Utils::Hook::Set(0x4BAEC3, sv_configStrings);
		Utils::Hook::Set(0x6252F5, sv_configStrings);
		Utils::Hook::Set(0x625372, sv_configStrings);
		Utils::Hook::Set(0x6253D3, sv_configStrings);
		Utils::Hook::Set(0x625480, sv_configStrings);
		Utils::Hook::Set(0x6254CB, sv_configStrings);

		// TODO: Check if all of these actually mark the end of the array
		// Only 2 actually mark the end, the rest is header data or so
		Utils::Hook::Set(0x405B8F, &sv_configStrings[ARRAYSIZE(sv_configStrings)]);
		Utils::Hook::Set(0x4A74C9, &sv_configStrings[ARRAYSIZE(sv_configStrings)]);

		Utils::Hook(0x405BBE, ConfigStrings::SV_ClearConfigStrings, HOOK_CALL).install()->quick();
		Utils::Hook(0x593CA4, ConfigStrings::CG_ParseConfigStrings, HOOK_CALL).install()->quick();

		// Weapon
		Utils::Hook(0x4BD52D, ConfigStrings::CL_GetWeaponConfigString, HOOK_CALL).install()->quick();
		Utils::Hook(0x45D19E , ConfigStrings::SV_SetWeaponConfigString, HOOK_CALL).install()->quick();

		// Cached Models
		Utils::Hook(0x589908, ConfigStrings::CL_GetCachedModelConfigString, HOOK_CALL).install()->quick();
		Utils::Hook(0x450A30, ConfigStrings::CL_GetCachedModelConfigString, HOOK_CALL).install()->quick();
		Utils::Hook(0x4503F6, ConfigStrings::CL_GetCachedModelConfigString, HOOK_CALL).install()->quick();
		Utils::Hook(0x4504A0, ConfigStrings::CL_GetCachedModelConfigString, HOOK_CALL).install()->quick();
		Utils::Hook(0x450A30, ConfigStrings::CL_GetCachedModelConfigString, HOOK_CALL).install()->quick();
		
		Utils::Hook(0x44F217, ConfigStrings::SV_GetCachedModelConfigStringConst, HOOK_CALL).install()->quick();
		Utils::Hook(0X418F93, ConfigStrings::SV_GetCachedModelConfigStringConst, HOOK_CALL).install()->quick();
		Utils::Hook(0x497B0A, ConfigStrings::SV_GetCachedModelConfigStringConst, HOOK_CALL).install()->quick();
		Utils::Hook(0x4F4493, ConfigStrings::SV_GetCachedModelConfigStringConst, HOOK_CALL).install()->quick();
		Utils::Hook(0x5FC46D, ConfigStrings::SV_GetCachedModelConfigStringConst, HOOK_JUMP).install()->quick();

		Utils::Hook(0x44F282, ConfigStrings::SV_SetCachedModelConfigString, HOOK_CALL).install()->quick();
		
		Utils::Hook::Set<DWORD>(0x44A333, sizeof(cl_gameState));
		Utils::Hook::Set<DWORD>(0x5A1F56, sizeof(cl_gameState));
		Utils::Hook::Set<DWORD>(0x5A2043, sizeof(cl_gameState));

		Utils::Hook::Set<DWORD>(0x5A2053, sizeof(cl_gameState.stringOffsets));
		Utils::Hook::Set<DWORD>(0x5A2098, sizeof(cl_gameState.stringOffsets));
		Utils::Hook::Set<DWORD>(0x5AC32C, sizeof(cl_gameState.stringOffsets));

		Utils::Hook::Set(0x4235AC, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x434783, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x44A339, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x44ADB7, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5A1FE6, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5A2048, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5A205A, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5A2077, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5A2091, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5A20D7, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5A83FF, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5A84B0, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5A84E5, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5AC333, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5AC44A, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5AC4F3, &cl_gameState.stringOffsets);
		Utils::Hook::Set(0x5AC57A, &cl_gameState.stringOffsets);

		Utils::Hook::Set(0x4235B7, &cl_gameState.stringData);
		Utils::Hook::Set(0x43478D, &cl_gameState.stringData);
		Utils::Hook::Set(0x44ADBC, &cl_gameState.stringData);
		Utils::Hook::Set(0x5A1FEF, &cl_gameState.stringData);
		Utils::Hook::Set(0x5A20E6, &cl_gameState.stringData);
		Utils::Hook::Set(0x5AC457, &cl_gameState.stringData);
		Utils::Hook::Set(0x5AC502, &cl_gameState.stringData);
		Utils::Hook::Set(0x5AC586, &cl_gameState.stringData);

		Utils::Hook::Set(0x5A2071, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5A20CD, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5A20DC, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5A20F3, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5A2104, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC33F, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC43B, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC450, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC463, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC471, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC4C3, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC4E8, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC4F8, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC50F, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC528, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC56F, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC580, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC592, &cl_gameState.dataCount);
		Utils::Hook::Set(0x5AC59F, &cl_gameState.dataCount);
	}

	void ConfigStrings::SV_SetConfigString(int index, const char* data, Game::ConfigString basegameLastPosition, int extendedFirstPosition)
	{
		if (index > basegameLastPosition)
		{
			// we jump straight to the reallocated part of the array
			const auto relativeIndex = index - (basegameLastPosition + 1);
			index = extendedFirstPosition + relativeIndex;
		}

		// This will go back to our reallocated game state anyway
		return Game::SV_SetConfigstring(index, data);
	}
	
	void ConfigStrings::SV_SetWeaponConfigString(int index, const char* data)
	{
		SV_SetConfigString(index, data, Game::CS_WEAPONFILES_LAST, EXTRA_WEAPONS_FIRST);
	}
	
	void ConfigStrings::SV_SetCachedModelConfigString(int index, const char* data)
	{
		SV_SetConfigString(index, data, Game::CS_MODELS_LAST, EXTRA_MODELCACHE_FIRST);
	}
	
	unsigned int ConfigStrings::SV_GetConfigString(int index, Game::ConfigString basegameLastPosition, int extendedFirstPosition)
	{
		if (index > basegameLastPosition)
		{
			// It's out of range, because we're loading more weapons than the basegame has
			// So we jump straight to the reallocated part of the array
			const auto relativeIndex = index - (basegameLastPosition + 1);

			index = extendedFirstPosition + relativeIndex;
		}

		// This will go back to our reallocated game state anyway
		return Game::SV_GetConfigstringConst(index);
	}

	const char* ConfigStrings::CL_GetConfigString(int index, Game::ConfigString basegameLastPosition, int extendedFirstPosition)
	{
		if (index > basegameLastPosition)
		{
			// It's out of range, because we're loading more weapons than the basegame has
			// So we jump straight to the reallocated part of the array
			const auto relativeIndex = index - (basegameLastPosition + 1);

			index = extendedFirstPosition + relativeIndex;
		}

		// This will go back to our reallocated game state anyway
		return Game::CL_GetConfigString(index);
	}

	const char* ConfigStrings::CL_GetCachedModelConfigString(int index)
	{
		return CL_GetConfigString(index, Game::CS_MODELS_LAST, EXTRA_MODELCACHE_FIRST);
	}

	int ConfigStrings::SV_GetCachedModelConfigStringConst(int index)
	{
		return SV_GetConfigString(index, Game::CS_MODELS_LAST, EXTRA_MODELCACHE_FIRST);
	}

	const char* ConfigStrings::CL_GetWeaponConfigString(int index)
	{
		return CL_GetConfigString(index, Game::CS_WEAPONFILES_LAST, EXTRA_WEAPONS_FIRST);
	}

	int ConfigStrings::SV_GetWeaponConfigStringConst(int index)
	{
		return SV_GetConfigString(index, Game::CS_WEAPONFILES_LAST, EXTRA_WEAPONS_FIRST);
	}

	int ConfigStrings::CG_ParseExtraConfigStrings()
	{
		Command::ClientParams params; 

		if (params.size() <= 1)
			return 0;

		char* end;
		const auto* input = params.get(1);
		auto index = std::strtol(input, &end, 10);

		if (input == end)
		{
			Logger::Warning(Game::CON_CHANNEL_DONT_FILTER, "{} is not a valid input\nUsage: {} <weapon index>\n",
				input, params.get(0));
			return 0;
		}

		// If it's one of our extra data
		// bypass parsing and handle it ourselves!
		if (index >= BASEGAME_MAX_CONFIGSTRINGS)
		{
			// Handle extra weapons
			if (index >= EXTRA_WEAPONS_FIRST && index <= EXTRA_WEAPONS_LAST)
			{
				// Recompute weapon index
				index = index - EXTRA_WEAPONS_FIRST + Weapon::BASEGAME_WEAPON_LIMIT;
				Game::CG_SetupWeaponConfigString(0, index);
			}
			// Handle extra models
			else if (index >= EXTRA_MODELCACHE_FIRST && index <= EXTRA_MODELCACHE_LAST)
			{
				const auto name = Game::CL_GetConfigString(index);

				const auto R_RegisterModel = 0x50FA00;

				index = index - EXTRA_MODELCACHE_FIRST + ModelCache::BASE_GMODEL_COUNT;
				ModelCache::gameModels_reallocated[index] = Utils::Hook::Call<Game::XModel*(const char*)>(R_RegisterModel)(name);
			}
			else
			{
				// Unknown for now?
				// Pass it to the game
				return 0;
			}

			// We handled it
			return 1;
		}

		// Pass it to the game
		return 0;
	}

	__declspec(naked) void ConfigStrings::CG_ParseConfigStrings()
	{
		__asm
		{
			push eax
			pushad

			call ConfigStrings::CG_ParseExtraConfigStrings

			mov[esp + 20h], eax
			popad

			pop eax

			test eax, eax
			jz continueParsing

			retn

			continueParsing :
			push 592960h
				retn
		}
	}

	int ConfigStrings::SV_ClearConfigStrings(void* dest, int value, int size)
	{
		memset(Utils::Hook::Get<void*>(0x405B72), value, MAX_CONFIGSTRINGS * sizeof(uint16_t));
		return Utils::Hook::Call<int(void*, int, int)>(0x4C98D0)(dest, value, size); // Com_Memset
	}


	ConfigStrings::ConfigStrings()
	{
		PatchConfigStrings();
	}
}
