//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

// Author: Michael S. Booth (mike@turtlerockstudios.com), 2003

#include "cbase.h"
#include "cs_shareddefs.h"
#include "engine/IEngineSound.h"
#include "keyvalues.h"

#include "bot.h"
#include "bot_util.h"
#include "bot_profile.h"

#include "cs_bot.h"
#include <ctype.h>
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

static int s_iBeamSprite = 0;

extern ConVar mp_randomspawn_dist;
extern ConVar mp_randomspawn_los;

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if given name is already in use by another player
 */
bool UTIL_IsNameTaken( const char *name, bool ignoreHumans )
{
	for ( int i = 1; i <= gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );

		if (player == NULL)
			continue;

		if (player->IsPlayer() && player->IsBot())
		{
			// bots can have prefixes so we need to check the name
			// against the profile name instead.
			CCSBot *bot = dynamic_cast<CCSBot *>(player);
			if ( bot && bot->GetProfile()->GetName() && FStrEq(name, bot->GetProfile()->GetName()))
			{
				return true;
			}
		}
		else
		{
			if (!ignoreHumans)
			{
				if (FStrEq( name, player->GetPlayerName() ))
					return true;
			}
		}
	}

	return false;
}


//--------------------------------------------------------------------------------------------------------------
int UTIL_ClientsInGame( void )
{
	int count = 0;

	for ( int i = 1; i <= gpGlobals->maxClients; ++i )
	{
		CBaseEntity *player = UTIL_PlayerByIndex( i );

		if (player == NULL)
			continue;

		count++;
	}

	return count;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return the number of non-bots on the given team
 */
int UTIL_HumansOnTeam( int teamID, bool isAlive )
{
	int count = 0;

	for ( int i = 1; i <= gpGlobals->maxClients; ++i )
	{
		CBaseEntity *entity = UTIL_PlayerByIndex( i );

		if ( entity == NULL )
			continue;

		CBasePlayer *player = static_cast<CBasePlayer *>( entity );

		if (player->IsBot())
			continue;

		if (player->GetTeamNumber() != teamID)
			continue;

		if (isAlive && !player->IsAlive())
			continue;

		count++;
	}

	return count;
}


//--------------------------------------------------------------------------------------------------------------
int UTIL_BotsInGame( void )
{
	int count = 0;

	for (int i = 1; i <= gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>(UTIL_PlayerByIndex( i ));

		if ( player == NULL )
			continue;

		if ( !player->IsBot() )
			continue;

		count++;
	}

	return count;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Kick a bot from the given team. If no bot exists on the team, return false.
 */
bool UTIL_KickBotFromTeam( int kickTeam, bool bQueue )
{
	int i;

	// try to kick a dead bot first
	for ( i = 1; i <= gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );

		if (player == NULL)
			continue;

		if (!player->IsBot())
			continue;

		if ( player->GetPendingTeamNumber() == TEAM_SPECTATOR )
		{
			// bot has already been flagged for kicking
			continue;
		}

		if ( !player->IsAlive() )
		{
			// Address issue with bots getting kicked during half-time (this was resulting in bots from the wrong team being kicked)
			if ( player->CanKickFromTeam( kickTeam ) )
			{
				if ( bQueue )
				{
					// bots flagged as spectators will be kicked at the beginning of the next round restart
					player->SetPendingTeamNum( TEAM_SPECTATOR );
				}
				else
				{
					// its a bot on the right team - kick it
					engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", engine->GetPlayerUserId( player->edict() ) ) );
				}
			
				return true;
			}
		}
	}

	// no dead bots, kick any bot on the given team
	for ( i = 1; i <= gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );

		if (player == NULL)
			continue;

		if (!player->IsBot())
			continue;

		if ( player->GetPendingTeamNumber() == TEAM_SPECTATOR )
		{
			// bot has already been flagged for kicking
			continue;
		}

		// Address issue with bots getting kicked during half-time (this was resulting in bots from the wrong team being kicked)
		if ( player->CanKickFromTeam( kickTeam ) )
		{
			if ( bQueue )
			{
				// bots flagged as spectators will be kicked at the beginning of the next round restart
				player->SetPendingTeamNum( TEAM_SPECTATOR );
			}
			else
			{
				// its a bot on the right team - kick it
				engine->ServerCommand( UTIL_VarArgs( "kickid %d\n", engine->GetPlayerUserId( player->edict() ) ) );
			}

			return true;

		}
	}

	return false;
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if all of the members of the given team are bots
 */
bool UTIL_IsTeamAllBots( int team )
{
	int botCount = 0;

	for( int i=1; i <= gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );

		if (player == NULL)
			continue;

		// skip players on other teams
		if (player->GetTeamNumber() != team)
			continue;

		// if not a bot, fail the test
		if (!player->IsBot())
			return false;

		// is a bot on given team
		++botCount;
	}

	// if team is empty, there are no bots
	return (botCount) ? true : false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return the closest active player to the given position.
 * If 'distance' is non-NULL, the distance to the closest player is returned in it.
 */
extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, float *distance )
{
	CBasePlayer *closePlayer = NULL;
	float closeDistSq = 999999999999.9f;

	for ( int i = 1; i <= gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );

		if (!IsEntityValid( player ))
			continue;

		if (!player->IsAlive())
			continue;

		Vector playerOrigin = GetCentroid( player );
		float distSq = (playerOrigin - pos).LengthSqr();
		if (distSq < closeDistSq)
		{
			closeDistSq = distSq;
			closePlayer = static_cast<CBasePlayer *>( player );
		}
	}
	
	if (distance)
		*distance = (float)sqrt( closeDistSq );

	return closePlayer;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return the closest active player on the given team to the given position.
 * If 'distance' is non-NULL, the distance to the closest player is returned in it.
 */
