/*
   Copyright (C) 1997-2001 Id Software, Inc.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

   See the GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

 */
#include "g_local.h"
#include "g_gametypes.h"

char scoreboardString[MAX_STRING_CHARS];
const unsigned int scoreboardInterval = 1000;
static const char *G_PlayerStatsMessage( edict_t *ent );

//======================================================================
//
//PLAYER SCOREBOARDS
//
//======================================================================

//==================
//G_ClientUpdateScoreBoardMessage
//
//Show the scoreboard messages if the scoreboards are active
//==================
void G_UpdateScoreBoardMessages( void )
{
	static int nexttime = 0;
	int i;
	edict_t	*ent;
	gclient_t *client;
	char *scoreBoardMessage;
	qboolean forcedUpdate = qfalse;

	scoreBoardMessage = G_Gametype_ScoreboardMessage();

update:
	// send to players who have scoreboard visible
	for( i = 0; i < game.maxclients; i++ )
	{
		ent = game.edicts + 1 + i;
		if( !ent->r.inuse || !ent->r.client )
			continue;

		client = ent->r.client;
		if( game.realtime <= client->scoreboard_time + scoreboardInterval )
			continue;

		if( forcedUpdate || ( client->ps.stats[STAT_LAYOUTS] & STAT_LAYOUT_SCOREBOARD ) )
		{
			client->scoreboard_time = game.realtime + scoreboardInterval - ( game.realtime%scoreboardInterval );

			trap_GameCmd( ent, scoreBoardMessage );
			trap_GameCmd( ent, G_PlayerStatsMessage( ent ) );
		}
	}

	if( !forcedUpdate )
	{
		// every 10 seconds, send everyone the scoreboard
		nexttime -= game.snapFrameTime;
		if( nexttime > 0 )
			return;

		do
		{
			nexttime += 10000;
		}
		while( nexttime <= 0 );

		forcedUpdate = qtrue;
		goto update;
	}
}

//==================
//G_ScoreboardMessage_AddSpectators
//generic one to add the same spectator entries to all scoreboards
//==================
void G_ScoreboardMessage_AddSpectators( void )
{
	char entry[MAX_TOKEN_CHARS];
	int i, clstate;
	edict_t	*e;
	size_t len;

	len = strlen( scoreboardString );
	if( !len )
		return;

	e = G_Teams_BestInChallengersQueue( 0, NULL );
	while( e )
	{
		//spectator tab entry
		if( e->r.client->pers.connecting == qtrue || trap_GetClientState( PLAYERNUM( e ) ) < CS_SPAWNED )
		{

		}
		else
		{
			*entry = 0;
			Q_snprintfz( entry, sizeof( entry ), "&w %i %i ",
			             PLAYERNUM( e ),
			             e->r.client->r.ping > 999 ? 999 : e->r.client->r.ping );

			if( SCOREBOARD_MSG_MAXSIZE - len > strlen( entry ) )
			{
				Q_strncatz( scoreboardString, entry, sizeof( scoreboardString ) );
				len = strlen( scoreboardString );
			}
		}

		e = G_Teams_BestInChallengersQueue( e->r.client->pers.queueTimeStamp, e );
	}

	//add spectator team
	for( i = 0; teamlist[TEAM_SPECTATOR].playerIndices[i] != -1; i++ )
	{
		e = game.edicts + teamlist[TEAM_SPECTATOR].playerIndices[i];
		if( e->r.client->pers.connecting == qtrue || trap_GetClientState( PLAYERNUM( e ) ) < CS_SPAWNED )
			continue;

		//spectator tab entry
		*entry = 0;
		if( !e->r.client->pers.queueTimeStamp )
		{	// not in challenger queue
			Q_snprintfz( entry, sizeof( entry ), "&s %i %i ",
			             PLAYERNUM( e ),
			             e->r.client->r.ping > 999 ? 999 : e->r.client->r.ping );
		}

		if( *entry )
		{
			if( SCOREBOARD_MSG_MAXSIZE - len > strlen( entry ) )
			{
				Q_strncatz( scoreboardString, entry, sizeof( scoreboardString ) );
				len = strlen( scoreboardString );
			}
		}
	}

	//add connecting spectators
	for( i = 0; teamlist[TEAM_SPECTATOR].playerIndices[i] != -1; i++ )
	{
		e = game.edicts + teamlist[TEAM_SPECTATOR].playerIndices[i];

		//spectator tab entry
		*entry = 0;
		clstate = trap_GetClientState( PLAYERNUM( e ) );
		if( e->r.client->pers.connecting == qtrue || ( clstate >= CS_CONNECTED && clstate < CS_SPAWNED ) )
			Q_snprintfz( entry, sizeof( entry ), "&c %i", PLAYERNUM( e ) );
		if( *entry )
		{
			if( SCOREBOARD_MSG_MAXSIZE - len > strlen( entry ) )
			{
				Q_strncatz( scoreboardString, entry, sizeof( scoreboardString ) );
				len = strlen( scoreboardString );
			}
		}
	}
}

