#pragma semicolon 1

#include <sourcemod>

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

/**
 * Checks if an entity is a corner.
 *
 * @param iCorner		The corner.
 * @return				True if valid, false otherwise.
 */

stock bool Corner_IsValid(int iCorner) {
	if (!IsValidEntity(iCorner)) {
		return false;
	}
	
	char sBuffer[64];
	GetEntityClassname(iCorner, sBuffer, sizeof(sBuffer));
	
	if (StrEqual(sBuffer, "trigger_multiple")) {
		GetEntPropString(iCorner, Prop_Data, "m_iName", sBuffer, sizeof(sBuffer));
		
		return (StrContains(sBuffer, "corner_") != -1);
	}
	
	return false;
}

/**
 * Finds a corner by name.
 *
 * @param sName			The corners name.
 * @return				The corners entity index, or -1 on failure.
 */

stock int Corner_FindByName(const char[] sName) {
	int iCorner = -1;
	char sCornerName[64];
	
	while ((iCorner = FindEntityByClassname(iCorner, "trigger_multiple")) != -1) {
		Corner_GetName(iCorner, sCornerName, sizeof(sCornerName));
		
		if (StrEqual(sCornerName, sName)) {
			return iCorner;
		}
	}
	
	return -1;
}

/**
 * Finds a corner by it's next corners name.
 *
 * @param sName			The next corners name.
 * @return				The corners entity index, or -1 on failure.
 */

stock int Corner_FindByNextName(const char[] sName) {
	int iCorner = -1;
	char sCornerName[64];
	
	while ((iCorner = FindEntityByClassname(iCorner, "trigger_multiple")) != -1) {
		Corner_GetNextName(iCorner, sCornerName, sizeof(sCornerName));
		
		if (StrEqual(sCornerName, sName)) {
			return iCorner;
		}
	}
	
	return -1;
}

/**
 * Gets a corners name.
 *
 * @param iCorner 		The corner.
 * @param bValidate		Validate the corner.
 * @return				The corner number, or -1 on failure.
 */

stock int Corner_GetNumber(int iCorner, bool bValidate = true) {
	if (!bValidate) {
		char sName[64];
		Corner_GetName(iCorner, sName, sizeof(sName), false);
		
		if (!StrEqual(sName, "corner_final")) {
			char sNameParts[2][32];
			ExplodeString(sName, "_", sNameParts, sizeof(sNameParts), sizeof(sNameParts[]));
			
			return StringToInt(sNameParts[1]);
		}
	} else if (Corner_IsValid(iCorner)) {
		char sName[64];
		Corner_GetName(iCorner, sName, sizeof(sName), false);
		
		if (!StrEqual(sName, "corner_final")) {
			char sNameParts[2][32];
			ExplodeString(sName, "_", sNameParts, sizeof(sNameParts), sizeof(sNameParts[]));
			
			return StringToInt(sNameParts[1]);
		}
	}
	
	return -1;
}

/**
 * Gets a corners name.
 *
 * @param iCorner 		The corner.
 * @param sBuffer		The destination string buffer.
 * @param iMaxLength	The maximum length of the output string buffer.
 * @param bValidate		Validate the corner.
 * @return				True on success, false if corner was not found.
 */

stock bool Corner_GetName(int iCorner, char[] sBuffer, int iMaxLength, bool bValidate = true) {
	if (!bValidate) {
		GetEntPropString(iCorner, Prop_Data, "m_iName", sBuffer, iMaxLength);
		return true;
	} else if (Corner_IsValid(iCorner)) {
		GetEntPropString(iCorner, Prop_Data, "m_iName", sBuffer, iMaxLength);
		return true;
	}
	
	return false;
}

/**
 * Gets a corners next corner name.
 *
 * @param iCorner 		The corner.
 * @param sBuffer		The destination string buffer.
 * @param iMaxLength	The maximum length of the output string buffer.
 * @param bValidate		Validate the corner.
 * @return				True on success, false if corner was not found.
 */

stock bool Corner_GetNextName(int iCorner, char[] sBuffer, int iMaxLength, bool bValidate = true) {
	if (!bValidate) {
		GetEntPropString(iCorner, Prop_Data, "m_iParent", sBuffer, iMaxLength);
		return true;
	} else if (Corner_IsValid(iCorner)) {
		GetEntPropString(iCorner, Prop_Data, "m_iParent", sBuffer, iMaxLength);
		return true;
	}
	
	return false;
}

