#pragma semicolon 1
#pragma newdecls required

#include <sdkhooks>
#include <clientprefs>
#include <multicolors>
#include <sdktools>

#define DATA_CONFIG_PATH "addons/sourcemod/configs/jump_king_records.cfg"
#define SKIN_FOR_TOP "models/microrost/player_solaire1.mdl"
#define CROWN "models/microrost/jumpking/star.mdl"

#define Entity_GetTargetName(%1,%2,%3) GetEntPropString(%1, Prop_Data, "m_iName", %2, %3)

#define RECORD_LIMIT 20
#define MAX_TOP_PLAYERS 4
#define MAX_DISPLAY 4

public Plugin myinfo =
{
	name = "[MAP] Jump King Plus",
	author = "Kotya",
	version = "0.1.0"
};

char g_szString[256];

float g_fRoundStart;

enum struct class_record
{
	char szName[128];
	char szSteam[32];

	float fTime;
}

enum struct class_cookie
{
	Cookie PassMain;
}

enum struct class_player
{
	int iDisplayID;
	bool bPassMain;
	bool bTop;
}

class_cookie g_Cookie;
class_player g_PlayerData[MAXPLAYERS+1];

ArrayList g_alRecords;
ArrayList g_alSteamAcces;

class_record g_rLastRun;

Handle g_hTimerSkin;

float g_fRecord;

bool g_bAddRecord;

bool g_bDisplay;
bool g_bRecord;
bool g_bEnding;

public void OnPluginStart()
{
	g_hTimerSkin = INVALID_HANDLE;
	g_alSteamAcces = new ArrayList(ByteCountToCells(32));
	g_alRecords = new ArrayList(sizeof(class_record));

	// Cookie
	g_Cookie.PassMain = RegClientCookie("jump_king_plus_passmain", "Jump King beat map", CookieAccess_Private);

	// Commands
	RegAdminCmd("sm_af_remove", Command_Remove, ADMFLAG_BAN);

	// Hooks
	HookEvent("round_freeze_end", Event_FreezeEnd);
	HookEvent("round_end", Event_RoundEnd);
	HookEvent("round_start", Event_RoundStart);

	for (int iClient = 1; iClient <= MaxClients; iClient++)
	{
		if (AreClientCookiesCached(iClient))
		{
			OnClientCookiesCached(iClient);
		}
		else
		{
			OnClientDisconnect(iClient);
		}
	}
}

public void OnClientCookiesCached(int iClient)
{
	if (IsFakeClient(iClient))
	{
		return;
	}

	GetClientCookie(iClient, g_Cookie.PassMain, g_szString, sizeof(g_szString));
	g_PlayerData[iClient].bPassMain = false;
	if (g_szString[0] != '\0')
	{
		g_PlayerData[iClient].bPassMain = view_as<bool>(StringToInt(g_szString));
	}

	g_PlayerData[iClient].bTop = false;
	if (g_alSteamAcces.Length > 0)
	{
		GetClientAuthId(iClient, AuthId_Steam2, g_szString, sizeof(g_szString));

		int ID = g_alSteamAcces.FindString(g_szString);
		if (ID != -1)
		{
			g_PlayerData[iClient].bTop = true;

			g_alSteamAcces.Erase(ID);
		}
	}
}

public void OnClientDisconnect(int iClient)
{
	g_PlayerData[iClient].iDisplayID = 0;
	g_PlayerData[iClient].bPassMain = false;
	g_PlayerData[iClient].bTop = false;
}

public void Event_FreezeEnd(Event hEvent, const char[] sEvName, bool bDontBroadcast)
{
	if (g_alRecords.Length > 0)
	{
		class_record Record;
		g_alRecords.GetArray(0, Record, sizeof(Record));
		g_fRecord = Record.fTime;

		int iM = RoundToFloor(g_fRecord / 60);
		int iS = RoundToFloor(g_fRecord - (iM * 60));
		char szD[4];

		FormatEx(g_szString, sizeof(g_szString), "%.3f", (g_fRecord - RoundToFloor(g_fRecord)));
		strcopy(szD, sizeof(szD), g_szString[2]);

		CPrintToChatAll(" {default}[{red}MAP{default}] {green}%s{default} {{purple}%s{default}} map record: {orange}%i:%i:%s", Record.szName, Record.szSteam, iM, iS, szD);
	}
}

