/*
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"

/*
======================================================================

PLAYER SCOREBOARDS

======================================================================
*/
//newgametypes[start]

static unsigned int scoreboardevent_time = 0;

//==================
//ClientScoreboardMessage
//
//Tells that something has changed _important_ scoreboard information (player join, frag...)
//==================
void G_ScoreboardEvent( void )
{
	scoreboardevent_time = game.realtime;
}

//==================
//ClientScoreboardMessage
//==================
char *ClientScoreboardMessage( edict_t *ent, edict_t *killer )
{
	if( ent->r.svflags & SVF_FAKECLIENT )
		return NULL;

	return G_Gametype_ScoreboardMessage( ent, ent->enemy );
}

//==================
//ClientEndServerFrames_UpdateScoreBoardMessages
//
//Show the scoreboard messages if the scoreboards are active
//==================
void ClientEndServerFrames_UpdateScoreBoardMessages( void )
{
	edict_t		*ent;
	gclient_t	*client;

	for( ent = game.edicts + 1 ; PLAYERNUM(ent) < game.maxclients ; ent++ )
	{
		if( trap_GetClientState(PLAYERNUM(ent)) != CS_SPAWNED )
			continue;

		if( !ent->r.inuse || !ent->r.client || ent->r.svflags & SVF_FAKECLIENT )
			continue;

		client = ent->r.client;

		// 1 second delay if scoreboard is visible, 2.5 if an event has happened since last update, 10 seconds otherwise
		if( !client->scoreboard_time || game.realtime > client->scoreboard_time + 10000 ||
			((client->scoreboard_time < scoreboardevent_time) && (game.realtime > client->scoreboard_time + 2500)) ||
			((client->ps.stats[STAT_LAYOUTS] & STAT_LAYOUT_SCOREBOARD) && (game.realtime > client->scoreboard_time + 1000)) )
		{
			client->scoreboard_time = game.realtime;
			trap_ServerCmd( ent, ClientScoreboardMessage(ent, ent->enemy) );
		}
	}
}


//==================
//G_ScoreboardMessage_AddPlayerStats
//generic one to add the stats of the current player into the scoreboard message
//==================
void G_ScoreboardMessage_AddPlayerStats( edict_t *ent )
{
	char entry[MAX_TOKEN_CHARS];
	size_t len;
	gitem_t *it;
	int i;
	int weakhit, weakshot;
	int hit, shot, percent;
	gclient_t *client;

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

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

	// message header
	*entry = '\0';
	Q_snprintfz( entry, sizeof(entry), "&z");

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

		it = GS_FindItemByTag( i ); 

		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];
		} else {
			weakhit = 0;
			weakshot = 0;
		}
		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];
		} else {
			hit = 0;
			shot = 0;
		}

		if( i == WEAP_LASERGUN || i == WEAP_ELECTROBOLT )
		{
			// weak
			if( weakshot > 0 )
				percent = min((int)(floor((100.0f * weakhit) / ((float)weakshot)) + 0.5), 99);
			else
				percent = -1;
			Q_strncatz( entry, va(" %2d", percent), sizeof(entry));

			// strong
			if( shot > 0 )
				percent = min((int)(floor((100.0f * hit) / ((float)shot)) + 0.5), 99);
			else
				percent = -1;

			Q_strncatz( entry, va(" %2d", percent), sizeof(entry) );
		}
		else
		{
			// both in one
			if( weakshot+shot > 0 )
				percent = min((int)(floor((100.0f * (weakhit+hit)) / ((float)(weakshot+shot))) + 0.5), 99);
			else
				percent = -1;

			Q_strncatz( entry, va(" %2d", percent), sizeof(entry) );
		}
	}

	// add it to the message
	if( SCOREBOARD_MSG_MAXSIZE - len > strlen(entry) ) 
	{
		Q_strncatz( scoreboardString, entry, sizeof(scoreboardString) );
		len = strlen( scoreboardString );
	}
}

//==================
//G_ScoreboardMessage_AddSpectators
//generic one to add the same spectator entries to all scoreboards
//==================
edict_t *G_Teams_BestInChallengersQueue( unsigned int lastTimeStamp, edict_t *ignore );
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 );
			}
		}
	}
}

/*
======================================================================

PLAYER WEAPONLISTS

======================================================================
*/

