public void EstablishConnection()
{
	if (SQL_CheckConfig("ctimer"))
		Database.Connect(ConnectionCallback, "ctimer");
	else
		SetFailState("'ctimer' not found in 'sourcemod/configs/databases.cfg'");
}

public void ConnectionCallback(Database db, const char[] error, any data)
{
	if (db == null)
	{
		SetFailState("Failed to connect to the database, will attempt to reconnect on map change");
		return;
	}
	
	g_hDatabase = db;
	
	LoadMapInfo();
}

public void GetPlayerInfo(client)
{
	int steamid = GetTimerSteamId(client);
	char query[512], username[65], ip[16];
	
	GetClientName(client, username, sizeof(username));
	g_hDatabase.Escape(username, username, sizeof(username));
	GetClientIP(client, ip, sizeof(ip));
	
	Format(query, sizeof(query), "INSERT INTO ctimer_users (userid, name, ip, lastconnected) values ('%i', '%s', INET_ATON('%s'), CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE name = VALUES(name), ip = VALUES(ip), lastconnected = CURRENT_TIMESTAMP;", steamid, username, ip);
	g_hDatabase.Query(SQL_InsertUser, query, DBPrio_High);
	
	if (g_iMapID == -1)
	{
		LogError("Error, map ID is invalid, can't load players time %N<%i>", client, client);
		return;
	}
	
	int userid = GetClientUserId(client);
	Format(query, sizeof(query), "SELECT time FROM ctimer_times WHERE mapid = %i AND userid = %i;", g_iMapID, GetTimerSteamId(client));
	g_hDatabase.Query(SQL_GetUserTime, query, userid);
}

public void SQL_InsertUser(Database db, DBResultSet results, const char[] error, any data)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on inserting user: %s", error);
		return;
	}
}

public void SQL_GetUserTime(Database db, DBResultSet results, const char[] error, int userid)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on getting user time: %s", error);
		return;
	}
	
	int client = GetClientOfUserId(userid);
	
	if (!isValidClient(client))
		return;
	
	if (results.RowCount == 0)
	{
		g_fMapTime[client] = 0.0;
		return;
	}
	
	if (results.RowCount > 1)
	{
		LogError("Unexpected amount of rows: %i", results.RowCount);		
		return;
	}
	
	results.FetchRow();
	g_fMapTime[client] = results.FetchFloat(0);
	
}

public void LoadMapInfo()
{
	char query[512];
	Format(query, sizeof(query), "INSERT INTO ctimer_maps (mapname, lastplayed) values ('%s', CURRENT_TIMESTAMP) ON DUPLICATE KEY UPDATE lastplayed = CURRENT_TIMESTAMP", g_sMapName);
	g_hDatabase.Query(SQL_InsertMap, query, DBPrio_High); ///Insert map or update lastplayed
	Format(query, sizeof(query), "SELECT mapid, tier, enabled FROM ctimer_maps WHERE mapname = '%s'", g_sMapName);
	g_hDatabase.Query(SQL_GetMapInfo, query);
}

public void SQL_InsertMap(Database db, DBResultSet results, const char[] error, any data)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on inserting map: %s", error);
		return;
	}
	
}

public void SQL_GetMapInfo(Database db, DBResultSet results, const char[] error, any data)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on inserting map: %s", error);		
		return;
	}
	
	if (results.RowCount == 0)
	{
		LogError("Map not found");
		return;
	}
	
	if (results.RowCount > 1)
	{
		LogError("Unexpected amount of rows: %i", results.RowCount);		
		return;
	}
	
	results.FetchRow();
	g_iMapID = results.FetchInt(0);
	g_iTier = results.FetchInt(1);
	g_bActive = view_as<bool>(results.FetchInt(2));
	
	LoadZones();
	GetWRInfo();
	
	if (g_bLateLoad)
	{
		for (int i = 1; i <= MaxClients; i++) 
		{
			if (IsClientConnected(i) && IsClientInGame(i)) 
			{
				OnClientPostAdminCheck(i);
			}
		}
	}
}

public void GetWRInfo()
{
	if (g_iMapID == -1)
	{
		LogError("Error, map ID is invalid");
		return;
	}
	char query[512];
	Format(query, sizeof(query), "SELECT u.name , wr.time FROM ctimer_users u, ctimer_times wr WHERE u.userid = wr.userid AND wr.mapid = %i AND u.userid = getWrUserId(%i);", g_iMapID, g_iMapID);
	g_hDatabase.Query(SQL_GetWRInfo, query);
}