public void Event_RoundEnd(Event hEvent, const char[] sEvName, bool bDontBroadcast)
{
	if (g_bEnding)
	{
		for (int iClient = 1; iClient <= MaxClients; iClient++)
		{
			if (!IsClientInGame(iClient) ||
			IsFakeClient(iClient) ||
			!IsPlayerAlive(iClient) ||
			GetClientTeam(iClient) != 3)
			{
				continue;
			}

			g_PlayerData[iClient].bPassMain = true;
			SetClientCookie(iClient, g_Cookie.PassMain, "1");
		}
	}

	Reset();
}

public void Event_RoundStart(Event hEvent, const char[] sEvName, bool bDontBroadcast)
{
	if (g_bAddRecord)
	{
		TryInsert(g_rLastRun);
		g_bAddRecord = false;
	}

	if (g_alRecords.Length > 0)
	{
		g_alSteamAcces.Clear();

		class_record Record;
		for (int i = 0; i < g_alRecords.Length; i++)
		{
			if (i >= MAX_TOP_PLAYERS)
			{
				break;
			}
			g_alRecords.GetArray(i, Record, sizeof(Record));
			g_alSteamAcces.PushString(Record.szSteam);
		}

		for (int iClient = 1, ID; iClient <= MaxClients; iClient++)
		{
			if (!IsClientInGame(iClient) ||
			IsFakeClient(iClient))
			{
				continue;
			}
			g_PlayerData[iClient].iDisplayID = 0;
			g_PlayerData[iClient].bTop = false;

			if (g_alSteamAcces.Length > 0)
			{
				GetClientAuthId(iClient, AuthId_Steam2, g_szString, sizeof(g_szString));

				ID = g_alSteamAcces.FindString(g_szString);
				if (ID != -1)
				{
					g_PlayerData[iClient].bTop = true;

					g_alSteamAcces.Erase(ID);
				}
			}
		}

		if (g_hTimerSkin != INVALID_HANDLE)
		{
			KillTimer(g_hTimerSkin);
		}
		g_hTimerSkin = CreateTimer(34.0, Timer_SetSkin);
	}

	Reset();
}

public Action Timer_SetSkin(Handle hTimer)
{
	g_hTimerSkin = INVALID_HANDLE;

	for (int iClient = 1; iClient <= MaxClients; iClient++)
	{
		if (!IsClientInGame(iClient) ||
		IsFakeClient(iClient) ||
		!IsPlayerAlive(iClient) ||
		GetClientTeam(iClient) != 3)
		{
			continue;
		}

		if (g_PlayerData[iClient].bTop)
		{
			SetEntityModel(iClient, SKIN_FOR_TOP);
		}

		if (g_PlayerData[iClient].bPassMain)
		{
			SetCrown(iClient);
		}
	}

	return Plugin_Handled;
}

public void SetCrown(int iClient)
{
	float vecPos[3];
	int iEntity = CreateEntityByName("prop_dynamic");
	if (iEntity)
	{
		DispatchKeyValue(iEntity, "model", CROWN);
		// DispatchKeyValue(iEntity, "rendermode", "1");
		// DispatchKeyValue(iEntity, "renderamt", "192");
		DispatchKeyValue(iEntity, "disableshadows", "1");
		DispatchKeyValue(iEntity, "disableflashlight", "1");
		if (DispatchSpawn(iEntity))
		{

			GetClientAbsOrigin(iClient, vecPos);
			vecPos[2] += 73.0;

			TeleportEntity(iEntity, vecPos);


			SetVariantString("!activator");
			AcceptEntityInput(iEntity, "SetParent", iClient, iEntity, 0);
		}
	}
	int iEntity1 = CreateEntityByName("env_spritetrail");
	if (iEntity1)
	{
		DispatchKeyValue(iEntity1, "rendermode", "5");
		DispatchKeyValue(iEntity1, "spritename", "sprites/physbeam.vmt");
		DispatchKeyValue(iEntity1, "startwidth", "16");
		DispatchKeyValue(iEntity1, "rendercolor", "255 223 0");
		DispatchKeyValue(iEntity1, "lifetime", "40");
		if (DispatchSpawn(iEntity1))
		{
			GetClientAbsOrigin(iClient, vecPos);
			vecPos[2] += 24.0;

			TeleportEntity(iEntity1, vecPos);

			SetVariantString("!activator");
			AcceptEntityInput(iEntity1, "SetParent", iClient, iEntity1, 0);

			SetVariantString("OnUser1 !self,SetScale,1.0,2.0,1");
			AcceptEntityInput(iEntity1, "AddOutPut");
			AcceptEntityInput(iEntity1, "FireUser1");
		}
	}
}

