#include <sourcemod>

#define VERSION "2.3.4"
#define LISTBANS_USAGE "sm_banlist/listban/s <#userid|name> - Lists a user's prior bans"
#define DATABASE_PREFIX "sb"

Menu g_PlayersMainMenu[MAXPLAYERS];
int g_CurrentBanID[MAXPLAYERS];
Database g_DB;

public Plugin myinfo = 
{
	name = "Sourcebans Ban Checker",
	author = "Pan32",
	description = "Looks up if a player has been a bad boy. Shoutouts to my boy ici",
	version = VERSION,
	url = "http://www.unloze.com"
};

public void OnPluginStart()
{
	LoadTranslations("common.phrases");
	
	RegAdminCmd("sm_listbans", OnListSourceBansCmd, ADMFLAG_BAN, LISTBANS_USAGE);
	RegAdminCmd("sm_listban", OnListSourceBansCmd, ADMFLAG_BAN, LISTBANS_USAGE);
	RegAdminCmd("sm_banlist", OnListSourceBansCmd, ADMFLAG_BAN, LISTBANS_USAGE)
	
	Database.Connect(OnDatabaseConnected, "sourcebans");
}

public void OnMapStart()
{
	for (int i; i < MAXPLAYERS; i++)
	{
		g_CurrentBanID[i] = 0;
		g_PlayersMainMenu[i] = null;
	}
}

public void OnDatabaseConnected(Database db, const char[] error, any data)
{
	if (db == null)
		SetFailState("Failed to connect to SB db, %s", error);
	
	g_DB = db;
}

public Action OnListSourceBansCmd(int client, int args)
{
	if (args < 1)
	{
		ReplyToCommand(client, LISTBANS_USAGE);
		return Plugin_Handled;
	}
	
	if (client == 0)
	{
		ReplyToCommand(client, "This command is not supported through console.");
		return Plugin_Handled;
	}
	
	if (g_DB == INVALID_HANDLE)
	{
		ReplyToCommand(client, "Error: database not ready.");
		return Plugin_Handled;
	}
	
	char pattern[MAX_TARGET_LENGTH], target_name[MAX_TARGET_LENGTH];
	int target_list[MAXPLAYERS], target_count;
	bool tn_is_ml;
	
	GetCmdArg(1, pattern, sizeof(pattern));
	
	if ((target_count = ProcessTargetString(pattern, client, target_list, MAXPLAYERS, COMMAND_FILTER_NO_BOTS|COMMAND_FILTER_NO_MULTI, target_name, sizeof(target_name), tn_is_ml)) <= 0)
	{
		ReplyToTargetError(client, target_count, true);
		return Plugin_Handled;
	}
	
	char auth[32];
	if (!GetClientAuthId(target_list[0], AuthId_Steam2, auth, sizeof(auth))
		|| auth[0] == 'B' || auth[9] == 'L')
	{
		ReplyToCommand(client, "Error: could not retrieve %N's steam id.", target_list[0]);
		return Plugin_Handled;
	}
	
	char query[1024];
	char ip[30];
	GetClientIP(target_list[0], ip, sizeof(ip));
	FormatEx(query, sizeof(query), "SELECT bid, created, length, reason, RemoveType FROM %s_bans WHERE ((type = 0 AND %s_bans.authid REGEXP '^STEAM_[0-9]:%s$') OR (type = 1 AND ip = '%s')) AND ((length > '0' AND ends > UNIX_TIMESTAMP()) OR RemoveType IS NOT NULL) ORDER BY created DESC;", DATABASE_PREFIX, DATABASE_PREFIX, auth[8], ip);
	
	char targetName[MAX_NAME_LENGTH];
	GetClientName(target_list[0], targetName, sizeof(targetName));
	
	DataPack pack = new DataPack();
	pack.WriteCell(GetClientUserId(client));
	pack.WriteString(targetName);
	
	g_DB.Query(OnListBans, query, pack, DBPrio_Low);
	
	return Plugin_Handled;
}