public void SQL_GetWRInfo(Database db, DBResultSet results, const char[] error, any data)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on getting map WR info: %s", error);		
		return;
	}
	
	if (results.RowCount == 0)
	{	
		g_fWrTime = 0.0;
		return;
	}
	
	if (results.RowCount > 2)
	{
		LogError("Unexpected amount of rows: %i", results.RowCount);		
		return;
	}
	results.FetchRow();
	results.FetchString(0, g_sWrHolder, sizeof(g_sWrHolder));
	g_fWrTime = results.FetchFloat(1);
}
	

public void SaveZones(int client)
{
	if (g_iMapID == -1)
	{
		LogError("Error, map ID is invalid");
		return;
	}
	char query[512], startcord[42], endcord[42];
	for (int i = 0; i <= 1; i++)
	{
		VectorToString(startcord, sizeof(startcord), g_fStartOrigins[i]);
		VectorToString(endcord, sizeof(endcord), g_fEndOrigins[i]);
		Format(query, sizeof(query), "INSERT INTO ctimer_zones(mapid, zonetype, startcord, endcord) VALUES (%i, %i, '%s', '%s') ON DUPLICATE KEY UPDATE startcord = values(startcord), endcord = values(endcord)", g_iMapID, i, startcord, endcord);
		g_hDatabase.Query(SQL_SaveZones, query);
	}
	PrintToChat(client, "Zones Saved");
}

public void SQL_SaveZones(Database db, DBResultSet results, const char[] error, any data)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on saving zones: %s", error);
		return;
	}
}

public void LoadZones()
{
	if (g_iMapID == -1)
	{
		LogError("Error, map ID is invalid");
		return;
	}
	
	char query[512];
	Format(query, sizeof(query), "SELECT zonetype, startcord, endcord from ctimer_zones where mapid = %i ORDER BY zonetype", g_iMapID);
	g_hDatabase.Query(SQL_LoadZones, query);
	
}

public void SQL_LoadZones(Database db, DBResultSet results, const char[] error, any data)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on inserting map: %s", error);		
		return;
	}
	
	if (results.RowCount == 0)
	{	
		return;
	}
	
	if (results.RowCount > 2)
	{
		LogError("Unexpected amount of rows: %i", results.RowCount);		
		return;
	}
	
	int zonetype;
	char startcord[42], endcord[42];
	float vec[3];
	
	while (results.FetchRow())
	{
		zonetype = results.FetchInt(0);
		results.FetchString(1, startcord, sizeof(startcord));
		results.FetchString(2, endcord, sizeof(endcord));
		StringToVector(vec, startcord);
		g_fStartOrigins[zonetype] = vec;
		StringToVector(vec, endcord);
		g_fEndOrigins[zonetype] = vec;
		CreateTrigger(zonetype);
		//CreateTimer(1.0, Timer_CreateTrigger, zonetype);
	}
	//CS_TerminateRound(0.0, CSRoundEnd_Draw, true);
}

public void SetMapTier(int tier)
{
	if (g_iMapID == -1)
	{
		LogError("Error, map ID is invalid");
		return;
	}
	char query[512];
	Format(query, sizeof(query), "UPDATE ctimer_maps SET tier = %i WHERE mapname = '%s'", tier, g_sMapName);
	g_hDatabase.Query(SQL_SetMapTier, query);
}
	
public void SQL_SetMapTier(Database db, DBResultSet results, const char[] error, any data)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on setting map tier: %s", error);
		return;
	}
}

public void SetMapState(int state)
{
	if (g_iMapID == -1)
	{
		LogError("Error, map ID is invalid");
		return;
	}
	char query[512];
	Format(query, sizeof(query), "UPDATE ctimer_maps SET enabled = %i WHERE mapname = '%s'", state, g_sMapName);
	g_hDatabase.Query(SQL_SetMapState, query);
}
	
public void SQL_SetMapState(Database db, DBResultSet results, const char[] error, any data)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on setting map active state: %s", error);
		return;
	}
}

public void UpdateTime(int client)
{
	if (g_iMapID == -1)
	{
		LogError("Error, map ID is invalid");
		return;
	}
	char query[512];
	int userid = GetClientUserId(client);
	Format(query, sizeof(query), "SELECT updateTime(%i, %i, %f), getTimeRank(%i, %i), getTimeComps(%i);", g_iMapID, GetTimerSteamId(client), g_fMapTime[client], g_iMapID, GetTimerSteamId(client), g_iMapID);
	g_hDatabase.Query(SQL_UpdateTime, query, userid);
}
	