public void Reset()
{
	g_bDisplay = false;
	g_bEnding = false;
}

public void OnMapEnd()
{
	Save_Config();

	if (g_hTimerSkin != INVALID_HANDLE)
	{
		KillTimer(g_hTimerSkin);
	}
	g_hTimerSkin = INVALID_HANDLE;
}

public void OnMapStart()
{
	GetCurrentMap(g_szString, sizeof(g_szString));
	if (StrContains(g_szString, "ze_jump_king") == -1)
	{
		GetPluginFilename(INVALID_HANDLE, g_szString, sizeof(g_szString));
		return;
	}

	g_alRecords.Clear();

	KeyValues kv = new KeyValues("");

	if (FileExists(DATA_CONFIG_PATH) &&
	kv.ImportFromFile(DATA_CONFIG_PATH) &&
	kv.GotoFirstSubKey()) // Has Config
	{
		int iCount = 0;
		class_record Record;

		do
		{
			kv.GetString("name", Record.szName, sizeof(Record.szName));
			kv.GetString("steam", Record.szSteam, sizeof(Record.szSteam));
			Record.fTime = kv.GetFloat("time", 5000.000);

			g_alRecords.PushArray(Record, sizeof(Record));
			iCount++;
		}
		while (kv.GotoNextKey() &&
		RECORD_LIMIT > iCount);
	}

	delete kv;

	PrecacheModel(SKIN_FOR_TOP);
	PrecacheModel(CROWN);
}


public void TryInsert(class_record newRecord)
{
	bool bSave = false;
	int Index = -1;
	class_record Record;

	for (int i = 0; i < g_alRecords.Length; i++)
	{
		g_alRecords.GetArray(i, Record, sizeof(Record));
		if (!strcmp(newRecord.szSteam, Record.szSteam))
		{
			if (Record.fTime > newRecord.fTime)
			{
				g_alRecords.Erase(i);
			}
			else
			{
				return;
			}
			break;
		}
	}

	for (int i = 0; i < g_alRecords.Length; i++)
	{
		g_alRecords.GetArray(i, Record, sizeof(Record));
		if (Record.fTime > newRecord.fTime)
		{
			Index = i;
			break;
		}
	}

	if (g_alRecords.Length < RECORD_LIMIT)
	{
		bSave = true;
		g_alRecords.PushArray(newRecord, sizeof(newRecord));
	}

	if (Index != -1)
	{
		for (int i = g_alRecords.Length - 2; i >= Index; i--)
		{
			g_alRecords.GetArray(i, Record, sizeof(Record));
			g_alRecords.SetArray(i + 1, Record, sizeof(Record));
		}
		g_alRecords.SetArray(Index, newRecord, sizeof(newRecord));
	}

	if (bSave)
	{
		Save_Config();
	}
}

public void Save_Config()
{
	KeyValues kv = new KeyValues("Data");
	class_record Record;

	for (int i = 0; i < g_alRecords.Length; i++)
	{
		FormatEx(g_szString, sizeof(g_szString), "%i", i);
		kv.JumpToKey(g_szString, true);

		g_alRecords.GetArray(i, Record, sizeof(Record));

		kv.SetString("name", Record.szName);
		kv.SetString("steam", Record.szSteam);

		FormatEx(g_szString, sizeof(g_szString), "%.3f", Record.fTime);
		kv.SetString("time", g_szString);

		kv.GoBack();
	}

	kv.ExportToFile(DATA_CONFIG_PATH);
	delete kv;
}