/**
 * Gets a corners location.
 *
 * @param iCorner 		The corner.
 * @param fLocation		The location vector.
 * @param bValidate		Validate the corner.
 * @return				True on success, false if corner was not found.
 */

stock bool Corner_GetLocation(int iCorner, float fLocation[3], bool bValidate = true) {
	if (!bValidate) {
		GetEntPropVector(iCorner, Prop_Data, "m_vecAbsOrigin", fLocation);
		return true;
	} else if (Corner_IsValid(iCorner)) {
		GetEntPropVector(iCorner, Prop_Data, "m_vecAbsOrigin", fLocation);
		return true;
	}
	
	return false;
}

/**
 * Gets the angle between two corners.
 *
 * @param iCornerA 		The first corner.
 * @param iCornerB 		The second corner.
 * @param fAngles		The angles vector.
 * @param bValidate		Validate the corner.
 * @return				True on success, false if corner was not found.
 */

stock bool Corner_GetAngles(int iCornerA, int iCornerB, float fAngles[3], bool bValidate = true) {
	float fLocationCornerA[3], fLocationCornerB[3], fVector[3];
	
	if (!bValidate) {
		Corner_GetLocation(iCornerA, fLocationCornerA, false);
		Corner_GetLocation(iCornerB, fLocationCornerB, false);
		
		MakeVectorFromPoints(fLocationCornerA, fLocationCornerB, fVector);
		GetVectorAngles(fVector, fAngles);
	} else if (Corner_IsValid(iCornerA) && Corner_IsValid(iCornerB)) {
		Corner_GetLocation(iCornerA, fLocationCornerA, false);
		Corner_GetLocation(iCornerB, fLocationCornerB, false);
		
		MakeVectorFromPoints(fLocationCornerA, fLocationCornerB, fVector);
		GetVectorAngles(fVector, fAngles);
	}
	
	return false;
}

/**
 * Gets the next corner.
 *
 * @param iCorner		The current corner.
 * @param sName			The current corners name (optional).
 * @return				The next corners entity index, or -1 on failure.
 */

stock int Corner_GetNext(int iCorner = -1, const char[] sName = "") {
	char sCornerName[64];
	
	if (StrEqual(sName, "") && Corner_IsValid(iCorner)) {
		Corner_GetName(iCorner, sCornerName, sizeof(sCornerName));
	} else if (!StrEqual(sName, "") && !Corner_IsValid(iCorner)) {
		strcopy(sCornerName, sizeof(sCornerName), sName);
		iCorner = Corner_FindByName(sCornerName);
	} else {
		return -1;
	}
	
	if (StrContains(sCornerName, "corner_") != -1 && !StrEqual(sCornerName, "corner_final")) {
		char sNextName[64];
		Corner_GetNextName(iCorner, sNextName, sizeof(sNextName));
		
		return Corner_FindByName(sNextName);
	}
	
	return -1;
}

/**
 * Gets the previous corner.
 *
 * @param iCorner		The current corner.
 * @param sName			The current corners name (optional).
 * @return				The previous corners entity index, or -1 on failure.
 */

stock int Corner_GetPrevious(int iCorner = -1, const char[] sName = "") {
	char sCornerName[64];
	
	if (StrEqual(sName, "") && Corner_IsValid(iCorner)) {
		Corner_GetName(iCorner, sCornerName, sizeof(sCornerName));
	} else if (!StrEqual(sName, "") && !Corner_IsValid(iCorner)) {
		strcopy(sCornerName, sizeof(sCornerName), sName);
		iCorner = Corner_FindByName(sCornerName);
	} else {
		return -1;
	}
	
	if (StrContains(sCornerName, "corner_") != -1) {
		return Corner_FindByNextName(sCornerName);
	}
	
	return -1;
}

/**
 * Gets the nearest corner to a client.
 *
 * @param iClient		The client.
 * @return				The corners entity index, or -1 on failure.
 */