//==================
//G_PlayerStatsMessage
//generic one to add the stats of the current player into the scoreboard message at cgame
//==================
static const char *G_PlayerStatsMessage( edict_t *ent )
{
	gitem_t *it;
	int i;
	int weakhit, weakshot;
	int hit, shot;
	edict_t *target;
	gclient_t *client;
	static char entry[MAX_TOKEN_CHARS];

	// when chasing generate from target
	target = ent;
	client = ent->r.client;
	if( client->chase.active && game.edicts[client->chase.target].r.client )
	{
		target = &game.edicts[client->chase.target];
		client = target->r.client;
	}

	// message header
	entry[0] = '\0';
	Q_snprintfz( entry, sizeof( entry ), "plstats 0 \"" );
	Q_strncatz( entry, va( " %d", PLAYERNUM( target ) ), sizeof( entry ) );

	// weapon loop
	for( i = WEAP_GUNBLADE; i < WEAP_TOTAL; i++ )
	{
		if( i == WEAP_SHOCKWAVE )
			continue;

		it = GS_FindItemByTag( i );
		assert( it );

		weakhit = hit = 0;
		weakshot = shot = 0;

		if( it->weakammo_tag != AMMO_NONE )
		{
			weakhit = client->resp.accuracy_hits[it->weakammo_tag-AMMO_CELLS];
			weakshot = client->resp.accuracy_shots[it->weakammo_tag-AMMO_CELLS];
		}

		if( it->ammo_tag != AMMO_NONE )
		{
			hit = client->resp.accuracy_hits[it->ammo_tag-AMMO_CELLS];
			shot = client->resp.accuracy_shots[it->ammo_tag-AMMO_CELLS];
		}

		// both in one
		Q_strncatz( entry, va( " %d", weakshot+shot ), sizeof( entry ) );
		if( weakshot+shot > 0 )
		{
			Q_strncatz( entry, va( " %d", weakhit+hit ), sizeof( entry ) );

			if( i == WEAP_LASERGUN || i == WEAP_ELECTROBOLT )
			{
				// strong
				Q_strncatz( entry, va( " %d", shot ), sizeof( entry ) );
				if( shot != (weakshot+shot) )
					Q_strncatz( entry, va( " %d", hit ), sizeof( entry ) );
			}
		}
	}

	// add enclosing quote
	Q_strncatz( entry, "\"", sizeof( entry ) );

	return entry;
}

//=======================================================================