public void OnGameFrame()
{
	if (!g_bDisplay)
	{
		return;
	}

	float fTottal;

	if (g_bEnding)
	{
		fTottal = g_rLastRun.fTime;
	}
	else
	{
		fTottal = GetGameTime() - g_fRoundStart;
	}

	g_bRecord = (fTottal < g_fRecord);

	int iM = RoundToFloor(fTottal / 60);
	int iS = RoundToFloor(fTottal - (iM * 60));

	char szD[4];
	FormatEx(g_szString, sizeof(g_szString), "%.3f", (fTottal - RoundToFloor(fTottal)));
	strcopy(szD, sizeof(szD), g_szString[2]);


	int iColor[3] = {255, 255, 255};

	if (g_alRecords.Length > 0)
	{
		if (g_bRecord)
		{
			FormatEx(g_szString, sizeof(g_szString), "%i:%i:%s -%.3f", iM, iS, szD, (g_fRecord - fTottal));
			iColor = {255, 215, 0};
		}
		else
		{
			FormatEx(g_szString, sizeof(g_szString), "%i:%i:%s +%.3f", iM, iS, szD, (fTottal - g_fRecord));

			if (fTottal - g_fRecord < 20.0)
			{
				iColor = {0, 255, 0};
			}
			else
			{
				iColor = {255, 0, 0};
			}
		}
	}
	else
	{
		FormatEx(g_szString, sizeof(g_szString), "%i:%i:%s", iM, iS, szD);
		iColor = {255, 215, 0};
	}

	SetHudTextParams(-1.0, 0.15, 1.5, iColor[0], iColor[1], iColor[2], 0, 0, 0.25, 0.0, 0.0);

	for (int iClient = 1; iClient <= MaxClients; iClient++)
	{
		if (IsClientInGame(iClient))
		{
			ShowHudText(iClient, 1, g_szString);
		}
	}
}

public Action Command_Remove(int iClient, int iArgs)
{
	// Hook_EndTimer("123", 0, iClient, 0.00);

	// return Plugin_Handled;
	GetCmdArgString(g_szString, sizeof(g_szString));
	class_record Record;

	for (int i = 0; i < g_alRecords.Length; i++)
	{
		g_alRecords.GetArray(i, Record, sizeof(Record));
		if (!strcmp(Record.szSteam, g_szString))
		{
			g_alRecords.Erase(i);
			Save_Config();
			break;
		}
	}

	return Plugin_Handled;
}

public void OnEntityCreated(int iEntity, const char[] szClassName)
{
	if (!strcmp(szClassName, "trigger_once") ||
	!strcmp(szClassName, "trigger_multiple") ||
	!strcmp(szClassName, "logic_relay"))
	{
		SDKHook(iEntity, SDKHook_SpawnPost, Hook_SpawnPost);
	}
}
public void Hook_SpawnPost(int iEntity)
{
	SDKUnhook(iEntity, SDKHook_SpawnPost, Hook_SpawnPost);

	if (!IsValidEntity(iEntity))
	{
		return;
	}

	Entity_GetTargetName(iEntity, g_szString, sizeof(g_szString));

	if (g_szString[0] == '\0')
	{
		return;
	}

	if (!strcmp(g_szString, "speedrun_timer_start"))
	{
		HookSingleEntityOutput(iEntity, "OnStartTouch", Hook_StartTimer, true);
	}
	else if (!strcmp(g_szString, "speedrun_stop_timer"))
	{
		HookSingleEntityOutput(iEntity, "OnTrigger", Hook_EndTimer, true);
	}
	else if (!strcmp(g_szString, "nigger"))
	{
		HookSingleEntityOutput(iEntity, "OnStartTouch", Hook_ShowTop);
	}
}