//==================
//ClientUpdateWeaponListStats
//==================
void ClientUpdateWeaponListStats( gclient_t *client ) 
{
	int	i;
	gclient_t *source;

	if( !client )
		return;

	// when chasing generate from target
	if( client->chase.active && game.edicts[client->chase.target].r.client )
		source = game.edicts[client->chase.target].r.client;
	else
		source = client;
	
	memset( &client->ps.weaponlist, 0, sizeof(client->ps.weaponlist) );
	
	// note that weaponlist only supports up to 32 weapons
	for( i = 0; (i < WEAP_TOTAL-1) && (i < MAX_WEAPLIST_STATS); i++ )
	{
		client->ps.weaponlist[i][0] = min( source->inventory[WEAP_GUNBLADE + i], 255 );		// weapon
		client->ps.weaponlist[i][1] = min( source->inventory[AMMO_CELLS + i], 255 );			// strong ammo
		client->ps.weaponlist[i][2] = min( source->inventory[AMMO_WEAK_GUNBLADE + i], 255 );	// weak ammo
	}
}

//==================
//ClientEndServerFrames_UpdateWeaponListMessages
//
//Send weaponlist updates if needed
//==================
void ClientEndServerFrames_UpdateWeaponListMessages( void )
{
	edict_t	*ent;

	for( ent = game.edicts + 1; PLAYERNUM(ent) < game.maxclients; ent++ )
	{
		if( !ent->r.inuse || !ent->r.client )
			continue;

		ClientUpdateWeaponListStats( ent->r.client );
	}
}

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

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;

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

	for( i = 0; i < game.maxclients; i++ )
	{
		other = game.edicts + i + 1;
		if( !other->r.inuse ) 
			continue;
		if( !other->r.client ) 
			continue;
		if( other == self )
			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++ ) {
				trap_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_SetStats
===============
*/
void G_SetStats( edict_t *ent )
{
	gclient_t	*client = ent->r.client;

	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( client->showinventory && !G_IsDead(ent) )
		client->ps.stats[STAT_LAYOUTS] |= STAT_LAYOUT_INVENTORY;
	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;

	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] = 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->inventory[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->inventory[client->ammo_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;
	}

	//
	// timers
	//
	if( client->quad_timeout > level.timemsec )
	{
		client->ps.stats[STAT_POWERUP_ITEM] = POWERUP_QUAD;
	}
	else
	{
		client->ps.stats[STAT_POWERUP_ITEM] = 0;
	}

	//
	// 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) )
	{
		int team,i;

		// team based
		i=0;
		for( team = TEAM_RED; team < TEAM_RED + g_maxteams->integer; team++ )
		{
			client->ps.stats[STAT_TEAM_RED_SCORE+i] = teamlist[team].teamscore;
			i++;
		}
		// mark the rest as not set
		for(;team<GS_MAX_TEAMS;team++)
		{
			client->ps.stats[STAT_TEAM_RED_SCORE+i] = STAT_NOTSET;
			i++;
		}
	} 
	else
	{
		int team,i;

		// not team based
		i=0;
		for(team=TEAM_RED;team<GS_MAX_TEAMS;team++)
		{
			client->ps.stats[STAT_TEAM_RED_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_RACE_TIME] = STAT_NOTSET;
	client->ps.stats[STAT_RACE_MATCHBESTTIME] = STAT_NOTSET;
	client->ps.stats[STAT_RACE_PLAYERBESTTIME] = STAT_NOTSET;

	client->ps.stats[STAT_CTF_RED_FLAG] = STAT_NOTSET;
	client->ps.stats[STAT_CTF_BLUE_FLAG] = STAT_NOTSET;

	if( game.gametype == GAMETYPE_RACE ) {
		if( !ent->r.client->resp.race_in_race )
		{
			// race is not started
			client->ps.stats[STAT_RACE_TIME]=0;
		}
		else
		{
			// the race is on
			client->ps.stats[STAT_RACE_TIME]=(level.timemsec-ent->r.client->resp.race_start_time)/100.0f;
		}
		
		client->ps.stats[STAT_RACE_PLAYERBESTTIME] = ent->r.client->teamchange.race_best_lap_time/100.0f;
		client->ps.stats[STAT_RACE_MATCHBESTTIME] = game.besttime/100.0f;
	} 
	else if( game.gametype == GAMETYPE_CTF ) {
		if( g_tctf->integer ) {
			int capturedFlagTimer = G_Gametype_CTF_CapturedFlagTimer( ent->s.team )/100;
			if( capturedFlagTimer ) {
				client->ps.stats[STAT_RACE_TIME] = capturedFlagTimer;
			}
		}
		client->ps.stats[STAT_CTF_RED_FLAG] = G_Gametype_CTF_FlagStatus( TEAM_RED );
		client->ps.stats[STAT_CTF_BLUE_FLAG] = G_Gametype_CTF_FlagStatus( TEAM_BLUE );
	}

	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;

		}
	}

//ZOID
//	SetCTFStats(ent);
//ZOID
}