public void OnListBans(Database db, DBResultSet results, const char[] error, DataPack pack)
{
	pack.Reset();
	int clientuid = pack.ReadCell();
	int client = GetClientOfUserId(clientuid);
	char targetName[MAX_NAME_LENGTH];
	pack.ReadString(targetName, sizeof(targetName));
	delete pack;
	
	if (client == 0)
		return;
	
	if (db == null)
	{
		PrintToChat(client, "[SBChecker] Could not establish connection to the database");	
		return;
	}
	
	if (results == null)
	{
		PrintToChat(client, "[SBChecker] DB error while retrieving bans for %s:\n%s", targetName, error);		
		return;
	}
	
	int count = results.RowCount;
	
	if (count == 0)
	{
		PrintToChat(client, "[SBChecker] No bans found for %s.", targetName);
		return;
	}
	
	char cBuffer[512];
	
	Menu menu = new Menu(SBCheckerCallback);
	
	Format(cBuffer, sizeof(cBuffer), "Previous bans of %s (%i):\n ", targetName, count);
	menu.SetTitle(cBuffer);
	
	while (results.FetchRow())
	{
		char banid[12];
		char createddate[11] = "<Unknown>";
		//char bannedby[MAX_NAME_LENGTH]    = "<Unknown>";
		char lenstring[32]   = "N/A";
		char reason[28];
		char RemoveType[9] = " ";
		
		if (!results.IsFieldNull(0))
		{
			IntToString(results.FetchInt(0), banid, sizeof(banid));
		}
		
		if (!results.IsFieldNull(1))
		{
			FormatTime(createddate, sizeof(createddate), "%Y-%m-%d", results.FetchInt(1));
		}
		
		
		// NOT NULL
		int length = results.FetchInt(2);
		if (length == 0)
		{
			Format(lenstring, sizeof(lenstring), "Permanent");
		}
		if (length > 0)
		{
			CalculateLength(length, lenstring, sizeof(lenstring));
		}
		
		
		// NOT NULL
		results.FetchString(3, reason, sizeof(reason));
		
		if (!results.IsFieldNull(4))
		{
			results.FetchString(4, RemoveType, sizeof(RemoveType));
			if (StrEqual(RemoveType, "E", false))
				Format(RemoveType, sizeof(RemoveType), "Expired");
				
			if (StrEqual(RemoveType, "U", false))
				Format(RemoveType, sizeof(RemoveType), "Unbanned");
		}
		
		Format(cBuffer, sizeof(cBuffer), "%s\n%s(%s)\nReason: %s\n ", createddate, lenstring, RemoveType, reason);
		menu.AddItem(banid, cBuffer);
	}
	menu.ExitButton = true;
	menu.Display(client, MENU_TIME_FOREVER);
}

public int SBCheckerCallback(Menu menu, MenuAction action, int client, int choice)
{
	switch(action)
	{
		case MenuAction_Select:
		{
			g_PlayersMainMenu[client] = menu;
			char cBanid[12];
			int Banid;
			
			menu.GetItem(choice, cBanid, sizeof(cBanid));
			Banid = StringToInt(cBanid);

			
			g_CurrentBanID[client] = Banid;
			RequestBan(client, Banid);
			
		}
		case MenuAction_End:
		{
			if (choice != MenuEnd_Selected)
				delete menu;
		}
	}
}

void CalculateLength(int banLength, char[] time, int len) // banLength in secs
{
	static const int secs[6] = {31536000, 2592000, 604800, 86400, 3600, 60 };
	static const char names[6][6] = { "years", "months", "weeks", "days", "hours", "mins" }
	
	int values[6]
	int timeLeft = banLength;
	
	for (int i; i < 6; i++)
	{
		values[i] = timeLeft / secs[i];
		timeLeft = timeLeft % secs[i];
	}
	
	// print based on these ints, if they're 0, skip
	
	FormatEx(time, len, "");
	
	for (int i; i < 6; i++)
	{
		if(values[i])
		{
			Format(time, len, "%s%d %s ", time, values[i], names[i]);
		}
	}	
}