public void Hook_ShowTop(const char[] szOutput, int iCaller, int iClient, float fDelay)
{
	SetHudTextParams(-1.0, 0.6, 4.0, 0, 255, 255, 0, 0, 0.25, 0.0, 0.0);
	if (g_alRecords.Length < 1)
	{
		ShowHudText(iClient, 1, "No records");
		return;
	}

	class_record Record;
	char szString[512];

	int iM;
	int iS;
	char szD[4];

	int iStart = g_PlayerData[iClient].iDisplayID * MAX_DISPLAY;
	int iEnd = iStart + MAX_DISPLAY;
	if (iEnd > g_alRecords.Length)
	{
		iEnd = g_alRecords.Length;
	}

	bool bFind;

	for (int i = iStart; i < iEnd; i++)
	{
		g_alRecords.GetArray(i, Record, sizeof(Record));

		GetClientAuthId(iClient, AuthId_Steam2, g_szString, sizeof(g_szString));
		bFind = (!strcmp(g_szString, Record.szSteam));

		iM = RoundToFloor(Record.fTime / 60);
		iS = RoundToFloor(Record.fTime - (iM * 60));
		FormatEx(g_szString, sizeof(g_szString), "%.3f", (Record.fTime - RoundToFloor(Record.fTime)));
		strcopy(szD, sizeof(szD), g_szString[2]);

		if (bFind)
		{
			Format(szString, sizeof(szString), "%s>>> %i. %s {%s} - %im %is %sms <<<", szString, i+1, Record.szName, Record.szSteam[6], iM, iS, szD);
		}
		else
		{
			Format(szString, sizeof(szString), "%s%i. %s {%s} - %im %is %sms", szString, i+1, Record.szName, Record.szSteam[6], iM, iS, szD);
		}

		if (i + 1 < iEnd)
		{
			StrCat(szString, sizeof(szString), "\n");
		}
	}

	ShowHudText(iClient, 1, szString);

	g_PlayerData[iClient].iDisplayID++;
	if (g_PlayerData[iClient].iDisplayID >= RoundToCeil(float(g_alRecords.Length) / MAX_TOP_PLAYERS))
	{
		g_PlayerData[iClient].iDisplayID = 0;
	}
}

public void Hook_StartTimer(const char[] szOutput, int iCaller, int iActivator, float fDelay)
{
	g_fRoundStart = GetGameTime();
	g_bDisplay = true;
}

public void Hook_EndTimer(const char[] szOutput, int iCaller, int iClient, float fDelay)
{
	g_bEnding = true;
	g_bAddRecord = true;

	char szSteam[32];
	GetClientAuthId(iClient, AuthId_Steam2, szSteam, sizeof(szSteam));

	FormatEx(g_rLastRun.szName, sizeof(g_rLastRun.szName), "%N", iClient);
	FormatEx(g_rLastRun.szSteam, sizeof(g_rLastRun.szSteam), szSteam);
	g_rLastRun.fTime = GetGameTime() - g_fRoundStart;

	g_bRecord = (g_rLastRun.fTime < g_fRecord);

	int iM = RoundToFloor(g_rLastRun.fTime / 60);
	int iS = RoundToFloor(g_rLastRun.fTime - (iM * 60));
	char szD[4];
	FormatEx(g_szString, sizeof(g_szString), "%.3f", (g_rLastRun.fTime - RoundToFloor(g_rLastRun.fTime)));
	strcopy(szD, sizeof(szD), g_szString[2]);

	if (g_alRecords.Length > 0)
	{
		if (g_bRecord)
		{
			FormatEx(g_szString, sizeof(g_szString), " {default}[{red}MAP{default}] {green}%N{default} {{purple}%s{default}} new record map: {orange}%i:%i:%s {green}-%.3f", iClient, szSteam, iM, iS, szD, (g_fRecord - g_rLastRun.fTime));
		}
		else
		{
			FormatEx(g_szString, sizeof(g_szString), " {default}[{red}MAP{default}] {green}%N{default} {{purple}%s{default}} complete map: %i:%i:%s {red}+%.3f", iClient, szSteam, iM, iS, szD, (g_rLastRun.fTime - g_fRecord));
		}
	}
	else
	{
		FormatEx(g_szString, sizeof(g_szString), " {default}[{red}MAP{default}] {green}%N{default} {{purple}%s{default}} new record map: {orange}%i:%i:%s", iClient, szSteam, iM, iS, szD);
	}

	CPrintToChatAll(g_szString);
}
