#pragma semicolon 1

#include <sourcemod>

#if defined INFO_INCLUDES
	#include "../info/constants.sp"
	#include "../info/enums.sp"
	#include "../info/variables.sp"
#endif

/**
 * Initializes the server.
 *
 * @noreturn
 */

stock void Server_Initialize() {
	Log(TDLogLevel_Debug, "Initializing server");

	StripConVarFlag("sv_cheats", FCVAR_NOTIFY);
	StripConVarFlag("sv_tags", FCVAR_NOTIFY);
	StripConVarFlag("tf_bot_count", FCVAR_NOTIFY);
	StripConVarFlag("sv_password", FCVAR_NOTIFY);

	HookButtons();
	Server_Reset();

	int iServerIp[4];
	SteamWorks_GetPublicIP(iServerIp);
	Format(g_sServerIp, sizeof(g_sServerIp), "%d.%d.%d.%d", iServerIp[0], iServerIp[1], iServerIp[2], iServerIp[3]);

	char sServerPort[6];
	GetConVarString(FindConVar("hostport"), sServerPort, sizeof(sServerPort));
	g_iServerPort = StringToInt(sServerPort);

	if (StrEqual(g_sServerIp, "0.0.0.0")) {
		Log(TDLogLevel_Error, "ServerIP: %s", g_sServerIp);
		Log(TDLogLevel_Error, "Vac enabled?: %b", SteamWorks_IsVACEnabled());
		Log(TDLogLevel_Error, "Is connected to Steam?: %b", SteamWorks_IsConnected());
		Log(TDLogLevel_Error, "This can be caused by using GSLT with an expired Token. https://steamcommunity.com/dev/managegameservers");
		Log(TDLogLevel_Error, "The server will loop indefinetly if it can't connect to Steam");

		Log(TDLogLevel_Info, "Server has been restarted completely, reloading map for initializing");
		ReloadMap();
	} else {
		Database_CheckServer();	   // Calls Database_OnServerChecked() when finished
	}
}

stock void Database_OnServerChecked() {
	Log(TDLogLevel_Trace, "Database_OnServerChecked");

	Database_LoadData();	// Calls Database_OnDataLoaded() when finished
}

stock void Database_OnDataLoaded() {
	Log(TDLogLevel_Debug, "Successfully initialized server");

	PrintToHudAll("WELCOME TO TF2 TOWER DEFENSE");

	g_bServerInitialized = true;
}

/**
 * Resets the server.
 *
 * @noreturn
 */