static unsigned int G_FindPointedPlayer( edict_t *self )
{
	trace_t	trace;
	int i, j, bestNum = 0;
	vec3_t boxpoints[8];
	float value, dist, value_best = 0.90f;   // if nothing better is found, print nothing
	edict_t	*other;
	vec3_t vieworg, dir, viewforward;

	if( G_IsDead( self ) )
		return 0;

	// we can't handle the thirdperson modifications in server side :/
	VectorSet( vieworg, self->r.client->ps.pmove.origin[0], self->r.client->ps.pmove.origin[1], self->r.client->ps.pmove.origin[2] + self->r.client->ps.viewheight );
	AngleVectors( self->r.client->ps.viewangles, viewforward, NULL, NULL );

	for( i = 0; i < game.maxclients; i++ )
	{
		other = PLAYERENT( i );
		if( !other->r.inuse )
			continue;
		if( !other->r.client )
			continue;
		if( other == self )
			continue;
		if( !other->r.solid || ( other->r.svflags & SVF_NOCLIENT ) )
			continue;

		VectorSubtract( other->s.origin, self->s.origin, dir );
		dist = VectorNormalize2( dir, dir );
		if( dist > 1000 )
			continue;

		value = DotProduct( dir, viewforward );

		if( value > value_best )
		{
			BuildBoxPoints( boxpoints, other->s.origin, tv( 4, 4, 4 ), tv( 4, 4, 4 ) );
			for( j = 0; j < 8; j++ )
			{
				G_Trace( &trace, vieworg, vec3_origin, vec3_origin, boxpoints[j], self, MASK_SHOT|MASK_OPAQUE );
				if( trace.ent && trace.ent == ENTNUM( other ) )
				{
					value_best = value;
					bestNum = ENTNUM( other );
				}
			}
		}
	}

	return bestNum;
}