void RequestBan(int client, int banid)
{
	char query[1024];
	Format(query, sizeof(query), "SELECT %s_bans.bid, ip, name, created, %s_admins.user, ends, length, reason, %s_bans.type, RemoveType, admins2.user, RemovedOn, ureason, COUNT(%s_comments.bid) AS nc FROM %s_bans LEFT JOIN %s_admins ON %s_bans.aid = %s_admins.aid LEFT JOIN %s_admins admins2 ON %s_bans.RemovedBy = admins2.aid LEFT JOIN %s_comments ON %s_comments.bid = %s_bans.bid WHERE (%s_bans.bid = '%i');", DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, banid);
	
	//PrintToConsole(client, "%s", query);
	
	DataPack pack = new DataPack();
	pack.WriteCell(client);
	pack.WriteCell(GetClientUserId(client));
	
	g_DB.Query(DetailBanInfo, query, pack, DBPrio_Low);
}

public void DetailBanInfo(Database db, DBResultSet results, const char[] error, DataPack pack)
{
	pack.Reset();
	
	int client = pack.ReadCell();
	int checkclient = GetClientOfUserId(pack.ReadCell());
	
	if (checkclient == 0)
	{
		delete g_PlayersMainMenu[client];
		return;
	}
		
	
	if (db == null)
	{
		PrintToChat(client, "[SBChecker] Could not establish connection to the database");
		delete g_PlayersMainMenu[client];
		return;
	}
	
	if (results == null)
	{
		PrintToChat(client, "[SBChecker] DB error while retrieving bans for the requested ban ID:\n%s", error);
		delete g_PlayersMainMenu[client];		
		return;
	}
	
	int count = results.RowCount;
	
	if (count == 0)
	{
		PrintToChat(client, "[SBChecker] No bans found with that ban id.");
		delete g_PlayersMainMenu[client];
		return;
	}
	
	results.FetchRow();
	
	int banid = results.FetchInt(0);
	
	char cBuffer[512];
	
	Panel panel = new Panel();
	
	Format(cBuffer, sizeof(cBuffer), "More information on Ban ID %i:\n ", banid);
	panel.SetTitle(cBuffer);
	
	char nickname[MAX_NAME_LENGTH] = "<Unknown>";
	char createddate[11] = "<Unknown>";
	char bannedby[MAX_NAME_LENGTH]    = "<Unknown>";
	char ip[17] = "<Unknown>";
	char lenstring[32]   = "N/A";
	char enddate[11]     = "N/A";
	char reason[32];
	char RemoveType[9] = " ";
	char BanType[12] = "<Unknown>";
	int comments = 0;
	bool unbanned = false;
	
	if (!results.IsFieldNull(1))
	{
		results.FetchString(1, ip, sizeof(ip));
	}
	
	if (!results.IsFieldNull(2))
	{
		results.FetchString(2, nickname, sizeof(nickname));
	}
	
	if (!results.IsFieldNull(3))
	{
		FormatTime(createddate, sizeof(createddate), "%Y-%m-%d", results.FetchInt(3));
	}
	
	if (!results.IsFieldNull(4))
	{
		results.FetchString(4, bannedby, sizeof(bannedby));
	}
	
	if (!results.IsFieldNull(5))
	{
		FormatTime(enddate, sizeof(enddate), "%Y-%m-%d", results.FetchInt(5));
	}
	
	int length = results.FetchInt(6);
	if (length == 0)
	{
		Format(lenstring, sizeof(lenstring), "Permanent");
	}
	if (length)
	{
		CalculateLength(length, lenstring, sizeof(lenstring));
	}
	
	if (!results.IsFieldNull(7))
	{
		results.FetchString(7, reason, sizeof(reason));
	}
	
	int banidtype = results.FetchInt(8);
	if (!banidtype)
		Format(BanType, sizeof(BanType), "Steam ID");
	if (banidtype == 1)
		Format(BanType, sizeof(BanType), "IP Address");
		
	
	if (!results.IsFieldNull(9))
	{
		results.FetchString(9, RemoveType, sizeof(RemoveType));
		if (StrEqual(RemoveType, "E", false))
			Format(RemoveType, sizeof(RemoveType), "Expired");
			
		if (StrEqual(RemoveType, "U", false))
		{
			unbanned = true;
			Format(RemoveType, sizeof(RemoveType), "Unbanned");
		}
		
	}
	
	Format(cBuffer, sizeof(cBuffer), "Banned on: %s", createddate);
	
	panel.DrawText(cBuffer);
	
	Format(cBuffer, sizeof(cBuffer), "Name when banned: %s", nickname);
	
	panel.DrawText(cBuffer);
	
	Format(cBuffer, sizeof(cBuffer), "IP Address: %s", ip);
	
	panel.DrawText(cBuffer);
	
	Format(cBuffer, sizeof(cBuffer), "Banned by: %s", bannedby);
	
	panel.DrawText(cBuffer);
	
	Format(cBuffer, sizeof(cBuffer), "Length: %s", lenstring);
	
	panel.DrawText(cBuffer);
	
	Format(cBuffer, sizeof(cBuffer), "Reason: %s", reason);
	
	panel.DrawText(cBuffer);
	
	Format(cBuffer, sizeof(cBuffer), "Ends on: %s (%s)", enddate, RemoveType);
	
	panel.DrawText(cBuffer);
	
	Format(cBuffer, sizeof(cBuffer), "Ban Type: %s\n ", BanType);
	
	panel.DrawText(cBuffer);
	
	if (unbanned)
	{
		char removedon[11] = "<Unknown>";
		char removedby[MAX_NAME_LENGTH]    = "<Unknown>";
		char ureason[32]    = "<Unknown>";
		
		if (!results.IsFieldNull(10))
		{
			results.FetchString(10, removedby, sizeof(removedby));
		}
		
		if (!results.IsFieldNull(11))
		{
			FormatTime(removedon, sizeof(removedon), "%Y-%m-%d", results.FetchInt(11));
		}
		
		if (!results.IsFieldNull(12))
		{
			results.FetchString(12, ureason, sizeof(ureason));
		}
		
		Format(cBuffer, sizeof(cBuffer), "Unbanned on: %s", removedon);
	
		panel.DrawText(cBuffer);
		
		Format(cBuffer, sizeof(cBuffer), "Unbanned by: %s", removedby);
	
		panel.DrawText(cBuffer);
		
		Format(cBuffer, sizeof(cBuffer), "Unban reason: %s\n ", ureason);
	
		panel.DrawText(cBuffer);
		
	}
	
	comments = results.FetchInt(13);
	
	panel.CurrentKey = 2;
	
	if (!comments)
		panel.DrawItem("View Comments (0)", ITEMDRAW_DISABLED);
	else
	{
		Format(cBuffer, sizeof(cBuffer), "View Comments (%i)", comments);
		panel.DrawItem(cBuffer);
	}
		
	
	panel.CurrentKey = 8;
	
	panel.DrawItem("Back", ITEMDRAW_CONTROL);
	
	panel.CurrentKey = 10;
	
	panel.DrawItem("Exit", ITEMDRAW_CONTROL);


	//menu.ExitButton = true;
	//menu.ExitBackButton = true;
	panel.Send(client, DetailBanCallback, MENU_TIME_FOREVER);
	delete panel;

}