stock int Corner_GetNearest(int iClient) {
	if (!IsValidClient(iClient) || !IsClientInGame(iClient) || !IsPlayerAlive(iClient)) {
		return -1;
	}
	
	float fCornerLocation[3], fClientLocation[3];
	GetClientAbsOrigin(iClient, fClientLocation);
	
	int iCorner = -1, iNearestCorner = -1;
	float fNearestDistance = 2147483647.0, fDistance;
	
	while ((iCorner = FindEntityByClassname(iCorner, "trigger_multiple")) != -1) {
		if (Corner_IsValid(iCorner)) {
			Corner_GetLocation(iCorner, fCornerLocation, false);
			
			fDistance = GetVectorDistance(fCornerLocation, fClientLocation);
			
			if (fDistance < fNearestDistance) {
				iNearestCorner = iCorner;
				fNearestDistance = fDistance;
			}
		}
	}
	
	return iNearestCorner;
}

/**
 * Gets the two corners a client is in between.
 *
 * @param iClient		The client.
 * @param iCornerA		The first corner.
 * @param iCornerB		The second corner.
 * @param fEpsilon		The possible deviation.
 * @return				False if not between any corners, true otherwise.
 */

stock bool Corner_GetBetween(int iClient, int &iCornerA, int &iCornerB, float fEpsilon = 15.0) {
	if (!IsValidClient(iClient) || !IsClientInGame(iClient) || !IsPlayerAlive(iClient)) {
		return false;
	}
	
	static int iNearBefore[MAXPLAYERS + 1];
	
	int iNear = Corner_GetNearest(iClient);
	
	float fLocationNear[3], fLocationClient[3];
	Corner_GetLocation(iNear, fLocationNear, false);
	GetClientAbsOrigin(iClient, fLocationClient);
	
	float fVector[3], fAngles[3], fAnglesClient[3];
	MakeVectorFromPoints(fLocationNear, fLocationClient, fVector);
	GetVectorAngles(fVector, fAngles);
	GetClientEyeAngles(iClient, fAnglesClient);
	
	float fAnglesDiff = FloatAbs(fAngles[1] - fAnglesClient[1]);
	
	if (fAnglesDiff != 0.0 && fAnglesDiff != 45.0 && fAnglesDiff != 90.0 && fAnglesDiff != 135.0 && fAnglesDiff != 180.0 && 
		fAnglesDiff != 225.0 && fAnglesDiff != 270.0 && fAnglesDiff != 315.0 && fAnglesDiff != 360.0 && fAnglesDiff > 5.0) {
		
		return false;
	}
	
	int iNumberNearBefore = Corner_GetNumber(iNearBefore[iClient]);
	
	if (iNumberNearBefore != -1 && IsValidEntity(iNear)) {
		int iNumberNear = Corner_GetNumber(iNear);
		
		if (iNumberNear != -1) {
			if (Abs(iNumberNearBefore - iNumberNear) > 1) {
				iNearBefore[iClient] = iNear;
				return false;
			}
		}
	}
	
	int iNext = Corner_GetNext(iNear);
	
	float fLocationNext[3];
	Corner_GetLocation(iNext, fLocationNext);
	
	float fResultA[3], fResultB[3];
	SubtractVectors(fLocationNear, fLocationNext, fResultA);
	SubtractVectors(fLocationClient, fLocationNext, fResultB);
	
	float fScale = GetVectorDotProduct(fResultA, fResultB) / GetVectorDotProduct(fResultA, fResultA);
	ScaleVector(fResultA, fScale);
	
	float fResult[3];
	SubtractVectors(fResultB, fResultA, fResult);
	
	if (FloatAbs(fResult[0]) <= fEpsilon && FloatAbs(fResult[1]) <= fEpsilon) {
		iCornerA = iNear;
		iCornerB = iNext;
		
		iNearBefore[iClient] = iNear;
		return true;
	}
	
	int iPrev = Corner_GetPrevious(iNear);
	
	float fLocationPrev[3];
	Corner_GetLocation(iPrev, fLocationPrev);
	
	SubtractVectors(fLocationNear, fLocationPrev, fResultA);
	SubtractVectors(fLocationClient, fLocationPrev, fResultB);
	
	fScale = GetVectorDotProduct(fResultA, fResultB) / GetVectorDotProduct(fResultA, fResultA);
	ScaleVector(fResultA, fScale);
	
	SubtractVectors(fResultB, fResultA, fResult);
	
	if (FloatAbs(fResult[0]) <= fEpsilon && FloatAbs(fResult[1]) <= fEpsilon) {
		iCornerA = iPrev;
		iCornerB = iNear;
		
		iNearBefore[iClient] = iNear;
		return true;
	}
	
	return false;
} 