stock void Server_Reset() {
	g_bEnabled = g_hEnabled.BoolValue && g_bTowerDefenseMap && g_bSteamWorks && g_bTF2Attributes;
	g_bMapRunning = true;

	UpdateGameDescription();

	if (!g_bEnabled) {
		if (!g_bTowerDefenseMap) {
			char sCurrentMap[PLATFORM_MAX_PATH];
			GetCurrentMap(sCurrentMap, sizeof(sCurrentMap));

			Log(TDLogLevel_Info, "Map \"%s\" is not supported, thus Tower Defense has been disabled.", sCurrentMap);
		} else {
			Log(TDLogLevel_Info, "Tower Defense is disabled.");
		}

		return;
	}

	g_iBuildingLimit[TDBuilding_Sentry]			 = 1;
	g_iBuildingLimit[TDBuilding_Dispenser]		 = 0;
	g_iBuildingLimit[TDBuilding_TeleporterEntry] = 1;
	g_iBuildingLimit[TDBuilding_TeleporterExit]	 = 1;

	// Reset Hint Timer
	if (hHintTimer != null) {
		CloseHandle(hHintTimer);
		hHintTimer = null;
	}

	for (int iClient = 1; iClient <= MaxClients; iClient++) {
		// Reset carry Towers/Sentry
		if (IsTower(g_iAttachedTower[iClient])) {
			TF2Attrib_RemoveByName(iClient, "cannot pick up buildings");
			g_iLastMover[g_iAttachedTower[iClient]] = 0;
			g_bCarryingObject[iClient]				= false;
			g_iAttachedTower[iClient]				= 0;
		}
		// Reset bought Towers
		if (IsTower(iClient)) {
			TDTowerId iTowerId = GetTowerId(iClient);
			// tf_bot_quota > 0 breaks this here
			if (iTowerId != TDTower_Invalid) {
				g_bTowerBought[view_as<int>(iTowerId)] = false;
			}
		}
		if (IsAttacker(iClient) && g_iSlowAttacker[iClient]) {
			g_iSlowAttacker[iClient] = false;
		}
		if (IsDefender(iClient)) {
			g_bCarryingObject[iClient] = false;
			// Remove Beam if there is one
			if (g_iHealBeamIndex[iClient][0] != 0) {
				if (IsValidEdict(g_iHealBeamIndex[iClient][0])) {
					RemoveEdict(g_iHealBeamIndex[iClient][0]);
					g_iHealBeamIndex[iClient][0] = 0;
				}
			}

			if (g_iHealBeamIndex[iClient][1] != 0) {
				if (IsValidEdict(g_iHealBeamIndex[iClient][1])) {
					RemoveEdict(g_iHealBeamIndex[iClient][1]);
					g_iHealBeamIndex[iClient][1] = 0;
				}
			}
		}
	}

	// Reset Multipliers
	for (int i = 1; i <= iMaxMultiplierTypes; i++) {
		fMultiplier[i] = 0.0;
	}
	g_iTime				 = GetTime();
	g_iMetalPackCount	 = 0;

	g_bTowersLocked		 = false;
	g_bAoEEngineerAttack = false;

	g_bStartWaveEarly	 = false;
	g_iBotsToSpawn		 = 0;
	g_iTotalBotsLeft	 = 0;

	g_iCurrentWave		 = 0;
	g_iNextWaveType		 = 0;

	iAoEEngineerTimer	 = 0;
	iAoEKritzMedicTimer	 = 0;

	g_iHealthBar		 = GetHealthBar();

	g_bLockable			 = true;
	g_bCanGetUnlocks	 = true;

	// Reset AoE Timer
	if (hAoETimer != null) {
		CloseHandle(hAoETimer);
		hAoETimer = null;
	}

	// Get map max clients
	g_iMaxClients = PLAYER_LIMIT;

	char sQuery[256];
	char sCurrentMap[PLATFORM_MAX_PATH];
	GetCurrentMap(sCurrentMap, sizeof(sCurrentMap));

	g_hDatabase.Format(sQuery, sizeof(sQuery),
		"SELECT `player_limit` " ...
		"FROM `map` " ...
		"WHERE `name` = '%s' " ...
		"LIMIT 1",
		sCurrentMap);

	DBResultSet queryResult = SQL_Query(g_hDatabase, sQuery);

	if (queryResult == null) {
		char error[255];
		SQL_GetError(g_hDatabase, error, sizeof(error));
		LogType(TDLogLevel_Error, TDLogType_FileAndConsole, "Failed to query (error: %s)", error);
	} else {
		if (queryResult.HasResults && queryResult.FetchRow()) {
			int iResult = queryResult.FetchInt(0);
			if (iResult > 0) {
				g_iMaxClients = iResult;
				LogType(TDLogLevel_Debug, TDLogType_FileAndConsole, "Max clients for map %s is %d", sCurrentMap, g_iMaxClients);
			} else {
				LogType(TDLogLevel_Error, TDLogType_FileAndConsole, "Max clients for map %s is %d. This isnt supported; Please check the database entries. Setting max clients to default %d", sCurrentMap, iResult, g_iMaxClients);
			}
		} else {
			LogType(TDLogLevel_Debug, TDLogType_FileAndConsole, "Couldn't find entry for map '%s'. Setting max clients to default %d", sCurrentMap, g_iMaxClients);
		}
		delete queryResult;
	}

	int clients = (g_iMaxClients + g_hMaxBotsOnField.IntValue) - MaxClients;

	if (clients > 0) {
		char cvarName[32];
		g_hMaxBotsOnField.GetName(cvarName, sizeof(cvarName));
		LogType(TDLogLevel_Warning, TDLogType_FileAndConsole, "ConVar '%s' value + the allowed max clients '%d' (database) is higher than the server maxplayers '%d'. Shrinking convar value by '%d'", cvarName, g_iMaxClients, MaxClients, clients);
		g_hMaxBotsOnField.IntValue = g_hMaxBotsOnField.IntValue - clients;
	}

	Format(g_sPassword, sizeof(g_sPassword), "");

	SetPassword(g_sPassword, false);	// Change upon release
}

/**
 * Called when a servers data was set.
 *
 * @param iServerId		The server id (unique for every server).
 * @param sKey				The set key.
 * @param iDataType			The datatype of the set data.
 * @param iValue			The value if the set data is an integer, -1 otherwise.
 * @param bValue			The value if the set data is a boolean, false otherwise.
 * @param fValue			The value if the set data is a float, -1.0 otherwise.
 * @param sValue			The value if the set data is a string, empty string ("") otherwise.
 * @noreturn
 */

stock void Server_OnDataSet(int iServerId, const char[] sKey, TDDataType iDataType, int iValue, int bValue, float fValue, const char[] sValue) {
	switch (iDataType) {
		case TDDataType_Integer: {
			Log(TDLogLevel_Trace, "Server_OnDataSet: iServerId=%d, sKey=%s, iDataType=TDDataType_Integer, iValue=%d", iServerId, sKey, iValue);
		}

		case TDDataType_Boolean: {
			Log(TDLogLevel_Trace, "Server_OnDataSet: iServerId=%d, sKey=%s, iDataType=TDDataType_Boolean, bValue=%s", iServerId, sKey, (bValue ? "true" : "false"));
		}

		case TDDataType_Float: {
			Log(TDLogLevel_Trace, "Server_OnDataSet: iServerId=%d, sKey=%s, iDataType=TDDataType_Float, fValue=%f", iServerId, sKey, fValue);
		}

		case TDDataType_String: {
			Log(TDLogLevel_Trace, "Server_OnDataSet: iServerId=%d, sKey=%s, iDataType=TDDataType_String, sValue=%s", iServerId, sKey, sValue);
		}
	}
}

