//====================================================================================================
//
// Name: [entWatch] Core
// Author: zaCade & Prometheum
// Description: Handle the core functions of [entWatch]
//
//====================================================================================================
#include <smlib>

#pragma newdecls required

#include <sourcemod>
#include <sdkhooks>
#include <sdktools>
#include <basic>

/* CLASSES */
#include "CConfig.inc"
#include "CItem.inc"

/* BOOLS */
bool g_bLate;
bool g_bIntermission;

/* ARRAYS */
ArrayList g_hArray_Items;
ArrayList g_hArray_Configs;

/* FORWARDS */
Handle g_hFwd_OnClientItemDrop;
Handle g_hFwd_OnClientItemDeath;
Handle g_hFwd_OnClientItemPickup;
Handle g_hFwd_OnClientItemActivate;
Handle g_hFwd_OnClientItemDisconnect;

/* HOOKS */
Handle g_hFwd_OnClientItemCanPickup;
Handle g_hFwd_OnClientItemCanActivate;

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Plugin myinfo =
{
	name         = "[entWatch] Core",
	author       = "zaCade & Prometheum",
	description  = "Handle the core functions of [entWatch]",
	version      = "4.0.0"
};

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public APLRes AskPluginLoad2(Handle hMyself, bool bLate, char[] sError, int errorSize)
{
	g_bLate = bLate;

	CreateNative("EW_GetItemCount", Native_GetItemCount);

	RegPluginLibrary("entWatch-core");
	return APLRes_Success;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnPluginStart()
{
	g_hFwd_OnClientItemDrop        = CreateGlobalForward("EW_OnClientItemDrop",        ET_Ignore, Param_Cell, Param_Cell);
	g_hFwd_OnClientItemDeath       = CreateGlobalForward("EW_OnClientItemDeath",       ET_Ignore, Param_Cell, Param_Cell);
	g_hFwd_OnClientItemPickup      = CreateGlobalForward("EW_OnClientItemPickup",      ET_Ignore, Param_Cell, Param_Cell);
	g_hFwd_OnClientItemActivate    = CreateGlobalForward("EW_OnClientItemActivate",    ET_Ignore, Param_Cell, Param_Cell);
	g_hFwd_OnClientItemDisconnect  = CreateGlobalForward("EW_OnClientItemDisconnect",  ET_Ignore, Param_Cell, Param_Cell);

	g_hFwd_OnClientItemCanPickup   = CreateGlobalForward("EW_OnClientItemCanPickup",   ET_Hook, Param_Cell, Param_Cell);
	g_hFwd_OnClientItemCanActivate = CreateGlobalForward("EW_OnClientItemCanActivate", ET_Hook, Param_Cell, Param_Cell);

	g_hArray_Items   = new ArrayList();
	g_hArray_Configs = new ArrayList();

	HookEvent("player_death", OnClientDeath);
	HookEvent("round_start",  OnRoundStart);
	HookEvent("round_end",    OnRoundEnd);

	if (g_bLate)
	{
		for (int client = 1; client <= MaxClients; client++)
		{
			if (!IsClientInGame(client) || IsFakeClient(client))
				continue;

			SDKHook(client, SDKHook_WeaponEquipPost, OnWeaponPickup);
			SDKHook(client, SDKHook_WeaponDropPost, OnWeaponDrop);
			SDKHook(client, SDKHook_WeaponCanUse, OnWeaponTouch);
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnMapStart()
{
	g_hArray_Items.Clear();
	g_hArray_Configs.Clear();

	char sCurrentMap[128];
	GetCurrentMap(sCurrentMap, sizeof(sCurrentMap));
	String_ToLower(sCurrentMap, sCurrentMap, sizeof(sCurrentMap));

	char sFilePathDefault[PLATFORM_MAX_PATH];
	char sFilePathOverride[PLATFORM_MAX_PATH];

	BuildPath(Path_SM, sFilePathDefault, sizeof(sFilePathDefault), "configs/entwatch/%s.cfg", sCurrentMap);
	BuildPath(Path_SM, sFilePathOverride, sizeof(sFilePathOverride), "configs/entwatch/%s.override.cfg", sCurrentMap);

	KeyValues hConfig = new KeyValues("items");

	if (FileExists(sFilePathOverride))
	{
		if (!hConfig.ImportFromFile(sFilePathOverride))
		{
			LogMessage("Unable to load config \"%s\"!", sFilePathOverride);

			delete hConfig;
			return;
		}
		else LogMessage("Loaded config \"%s\"", sFilePathOverride);
	}
	else
	{
		if (!hConfig.ImportFromFile(sFilePathDefault))
		{
			LogMessage("Unable to load config \"%s\"!", sFilePathDefault);

			delete hConfig;
			return;
		}
		else LogMessage("Loaded config \"%s\"", sFilePathDefault);
	}

	if (hConfig.GotoFirstSubKey())
	{
		do
		{
			CConfig config = new CConfig();

			char sName[64], sShort[64], sColor[64], sFilter[64];
			hConfig.GetString("name",   sName,   sizeof(sName));
			hConfig.GetString("short",  sShort,  sizeof(sShort));
			hConfig.GetString("color",  sColor,  sizeof(sColor));
			hConfig.GetString("filter", sFilter, sizeof(sFilter));

			config.SetName(sName);
			config.SetShort(sShort);
			config.SetColor(sColor);
			config.SetFilter(sFilter);

			config.iWeaponID  = hConfig.GetNum("weaponid");
			config.iButtonID  = hConfig.GetNum("buttonid");
			config.iTriggerID = hConfig.GetNum("triggerid");
			config.iDisplay   = hConfig.GetNum("display");
			config.iMode      = hConfig.GetNum("mode");
			config.iMaxUses   = hConfig.GetNum("maxuses");
			config.iCooldown  = hConfig.GetNum("cooldown");

			g_hArray_Configs.Push(config);
		}
		while (hConfig.GotoNextKey());
	}

	delete hConfig;
	return;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnRoundStart(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{
	g_bIntermission = false;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnRoundEnd(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{
	if (g_hArray_Items.Length)
		g_hArray_Items.Clear();

	g_bIntermission = true;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnEntityCreated(int entity, const char[] sClassname)
{
	if (Entity_IsValid(entity))
	{
		SDKHook(entity, SDKHook_SpawnPost, OnEntitySpawned);
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnEntitySpawned(int entity)
{
	if (Entity_IsValid(entity) && g_hArray_Configs.Length)
	{
		for (int index; index < g_hArray_Configs.Length; index++)
		{
			CConfig config = g_hArray_Configs.Get(index);

			if (config.iWeaponID && config.iWeaponID == Entity_GetHammerId(entity))
			{
				if (PerformRegisterItem(config, entity, 1))
					return;
			}
			else if (config.iButtonID && config.iButtonID == Entity_GetHammerId(entity))
			{
				if (PerformRegisterItem(config, entity, 2))
					return;
			}
			else if (config.iTriggerID && config.iTriggerID == Entity_GetHammerId(entity))
			{
				if (PerformRegisterItem(config, entity, 3))
					return;
			}
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock bool PerformRegisterItem(CConfig config, int entity, int type)
{
	bool bSuccessful;

	if (Entity_IsValid(entity))
	{
		if (g_hArray_Items.Length)
		{
			for (int index; index < g_hArray_Items.Length; index++)
			{
				CItem item = g_hArray_Items.Get(index);

				if (AttemptRegisterItem(item, entity, type))
				{
					g_hArray_Items.Set(index, item);
					bSuccessful = true;
					break;
				}
			}
		}

		if (!bSuccessful)
		{
			CItem item = new CItem(config);

			if (AttemptRegisterItem(item, entity, type))
			{
				g_hArray_Items.Push(item);
				bSuccessful = true;
			}
			else
			{
				LogError("Attempted to register new item, but failed! This should never happen.");

				delete item;
			}
		}
	}

	return bSuccessful;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
stock bool AttemptRegisterItem(CItem item, int entity, int type)
{
	if (Entity_IsValid(entity))
	{
		switch(type)
		{
			case(1):
			{
				if (!item.bWeapon && (Entity_GetOwner(entity) == INVALID_ENT_REFERENCE))
				{
					item.iWeapon = entity;
					return true;
				}
			}
			case(2):
			{
				if (!item.bButton && (Entity_GetParent(entity) == INVALID_ENT_REFERENCE || (item.bWeapon && Entity_GetParent(entity) == item.iWeapon)))
				{
					SDKHook(entity, SDKHook_Use, OnButtonPress);

					item.iButton = entity;
					return true;
				}
			}
			case(3):
			{
				if (!item.bTrigger && (Entity_GetParent(entity) == INVALID_ENT_REFERENCE ||	(item.bWeapon && Entity_GetParent(entity) == item.iWeapon)))
				{
					SDKHook(entity, SDKHook_StartTouch, OnTriggerTouch);
					SDKHook(entity, SDKHook_EndTouch, OnTriggerTouch);
					SDKHook(entity, SDKHook_Touch, OnTriggerTouch);

					item.iTrigger = entity;
					return true;
				}
			}
			default:
			{
				LogError("Attempted to register item with invalid type: %d! This should never happen.", type);
				return false;
			}
		}
	}

	return false;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnEntityDestroyed(int entity)
{
	if (Entity_IsValid(entity) && g_hArray_Items.Length)
	{
		for (int index; index < g_hArray_Items.Length; index++)
		{
			CItem item = g_hArray_Items.Get(index);

			if (item.bWeapon && item.iWeapon == entity)
			{
				g_hArray_Items.Erase(index);

				delete item;
				return;
			}
			else if (item.bButton && item.iButton == entity)
			{
				item.iButton = INVALID_ENT_REFERENCE;

				g_hArray_Items.Set(index, item);
				return;
			}
			else if (item.bTrigger && item.iTrigger == entity)
			{
				item.iTrigger = INVALID_ENT_REFERENCE;

				g_hArray_Items.Set(index, item);
				return;
			}
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientPutInServer(int client)
{
	if (!IsFakeClient(client))
	{
		SDKHook(client, SDKHook_WeaponEquipPost, OnWeaponPickup);
		SDKHook(client, SDKHook_WeaponDropPost, OnWeaponDrop);
		SDKHook(client, SDKHook_WeaponCanUse, OnWeaponTouch);
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientDisconnect(int client)
{
	if (!IsFakeClient(client) && g_hArray_Items.Length)
	{
		for (int index; index < g_hArray_Items.Length; index++)
		{
			CItem item = g_hArray_Items.Get(index);

			if (item.bOwner && item.iOwner == client)
			{
				item.iOwner = INVALID_ENT_REFERENCE;

				Call_StartForward(g_hFwd_OnClientItemDisconnect);
				Call_PushCell(client);
				Call_PushCell(index);
				Call_Finish();

				g_hArray_Items.Set(index, item);
			}
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public void OnClientDeath(Event hEvent, const char[] sEvent, bool bDontBroadcast)
{
	int client = GetClientOfUserId(hEvent.GetInt("userid"));

	if (Client_IsValid(client) && !IsFakeClient(client) && g_hArray_Items.Length)
	{
		for (int index; index < g_hArray_Items.Length; index++)
		{
			CItem item = g_hArray_Items.Get(index);

			if (item.bOwner && item.iOwner == client)
			{
				item.iOwner = INVALID_ENT_REFERENCE;

				Call_StartForward(g_hFwd_OnClientItemDeath);
				Call_PushCell(client);
				Call_PushCell(index);
				Call_Finish();

				g_hArray_Items.Set(index, item);
			}
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnWeaponPickup(int client, int weapon)
{
	if (Client_IsValid(client) && Entity_IsValid(weapon) && g_hArray_Items.Length)
	{
		for (int index; index < g_hArray_Items.Length; index++)
		{
			CItem item = g_hArray_Items.Get(index);

			if (item.bWeapon && item.iWeapon == weapon)
			{
				item.iOwner = client;

				Call_StartForward(g_hFwd_OnClientItemPickup);
				Call_PushCell(client);
				Call_PushCell(index);
				Call_Finish();

				g_hArray_Items.Set(index, item);
				return;
			}
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnWeaponDrop(int client, int weapon)
{
	if (Client_IsValid(client) && Entity_IsValid(weapon) && g_hArray_Items.Length)
	{
		for (int index; index < g_hArray_Items.Length; index++)
		{
			CItem item = g_hArray_Items.Get(index);

			if (item.bWeapon && item.iWeapon == weapon)
			{
				item.iOwner = INVALID_ENT_REFERENCE;

				Call_StartForward(g_hFwd_OnClientItemDrop);
				Call_PushCell(client);
				Call_PushCell(index);
				Call_Finish();

				g_hArray_Items.Set(index, item);
				return;
			}
		}
	}
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnButtonPress(int button, int client)
{
	if (Client_IsValid(client) && Entity_IsValid(button) && g_hArray_Items.Length)
	{
		if (HasEntProp(button, Prop_Data, "m_bLocked") &&
			GetEntProp(button, Prop_Data, "m_bLocked"))
			return Plugin_Handled;

		for (int index; index < g_hArray_Items.Length; index++)
		{
			CItem item = g_hArray_Items.Get(index);

			CConfig config = item.dConfig;

			if (item.bButton && item.iButton == button && item.bOwner && item.iOwner == client)
			{
				Action aResult;
				Call_StartForward(g_hFwd_OnClientItemCanActivate);
				Call_PushCell(client);
				Call_PushCell(index);
				Call_Finish(aResult);

				if ((aResult == Plugin_Continue) || (aResult == Plugin_Changed))
				{
					switch(config.iMode)
					{
						case(1):
						{
							if (item.iTimeReady < RoundToCeil(GetEngineTime()))
							{
								item.iTimeReady = RoundToCeil(GetEngineTime()) + config.iCooldown;
							}
							else return Plugin_Handled;
						}
						case(2):
						{
							if (item.iTimesUsed < config.iMaxUses)
							{
								item.iTimesUsed++;
							}
							else return Plugin_Handled;
						}
						case(3):
						{
							if (item.iTimeReady < RoundToCeil(GetEngineTime()) && item.iTimesUsed < config.iMaxUses)
							{
								item.iTimeReady = RoundToCeil(GetEngineTime()) + config.iCooldown;
								item.iTimesUsed++;
							}
							else return Plugin_Handled;
						}
						case(4):
						{
							if (item.iTimeReady < RoundToCeil(GetEngineTime()))
							{
								item.iTimesUsed++;

								if (item.iTimesUsed >= config.iMaxUses)
								{
									item.iTimeReady = RoundToCeil(GetEngineTime()) + config.iCooldown;
									item.iTimesUsed = 0;
								}
							}
							else return Plugin_Handled;
						}
					}

					char sFilter[64];
					if (config.GetFilter(sFilter, sizeof(sFilter)))
						Entity_SetName(client, sFilter);

					Call_StartForward(g_hFwd_OnClientItemActivate);
					Call_PushCell(client);
					Call_PushCell(index);
					Call_Finish();
				}

				g_hArray_Items.Set(index, item);
				return aResult;
			}
		}
	}
	return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnTriggerTouch(int trigger, int client)
{
	if (Client_IsValid(client) && Entity_IsValid(trigger) && g_hArray_Items.Length)
	{
		for (int index; index < g_hArray_Items.Length; index++)
		{
			CItem item = g_hArray_Items.Get(index);

			if (item.bTrigger && item.iTrigger == trigger)
			{
				if (g_bIntermission)
					return Plugin_Handled;

				Action aResult;
				Call_StartForward(g_hFwd_OnClientItemCanPickup);
				Call_PushCell(client);
				Call_PushCell(index);
				Call_Finish(aResult);

				g_hArray_Items.Set(index, item);
				return aResult;
			}
		}
	}
	return Plugin_Handled;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public Action OnWeaponTouch(int client, int weapon)
{
	if (Client_IsValid(client) && Entity_IsValid(weapon) && g_hArray_Items.Length)
	{
		for (int index; index < g_hArray_Items.Length; index++)
		{
			CItem item = g_hArray_Items.Get(index);

			if (item.bWeapon && item.iWeapon == weapon)
			{
				if (g_bIntermission)
					return Plugin_Handled;

				Action aResult;
				Call_StartForward(g_hFwd_OnClientItemCanPickup);
				Call_PushCell(client);
				Call_PushCell(index);
				Call_Finish(aResult);

				g_hArray_Items.Set(index, item);
				return aResult;
			}
		}
	}
	return Plugin_Continue;
}

//----------------------------------------------------------------------------------------------------
// Purpose:
//----------------------------------------------------------------------------------------------------
public int Native_GetItemCount(Handle hPlugin, int numParams)
{
	return g_hArray_Items.Length;
}