extern CBasePlayer *UTIL_GetClosestPlayer( const Vector &pos, int team, float *distance )
{
	CBasePlayer *closePlayer = NULL;
	float closeDistSq = 999999999999.9f;

	for ( int i = 1; i <= gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );

		if (!IsEntityValid( player ))
			continue;

		if (!player->IsAlive())
			continue;

		if (player->GetTeamNumber() != team)
			continue;

		Vector playerOrigin = GetCentroid( player );
		float distSq = (playerOrigin - pos).LengthSqr();
		if (distSq < closeDistSq)
		{
			closeDistSq = distSq;
			closePlayer = static_cast<CBasePlayer *>( player );
		}
	}
	
	if (distance)
		*distance = (float)sqrt( closeDistSq );

	return closePlayer;
}

//--------------------------------------------------------------------------------------------------------------
// Takes the bot pointer and constructs the net name using the current bot name prefix.
void UTIL_ConstructBotNetName( char *name, int nameLength, const BotProfile *profile )
{
	if (profile == NULL)
	{
		name[0] = 0;
		return;
	}

	// if there is no bot prefix just use the profile name.
	if ((cv_bot_prefix.GetString() == NULL) || (strlen(cv_bot_prefix.GetString()) == 0))
	{
		Q_strncpy( name, profile->GetName(), nameLength );
		return;
	}

	// find the highest difficulty
	const char *diffStr = BotDifficultyName[0];
	for ( int i=BOT_EXPERT; i>0; --i )
	{
		if ( profile->IsDifficulty( (BotDifficultyType)i ) )
		{
			diffStr = BotDifficultyName[i];
			break;
		}
	}

	const char *weaponStr = NULL;
	if ( profile->GetWeaponPreferenceCount() )
	{
		weaponStr = profile->GetWeaponPreferenceAsString( 0 );

		const char *translatedAlias = GetTranslatedWeaponAlias( weaponStr );

		char wpnName[128];
		Q_snprintf( wpnName, sizeof( wpnName ), "weapon_%s", translatedAlias );
		WEAPON_FILE_INFO_HANDLE	hWpnInfo = LookupWeaponInfoSlot( wpnName );
		if ( hWpnInfo != GetInvalidWeaponInfoHandle() )
		{
			CCSWeaponInfo *pWeaponInfo = dynamic_cast< CCSWeaponInfo* >( GetFileWeaponInfoFromHandle( hWpnInfo ) );
			if ( pWeaponInfo )
			{
				CSWeaponType weaponType = pWeaponInfo->GetWeaponType();
				weaponStr = WeaponClassAsString( weaponType );
			}
		}
	}
	if ( !weaponStr )
	{
		weaponStr = "";
	}

	char skillStr[16];
	Q_snprintf( skillStr, sizeof( skillStr ), "%.0f", profile->GetSkill()*100 );

	char temp[MAX_PLAYER_NAME_LENGTH*2];
	char prefix[MAX_PLAYER_NAME_LENGTH*2];
	Q_strncpy( temp, cv_bot_prefix.GetString(), sizeof( temp ) );
	Q_StrSubst( temp, "<difficulty>", diffStr, prefix, sizeof( prefix ) );
	Q_StrSubst( prefix, "<weaponclass>", weaponStr, temp, sizeof( temp ) );
	Q_StrSubst( temp, "<skill>", skillStr, prefix, sizeof( prefix ) );
	Q_snprintf( name, nameLength, "%s %s", prefix, profile->GetName() );
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if anyone on the given team can see the given spot
 */
bool UTIL_IsVisibleToTeam( const Vector &spot, int team )
{
	for( int i = 1; i <= gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );

		if (player == NULL)
			continue;

		if (!player->IsAlive())
			continue;

		if (player->GetTeamNumber() != team)
			continue;

		trace_t result;
		UTIL_TraceLine( player->EyePosition(), spot, CONTENTS_SOLID, player, COLLISION_GROUP_NONE, &result );

		if ( result.fraction == 1.0f )
			return true;
	}

	return false;
}