stock void Server_UAddValue(int iServerId, const char[] sKey, int iValue) {
	char sServerIdKey[128];
	int	 iOldValue;
	Server_UGetValue(iServerId, sKey, iOldValue);
	if (iOldValue != -1)
		iValue = iValue + iOldValue;

	Format(sServerIdKey, sizeof(sServerIdKey), "%d_%s", iServerId, sKey);

	Server_OnDataSet(iServerId, sKey, TDDataType_Integer, iValue, false, -1.0, "");

	SetTrieValue(g_hServerData, sServerIdKey, iValue);
}

stock void Server_USetValue(int iServerId, const char[] sKey, int iValue) {
	char sServerIdKey[128];
	Format(sServerIdKey, sizeof(sServerIdKey), "%d_%s", iServerId, sKey);

	Server_OnDataSet(iServerId, sKey, TDDataType_Integer, iValue, false, -1.0, "");

	SetTrieValue(g_hServerData, sServerIdKey, iValue);
}

stock bool Server_UGetValue(int iServerId, const char[] sKey, int &iValue) {
	char sServerIdKey[128];
	Format(sServerIdKey, sizeof(sServerIdKey), "%d_%s", iServerId, sKey);

	Log(TDLogLevel_Trace, "Server_UGetValue: iServerId=%d, sKey=%s", iServerId, sKey);

	if (!GetTrieValue(g_hServerData, sServerIdKey, iValue)) {
		iValue = -1;
		return false;
	}

	return true;
}

stock void Server_USetBool(int iServerId, const char[] sKey, bool bValue) {
	char sServerIdKey[128];
	Format(sServerIdKey, sizeof(sServerIdKey), "%d_%s", iServerId, sKey);

	Server_OnDataSet(iServerId, sKey, TDDataType_Integer, -1, bValue, -1.0, "");

	SetTrieValue(g_hServerData, sServerIdKey, (bValue ? 1 : 0));
}

stock bool Server_UGetBool(int iServerId, const char[] sKey) {
	char sServerIdKey[128];
	Format(sServerIdKey, sizeof(sServerIdKey), "%d_%s", iServerId, sKey);

	Log(TDLogLevel_Trace, "Server_UGetBool: iServerId=%d, sKey=%s", iServerId, sKey);

	int iValue = 0;
	GetTrieValue(g_hServerData, sServerIdKey, iValue);

	return (iValue != 0);
}

stock void Server_USetFloat(int iServerId, const char[] sKey, float fValue) {
	char sServerIdKey[128];
	Format(sServerIdKey, sizeof(sServerIdKey), "%d_%s", iServerId, sKey);

	char sValue[64];
	FloatToString(fValue, sValue, sizeof(sValue));

	Server_OnDataSet(iServerId, sKey, TDDataType_Integer, -1, false, fValue, "");

	SetTrieString(g_hServerData, sServerIdKey, sValue);
}

stock bool Server_UGetFloat(int iServerId, const char[] sKey, float &fValue) {
	char sServerIdKey[128];
	Format(sServerIdKey, sizeof(sServerIdKey), "%d_%s", iServerId, sKey);

	Log(TDLogLevel_Trace, "Server_UGetFloat: iServerId=%d, sKey=%s", iServerId, sKey);

	char sValue[64];
	if (!GetTrieString(g_hServerData, sServerIdKey, sValue, sizeof(sValue))) {
		fValue = -1.0;
		return false;
	}

	fValue = StringToFloat(sValue);
	return true;
}

stock void Server_USetString(int iServerId, const char[] sKey, const char[] sValue, any...) {
	char sServerIdKey[128];
	Format(sServerIdKey, sizeof(sServerIdKey), "%d_%s", iServerId, sKey);

	char sFormattedValue[256];
	VFormat(sFormattedValue, sizeof(sFormattedValue), sValue, 4);

	Server_OnDataSet(iServerId, sKey, TDDataType_String, -1, false, -1.0, sValue);

	SetTrieString(g_hServerData, sServerIdKey, sFormattedValue);
}

stock bool Server_UGetString(int iServerId, const char[] sKey, char[] sValue, int iMaxLength) {
	char sServerIdKey[128];
	Format(sServerIdKey, sizeof(sServerIdKey), "%d_%s", iServerId, sKey);

	Log(TDLogLevel_Trace, "Server_UGetString: iServerId=%d, sKey=%s, iMaxLength=%d", iServerId, sKey, iMaxLength);

	if (!GetTrieString(g_hServerData, sServerIdKey, sValue, iMaxLength)) {
		Format(sValue, iMaxLength, "");
		return false;
	}

	return true;
}