public int DetailBanCallback(Menu menu, MenuAction action, int client, int choice)
{
	switch(action)
	{
		case MenuAction_Select:
		{
			if (choice == 2)
				RequestComments(client, g_CurrentBanID[client]);
			if (choice == 8)
				g_PlayersMainMenu[client].Display(client, MENU_TIME_FOREVER);
			if (choice == 10)
			{
				g_CurrentBanID[client] = 0;
				delete g_PlayersMainMenu[client];
			}
		}
		
		case MenuAction_Cancel:
		{
			delete g_PlayersMainMenu[client];
			g_CurrentBanID[client] = 0;
		}
	}
}

void RequestComments(int client, int banid)
{
	char query[1024];
	FormatEx(query, sizeof(query), "SELECT added, %s_admins.user, commenttxt FROM %s_comments LEFT JOIN %s_admins ON %s_comments.aid = %s_admins.aid WHERE (%s_comments.bid = '%i');", DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, DATABASE_PREFIX, banid);
	
	DataPack pack = new DataPack();
	pack.WriteCell(client);
	pack.WriteCell(GetClientUserId(client));
	
	g_DB.Query(CommentsBanInfo, query, pack, DBPrio_Low);
}

public void CommentsBanInfo(Database db, DBResultSet results, const char[] error, DataPack pack)
{
	pack.Reset();
	
	int client = pack.ReadCell();
	int checkclient = GetClientOfUserId(pack.ReadCell());
	
	if (checkclient == 0)
	{
		delete g_PlayersMainMenu[client];
		g_CurrentBanID[client] = 0;
		return;
	}
		
	
	if (db == null)
	{
		PrintToChat(client, "[SBChecker] Could not establish connection to the database, returning to ban information.");
		RequestBan( client, g_CurrentBanID[client]);
		return;
	}
	
	if (results == null)
	{
		PrintToChat(client, "[SBChecker] DB error while retrieving comments for the requested ban ID: %s, returning to ban information.", error);
		RequestBan(client, g_CurrentBanID[client]);
		return;
	}
	
	int count = results.RowCount;
	
	if (count == 0)
	{
		PrintToChat(client, "[SBChecker] No comments with that ban id, returning to ban information.");
		RequestBan(client, g_CurrentBanID[client]);
		return;
	}
	
	char cBuffer[512];
	
	Panel panel = new Panel();
	
	Format(cBuffer, sizeof(cBuffer), "Comments on Ban ID %i:\n ", g_CurrentBanID[client]);
	panel.SetTitle(cBuffer);
	
	while (results.FetchRow())
	{
		char addeddate[11] = "<Unknown>";
		char addedby[MAX_NAME_LENGTH]    = "<Unknown>";
		char comment[128] = "<None>";
		
		FormatTime(addeddate, sizeof(addeddate), "%Y-%m-%d", results.FetchInt(0));
		
		if (!results.IsFieldNull(1))
		{
			results.FetchString(1, addedby, sizeof(addedby));
		}
		
		if (!results.IsFieldNull(2))
		{
			results.FetchString(2, comment, sizeof(comment));
		}
		
		Format(cBuffer, sizeof(cBuffer), "Added on: %s", addeddate);
		
		panel.DrawItem(cBuffer, ITEMDRAW_DISABLED)
		
		Format(cBuffer, sizeof(cBuffer), "By: %s", addedby);
		
		panel.DrawText(cBuffer);
		
		Format(cBuffer, sizeof(cBuffer), "Comment:\n%s\n ", comment);
		
		panel.DrawText(cBuffer);
		
	}
	
	panel.CurrentKey = 8;
	
	panel.DrawItem("Back", ITEMDRAW_CONTROL);
	
	panel.CurrentKey = 10;
	
	panel.DrawItem("Exit", ITEMDRAW_CONTROL);
	
	panel.Send(client, CommentsBanCallback, MENU_TIME_FOREVER);
	delete panel;
}

public int CommentsBanCallback(Menu menu, MenuAction action, int client, int choice)
{
	switch(action)
	{
		case MenuAction_Select:
		{
			if (choice == 8)
				RequestBan(client, g_CurrentBanID[client]);
			if (choice == 10)
			{
				g_CurrentBanID[client] = 0;
				delete g_PlayersMainMenu[client];
			}
		}
		
		case MenuAction_Cancel:
		{
			delete g_PlayersMainMenu[client];
			g_CurrentBanID[client] = 0;
		}
	}
}