//--------------------------------------------------------------------------------------------------------------
/**
 * Return true if anyone on the given team can see the given spot
 */
bool UTIL_IsRandomSpawnFarEnoughAwayFromTeam( const Vector &spot, int team )
{
	if ( mp_randomspawn_dist.GetInt() <= 0 )
		return true;

	if ( mp_randomspawn_los.GetInt() == 0 )
		return true;

	for( int i = 1; i <= gpGlobals->maxClients; ++i )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( i ) );

		if (player == NULL)
			continue;

		if (!player->IsAlive())
			continue;

		if (player->GetTeamNumber() != team)
			continue;

		if ( mp_randomspawn_dist.GetInt() > 0 && (player->GetAbsOrigin()).DistTo( spot ) < mp_randomspawn_dist.GetInt() )
		{
			//NDebugOverlay::Line( player->EyePosition(), spot + Vector( 0, 0, 32 ), 255, 0, 0, true, 4 );
			//NDebugOverlay::Line( spot, spot + Vector( 0, 0, 64 ), 255, 128, 0, true, 4 );
			continue;
		}
		else
		{
			return true;
		}
	}

	return false;
}


//------------------------------------------------------------------------------------------------------------
void UTIL_DrawBeamFromEnt( int i, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue )
{
/* BOTPORT: What is the replacement for MESSAGE_BEGIN?
	MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecEnd );   // vecEnd = origin???
					WRITE_BYTE( TE_BEAMENTPOINT );
					WRITE_SHORT( i );
					WRITE_COORD( vecEnd.x );
					WRITE_COORD( vecEnd.y );
					WRITE_COORD( vecEnd.z );
					WRITE_SHORT( s_iBeamSprite );
					WRITE_BYTE( 0 );		 // startframe
					WRITE_BYTE( 0 );		 // framerate
					WRITE_BYTE( iLifetime ); // life
					WRITE_BYTE( 10 );		 // width
					WRITE_BYTE( 0 );		 // noise
					WRITE_BYTE( bRed );		 // r, g, b
					WRITE_BYTE( bGreen );		 // r, g, b
					WRITE_BYTE( bBlue );    // r, g, b
					WRITE_BYTE( 255 );	 // brightness
					WRITE_BYTE( 0 );		 // speed
					MESSAGE_END();
					*/
}


//------------------------------------------------------------------------------------------------------------
void UTIL_DrawBeamPoints( Vector vecStart, Vector vecEnd, int iLifetime, byte bRed, byte bGreen, byte bBlue )
{
	NDebugOverlay::Line( vecStart, vecEnd, bRed, bGreen, bBlue, true, 0.1f );

	/*
	MESSAGE_BEGIN( MSG_PVS, SVC_TEMPENTITY, vecStart );
					WRITE_BYTE( TE_BEAMPOINTS );
					WRITE_COORD( vecStart.x );
					WRITE_COORD( vecStart.y );
					WRITE_COORD( vecStart.z );
					WRITE_COORD( vecEnd.x );
					WRITE_COORD( vecEnd.y );
					WRITE_COORD( vecEnd.z );
					WRITE_SHORT( s_iBeamSprite );
					WRITE_BYTE( 0 );		 // startframe
					WRITE_BYTE( 0 );		 // framerate
					WRITE_BYTE( iLifetime ); // life
					WRITE_BYTE( 10 );		 // width
					WRITE_BYTE( 0 );		 // noise
					WRITE_BYTE( bRed );		 // r, g, b
					WRITE_BYTE( bGreen );		 // r, g, b
					WRITE_BYTE( bBlue );    // r, g, b
					WRITE_BYTE( 255 );	 // brightness
					WRITE_BYTE( 0 );		 // speed
					MESSAGE_END();
					*/
}