public void SQL_UpdateTime(Database db, DBResultSet results, const char[] error, int userid)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on updating time: %s", error);
		return;
	}
	
	int client = GetClientOfUserId(userid);
	
	if (!isValidClient(client))
		return;
	
	results.FetchRow();
	int rank = results.FetchInt(1);
	int total = results.FetchInt(2);
	
	ProcessRankMessage(client, rank, total);
}

public void AddCompletion(int client)
{
	if (g_iMapID == -1)
	{
		LogError("Error, map ID is invalid");
		return;
	}
	char query[512];
	Format(query, sizeof(query), "UPDATE ctimer_times SET timescompleted = timescompleted + 1 where mapid = %i AND userid = %i", g_iMapID, GetTimerSteamId(client));
	g_hDatabase.Query(SQL_AddCompletion, query);
}

public void SQL_AddCompletion(Database db, DBResultSet results, const char[] error, any data)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	if (results == null)
	{
		LogError("Error on setting map active state: %s", error);
		return;
	}
}

public void RequestTop(int userid, char[] mapname, int limit)
{
	Transaction transaction = new Transaction();
	char query[512];
	Format(query, sizeof(query), "SELECT mapname FROM ctimer_maps WHERE enabled = 1 AND mapname LIKE '%%%s%%' ORDER BY mapname LIMIT 1", mapname);
	transaction.AddQuery(query);
	Format(query, sizeof(query), "SELECT u.name , times.time, times.timescompleted FROM ctimer_users u, ctimer_times times, ctimer_maps maps WHERE u.userid = times.userid AND times.mapid = maps.mapid AND maps.mapid = (SELECT mapid FROM ctimer_maps WHERE enabled = 1 AND mapname LIKE '%%%s%%' ORDER BY mapname LIMIT 1) ORDER BY time, runid LIMIT %i;", mapname, limit);
	transaction.AddQuery(query);
	g_hDatabase.Execute(transaction, SQL_RequestTop, SQL_RequestTopError, userid);
}

public void SQL_RequestTopError(Database db, int userid, int numQueries, const char[] error, int failIndex, any[] queryData)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}

	LogError("Error on requesting top time records on query %i: %s", failIndex, error);
}

public void RequestWR(int userid, char[] mapname)
{
	Transaction transaction = new Transaction();
	char query[512];
	Format(query, sizeof(query), "SELECT mapname FROM ctimer_maps WHERE enabled = 1 AND mapname LIKE '%%%s%%' ORDER BY mapname LIMIT 1", mapname);
	transaction.AddQuery(query);
	Format(query, sizeof(query), "SELECT name, time FROM ctimer_times times INNER JOIN ctimer_users u ON  u.userid = times.userid WHERE mapid=(SELECT mapid FROM ctimer_maps WHERE enabled = 1 AND mapname LIKE '%%%s%%' ORDER BY mapname LIMIT 1) ORDER BY time, runid LIMIT 1;", mapname);
	transaction.AddQuery(query);
	g_hDatabase.Execute(transaction, SQL_RequestWR, SQL_RequestWRError, userid);
}

public void SQL_RequestWRError(Database db, int userid, int numQueries, const char[] error, int failIndex, any[] queryData)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}

	LogError("Error on requesting world record time on query %i: %s", failIndex, error);
}

public void SQL_RequestWR(Database db, int userid, int numQueries, DBResultSet[] results, any[] queryData)
{
	if (db == null)
	{
		SetFailState("Lost connection to the database, will attempt to reconnect on map change");
		return;
	}
	
	int client = GetClientOfUserId(userid);
	
	if (!isValidClient(client))
		return;
	
	if (results[0].RowCount == 0)
	{	
		TimerPrintToChat(client, false, "%T", "MapNotFound", LANG_SERVER);
		return;
	}
	
	char cMap[64];
	results[0].FetchRow();
	results[0].FetchString(0, cMap, sizeof(cMap));
	
	if (results[1].RowCount == 0)
	{	
		TimerPrintToChat(client, false, "%T", "TimesNotFound", LANG_SERVER, cMap);
		return;
	}
	
	char cTime[16], cName[64];
	float fTime;
	
	results[1].FetchRow();
	results[1].FetchString(0, cName, sizeof(cName));
	fTime = results[1].FetchFloat(1);
	
	TimerFormat(fTime, cTime, sizeof(cTime), true, false);
	
	TimerPrintToChat(client, false, "%T", "WR", LANG_SERVER, cName, cMap, cTime);
}