//===============
//G_SetClientStats
//===============
void G_SetClientStats( edict_t *ent )
{
	gclient_t *client = ent->r.client;
	int team, i;

	if( ent->r.client->chase.active )  //in chasecam it copies the other player stats
		return;

	//
	// layouts
	//
	client->ps.stats[STAT_LAYOUTS] = 0;

	// don't force scoreboard when dead during timeout
	if( ( ent->deadflag && !gtimeout.active ) || ent->r.client->showscores || match.state >= MATCH_STATE_POSTMATCH )
		client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_SCOREBOARD;
	if( GS_Gametype_IsTeamBased( game.gametype ) && game.gametype != GAMETYPE_DUEL )
		client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_TEAMTAB;
	if( G_Gametype_hasChallengersQueue( game.gametype ) && ent->r.client->pers.queueTimeStamp )
		client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_CHALLENGER;
	if( match.state <= MATCH_STATE_WARMUP && match.ready[PLAYERNUM( ent )] )
		client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_READY; // wsw : imp : add dead signal
	if( G_ClientIsZoom( ent ) )
		client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_ZOOM;

	client->ps.stats[STAT_CHASING] = STAT_NOTSET; //it is set up in the chasecam code

	// gametype
	client->ps.stats[STAT_GAMETYPE] = game.gametype;

	//
	// team
	//
	client->ps.stats[STAT_TEAM] = client->ps.stats[STAT_REALTEAM] = ent->s.team;

	//
	// health
	//
	if( ent->s.team == TEAM_SPECTATOR )
		client->ps.stats[STAT_HEALTH] = STAT_NOTSET; // no health for spectator
	else
		client->ps.stats[STAT_HEALTH] = HEALTH_TO_INT( ent->health );
	client->r.frags = client->ps.stats[STAT_FRAGS];


	//
	// ammo
	//
	if( !client->ammo_index )
	{
		client->ps.stats[STAT_AMMO_ITEM] = 0;
		client->ps.stats[STAT_AMMO] = 0;
	}
	else
	{
		client->ps.stats[STAT_AMMO_ITEM] = client->ammo_index;
		client->ps.stats[STAT_AMMO] = client->inventory[client->ammo_index];
	}

	if( !client->ammo_weak_index )
	{
		client->ps.stats[STAT_WEAK_AMMO] = 0;
	}
	else
	{
		client->ps.stats[STAT_WEAK_AMMO] = client->inventory[client->ammo_weak_index];
	}

	//
	// armor
	//

	if( client->armortag )
	{
		//client->ps.stats[STAT_ARMOR_ICON] = trap_ModelIndex (game.items[armor_tag]->world_model[0]);
		client->ps.stats[STAT_ARMOR_ITEM] = client->armortag;
		client->ps.stats[STAT_ARMOR] = ARMOR_TO_INT( client->armor );
	}
	else
	{
		client->ps.stats[STAT_ARMOR_ITEM] = 0;
		client->ps.stats[STAT_ARMOR] = 0;
	}

	//
	// pickup message
	//
	if( level.time > client->pickup_msg_time )
	{
		client->ps.stats[STAT_PICKUP_ITEM] = 0;
	}

	//
	// powerups
	//
	client->ps.stats[STAT_POWERUP_TIMES] = 0;

	if( client->quad_timeout > level.time )
	{
		client->ps.stats[STAT_POWERUP_TIMES] = (int)ceil( ( client->quad_timeout - level.time ) / 1000.0f );
	}

	client->ps.stats[STAT_POWERUP_TIMES] <<= 8;
	if( client->shell_timeout > level.time )
	{
		client->ps.stats[STAT_POWERUP_TIMES] |= (int)ceil( ( client->shell_timeout - level.time ) / 1000.0f );
	}

	//
	// selected item
	//
	if( client->selected_item < 0 || !game.items[client->selected_item] )
	{
		client->ps.stats[STAT_SELECTED_ITEM] = 0;
	}
	else
	{
		client->ps.stats[STAT_SELECTED_ITEM] = client->selected_item;
	}

	//
	// frags
	//
	if( ent->s.team == TEAM_SPECTATOR )
	{
		client->ps.stats[STAT_FRAGS] = STAT_NOTSET; // no frags for spectators
	}
	else
	{
		client->ps.stats[STAT_FRAGS] = match.scores[PLAYERNUM( ent )].score;
	}

	//
	// Team scores
	//
	if( GS_Gametype_IsTeamBased( game.gametype ) )
	{
		// team based
		i = 0;
		for( team = TEAM_ALPHA; team < TEAM_ALPHA + g_maxteams->integer; team++ )
		{
			client->ps.stats[STAT_TEAM_ALPHA_SCORE+i] = teamlist[team].teamscore;
			i++;
		}
		// mark the rest as not set
		for(; team < GS_MAX_TEAMS; team++ )
		{
			client->ps.stats[STAT_TEAM_ALPHA_SCORE+i] = STAT_NOTSET;
			i++;
		}
	}
	else
	{
		// not team based
		i = 0;
		for( team = TEAM_ALPHA; team < GS_MAX_TEAMS; team++ )
		{
			client->ps.stats[STAT_TEAM_ALPHA_SCORE+i] = STAT_NOTSET;
			i++;
		}
	}

	//
	// weapon
	//
	if( ent->s.weapon > 0 )
	{
		client->ps.stats[STAT_WEAPON_ITEM] = ent->s.weapon;
	}
	else
	{
		client->ps.stats[STAT_WEAPON_ITEM] = 0;
	}

	// always set them to noset before
	client->ps.stats[STAT_GAMETYPE_SPECIFIC1] = STAT_NOTSET;
	client->ps.stats[STAT_GAMETYPE_SPECIFIC2] = STAT_NOTSET;
	client->ps.stats[STAT_GAMETYPE_SPECIFIC3] = STAT_NOTSET;

	if( game.gametype == GAMETYPE_RACE )
	{
		if( !ent->r.client->resp.race_active )
		{
			// race is not started
			client->ps.stats[STAT_RACE_TIME] = 0;
		}
		else
		{
			// the race is on
			client->ps.stats[STAT_RACE_TIME] = ent->r.client->resp.race_time/100;
		}

		client->ps.stats[STAT_RACE_PLAYERBESTTIME] = ent->r.client->teamchange.race_record / 100;
		client->ps.stats[STAT_RACE_MATCHBESTTIME] = game.race_record / 100;
	}
	else if( game.gametype == GAMETYPE_CTF )
	{
		// KoFFiE: Send correct timer stats to the client
		// FIXME? Only Alpha & Beta are supported at the moment
		client->ps.stats[STAT_CTF_FLAG_ALPHA] = G_Gametype_CTF_FlagStatus( TEAM_ALPHA, PLAYERNUM( ent ) );
		client->ps.stats[STAT_CTF_FLAG_BETA] = G_Gametype_CTF_FlagStatus( TEAM_BETA, PLAYERNUM( ent ) );
		// client->ps.stats[STAT_CTF_FLAG_GAMMA ] = G_Gametype_CTF_FlagStatus( TEAM_GAMMA  );
		// client->ps.stats[STAT_CTF_FLAG_DELTA ] = G_Gametype_CTF_FlagStatus( TEAM_DELTA  );
	}
	else if( game.gametype == GAMETYPE_TDM )
	{
		client->ps.stats[STAT_TDM_CAPTURE_AREA_STATUS] = G_Gametype_TDM_AreaStatus();
		client->ps.stats[STAT_TDM_CAPTURE_AREA_CAPTURING1] = G_Gametype_TDM_AreaCapturing( 1 );
		client->ps.stats[STAT_TDM_CAPTURE_AREA_CAPTURING2] = G_Gametype_TDM_AreaCapturing( 2 );
	}

	client->ps.stats[STAT_POINTED_TEAMPLAYER] = 0;
	client->ps.stats[STAT_POINTED_PLAYER] = G_FindPointedPlayer( ent );
	if( client->ps.stats[STAT_POINTED_PLAYER] && GS_Gametype_IsTeamBased( game.gametype ) )
	{
		edict_t	*e = &game.edicts[client->ps.stats[STAT_POINTED_PLAYER]];
		if( e->s.team == ent->s.team )
		{
			int pointedhealth = HEALTH_TO_INT( e->health );
			int pointedarmor = 0;
			int armor_type = 0;
			qboolean mega = qfalse;

			if( pointedhealth < 0 ) pointedhealth = 0;
			if( pointedhealth > 100 )
			{
				pointedhealth -= 100;
				mega = qtrue;
				if( pointedhealth > 100 )
					pointedhealth = 100;
			}
			pointedhealth /= 3.2;

			if( e->r.client->armortag )
			{
				pointedarmor = ARMOR_TO_INT( e->r.client->armor );
				armor_type = 1 + e->r.client->armortag - ARMOR_GA;
			}
			if( pointedarmor > 150 )
			{
				pointedarmor = 150;
			}
			pointedarmor /= 5;

			client->ps.stats[STAT_POINTED_TEAMPLAYER] = ( ( pointedhealth &0x1F )|( pointedarmor&0x3F )<<6|( armor_type &0xF )<<12 );
			if( mega )
			{
				client->ps.stats[STAT_POINTED_TEAMPLAYER] |= 0x20;
			}

		}
	}

	// dmh: ENGAGE NEWBMODE!
#if 0
	if( trap_Cvar_Value( "sv_skilllevel" ) == 0 ) // dmh: todo
	{
		client->ps.stats[STAT_REDARMOR_TIMER] = G_ItemRespawnTimer( "item_armor_ra" );
		client->ps.stats[STAT_MEGAHEALTH_TIMER] = G_ItemRespawnTimer( "item_health_mega" );		
	}
	else
	{
		client->ps.stats[STAT_REDARMOR_TIMER] = 0;
		client->ps.stats[STAT_MEGAHEALTH_TIMER] = 0;		
	}
#endif
	//ZOID
	//	SetCTFStats(ent);
	//ZOID
}