//------------------------------------------------------------------------------------------------------------
void CONSOLE_ECHO( char * pszMsg, ... )
{
	va_list     argptr;
	static char szStr[1024];

	va_start( argptr, pszMsg );
	vsprintf( szStr, pszMsg, argptr );
	va_end( argptr );

	Msg( "%s", szStr );
}


//------------------------------------------------------------------------------------------------------------
void BotPrecache( void )
{
	s_iBeamSprite = CBaseEntity::PrecacheModel( "sprites/smoke.vmt" );
}

//------------------------------------------------------------------------------------------------------------
#define COS_TABLE_SIZE 256
static float cosTable[ COS_TABLE_SIZE ];

void InitBotTrig( void )
{
	for( int i=0; i<COS_TABLE_SIZE; ++i )
	{
		float angle = (float)(2.0f * M_PI * i / (float)(COS_TABLE_SIZE-1));
		cosTable[i] = (float)cos( angle ); 
	}
}

float BotCOS( float angle )
{
	angle = AngleNormalizePositive( angle );
	int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f );
	return cosTable[i];
}

float BotSIN( float angle )
{
	angle = AngleNormalizePositive( angle - 90 );
	int i = (int)( angle * (COS_TABLE_SIZE-1) / 360.0f );
	return cosTable[i];
}


//--------------------------------------------------------------------------------------------------------------
/**
 * Send a "hint" message to all players, dead or alive.
 */
void HintMessageToAllPlayers( const char *message )
{
	hudtextparms_t textParms;

	textParms.x = -1.0f;
	textParms.y = -1.0f;
	textParms.fadeinTime = 1.0f;
	textParms.fadeoutTime = 5.0f;
	textParms.holdTime = 5.0f;
	textParms.fxTime = 0.0f;
	textParms.r1 = 100;
	textParms.g1 = 255;
	textParms.b1 = 100;
	textParms.r2 = 255;
	textParms.g2 = 255;
	textParms.b2 = 255;
	textParms.effect = 0;
	textParms.channel = 0;

	UTIL_HudMessageAll( textParms, message );
}

//--------------------------------------------------------------------------------------------------------------------
/**
 * Return true if moving from "start" to "finish" will cross a player's line of fire.
 * The path from "start" to "finish" is assumed to be a straight line.
 * "start" and "finish" are assumed to be points on the ground.
 */
bool IsCrossingLineOfFire( const Vector &start, const Vector &finish, CBaseEntity *ignore, int ignoreTeam  )
{
	for ( int p=1; p <= gpGlobals->maxClients; ++p )
	{
		CBasePlayer *player = static_cast<CBasePlayer *>( UTIL_PlayerByIndex( p ) );

		if (!IsEntityValid( player ))
			continue;

		if (player == ignore)
			continue;

		if (!player->IsAlive())
			continue;

		if (ignoreTeam && player->GetTeamNumber() == ignoreTeam)
			continue;

		// compute player's unit aiming vector 
		Vector viewForward;
		AngleVectors( player->GetFinalAimAngle(), &viewForward );

		const float longRange = 5000.0f;
		Vector playerOrigin = GetCentroid( player );
		Vector playerTarget = playerOrigin + longRange * viewForward;

		Vector result( 0, 0, 0 );
		if (IsIntersecting2D( start, finish, playerOrigin, playerTarget, &result ))
		{
			// simple check to see if intersection lies in the Z range of the path
			float loZ, hiZ;

			if (start.z < finish.z)
			{
				loZ = start.z;
				hiZ = finish.z;
			}
			else
			{
				loZ = finish.z;
				hiZ = start.z;
			}

			if (result.z >= loZ && result.z <= hiZ + HumanHeight)
				return true;
		}
	}

	return false;
}


//--------------------------------------------------------------------------------------------------------------
/**
* Performs a simple case-insensitive string comparison, honoring trailing * wildcards
*/
bool WildcardMatch( const char *query, const char *test )
{
	if ( !query || !test )
		return false;

	while ( *test && *query )
	{
		char nameChar = *test;
		char queryChar = *query;
		if ( tolower(nameChar) != tolower(queryChar) ) // case-insensitive
			break;
		++test;
		++query;
	}

	if ( *query == 0 && *test == 0 )
		return true;

	// Support trailing *
	if ( *query == '*' )
		return true;

	return false;
}