/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.
Copyright (C) 2000-2009 Darklegion Development

This file is part of Tremulous.

Tremulous 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.

Tremulous 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 Tremulous; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
*/
/*
===========================================================================
TREMULOUS EDGE MOD SRC FILE
===========================================================================
*/
#include "g_local.h"

// NULL for everyone
void QDECL PrintMsg( gentity_t *ent, const char *fmt, ... )
{
  char    msg[ 1024 ];
  va_list argptr;
  char    *p;

  va_start( argptr,fmt );

  if( Q_vsnprintf( msg, sizeof( msg ), fmt, argptr ) > sizeof( msg ) )
    G_Error ( "PrintMsg overrun" );

  va_end( argptr );

  // double quotes are bad
  while( ( p = strchr( msg, '"' ) ) != NULL )
    *p = '\'';

  trap_SendServerCommand( ( ( ent == NULL ) ? -1 : ent-g_entities ), va( "print \"%s\"", msg ) );
}

/*
================
G_TeamFromString

Return the team referenced by a string
================
*/
team_t G_TeamFromString( char *str )
{
  switch( tolower( *str ) )
  {
    case '0': case 's': return TEAM_NONE;
    case '1': case 'a': return TEAM_ALIENS;
    case '2': case 'h': return TEAM_HUMANS;
    default: return NUM_TEAMS;
  }
}

/*
================
G_TeamCommand

Broadcasts a command to only a specific team
================
*/
void G_TeamCommand( team_t team, char *cmd )
{
  int   i;

  for( i = 0 ; i < level.maxclients ; i++ )
  {
    if( level.clients[ i ].pers.connected == CON_CONNECTED )
    {
      if( level.clients[ i ].pers.teamSelection == team ||
        ( level.clients[ i ].pers.teamSelection == TEAM_NONE &&
          G_admin_permission( &g_entities[ i ], ADMF_SPEC_ALLCHAT ) ) )
        trap_SendServerCommand( i, cmd );
    }
  }
}

/*
==============
OnSameTeam
==============
*/
qboolean OnSameTeam( gentity_t *ent1, gentity_t *ent2 )
{
  if( !ent1->client || !ent2->client )
    return qfalse;

  if( ent1->client->pers.teamSelection == ent2->client->pers.teamSelection )
    return qtrue;

  return qfalse;
}

/*
==================
G_ClientListForTeam
==================
*/
static clientList_t G_ClientListForTeam( team_t team )
{
  int           i;
  clientList_t  clientList;

  Com_Memset( &clientList, 0, sizeof( clientList_t ) );

  for( i = 0; i < g_maxclients.integer; i++ )
  {
    gentity_t *ent = g_entities + i;
    if( ent->client->pers.connected != CON_CONNECTED )
      continue;

    if( ent->inuse && ( ent->client->ps.stats[ STAT_TEAM ] == team ) )
      Com_ClientListAdd( &clientList, ent->client->ps.clientNum );
  }

  return clientList;
}

/*
==================
G_UpdateTeamConfigStrings
==================
*/
void G_UpdateTeamConfigStrings( void )
{
  clientList_t alienTeam = G_ClientListForTeam( TEAM_ALIENS );
  clientList_t humanTeam = G_ClientListForTeam( TEAM_HUMANS );

  if( level.intermissiontime )
  {
    // No restrictions once the game has ended
    Com_Memset( &alienTeam, 0, sizeof( clientList_t ) );
    Com_Memset( &humanTeam, 0, sizeof( clientList_t ) );
  }

  trap_SetConfigstringRestrictions( CS_VOTE_TIME + TEAM_ALIENS,   &humanTeam );
  trap_SetConfigstringRestrictions( CS_VOTE_STRING + TEAM_ALIENS, &humanTeam );
  trap_SetConfigstringRestrictions( CS_VOTE_YES + TEAM_ALIENS,    &humanTeam );
  trap_SetConfigstringRestrictions( CS_VOTE_NO + TEAM_ALIENS,     &humanTeam );

  trap_SetConfigstringRestrictions( CS_VOTE_TIME + TEAM_HUMANS,   &alienTeam );
  trap_SetConfigstringRestrictions( CS_VOTE_STRING + TEAM_HUMANS, &alienTeam );
  trap_SetConfigstringRestrictions( CS_VOTE_YES + TEAM_HUMANS,    &alienTeam );
  trap_SetConfigstringRestrictions( CS_VOTE_NO + TEAM_HUMANS,     &alienTeam );

  trap_SetConfigstringRestrictions( CS_ALIEN_STAGES, &humanTeam );
  trap_SetConfigstringRestrictions( CS_HUMAN_STAGES, &alienTeam );
}

/*
==================
G_LeaveTeam
==================
*/
void G_LeaveTeamReal( gentity_t *self, qboolean reset_score )
{
  team_t    team = self->client->pers.teamSelection;
  gentity_t *ent;
  int       i;

  if( team == TEAM_ALIENS ) {
    G_RemoveFromSpawnQueue( &level.alienSpawnQueue, self->client->ps.clientNum );
    if ( reset_score && !level.intermissiontime ) {
      G_admin_reset_score( self );
    }
  } else if( team == TEAM_HUMANS ) {
    G_RemoveFromSpawnQueue( &level.humanSpawnQueue, self->client->ps.clientNum );
    if ( reset_score && !level.intermissiontime ) {
      G_admin_reset_score( self );
    }
  } else {
    if( self->client->sess.spectatorState == SPECTATOR_FOLLOW )
      G_StopFollowing( self );
    return;
  }

  // stop any following clients
  G_StopFromFollowing( self );

  G_Vote( self, team, qfalse );
  self->suicideTime = 0;

  for( i = 0; i < level.num_entities; i++ )
  {
    ent = &g_entities[ i ];
    if( !ent->inuse )
      continue;

    if( ent->client && ent->client->pers.connected == CON_CONNECTED )
    {
      // cure poison
      if( ent->client->ps.stats[ STAT_STATE ] & SS_POISONED &&
          ent->client->lastPoisonClient == self )
        ent->client->ps.stats[ STAT_STATE ] &= ~SS_POISONED;

      // cure infection
      if( ent->client->ps.stats[ STAT_STATE ] & SS_INFECTED &&
          ent->client->lastInfectionClient == self )
        ent->client->ps.stats[ STAT_STATE ] &= ~SS_INFECTED;
    }
    else if( ent->s.eType == ET_MISSILE && ent->r.ownerNum == self->s.number )
      G_FreeEntity( ent );
  }

  // cut all relevant zap beams
  G_ClearPlayerZapEffects( self );
  
  G_namelog_update_score( self->client );

  G_admin_writeconfig();
}

void G_LeaveTeam( gentity_t *self )
{
  G_LeaveTeamReal( self, qtrue );
}

/*
=================
G_ChangeTeam
=================
*/
void G_ChangeTeam( gentity_t *ent, team_t newTeam )
{
  team_t  oldTeam = ent->client->pers.teamSelection;

  if( oldTeam == newTeam )
    return;

  G_LeaveTeamReal( ent, ( newTeam == TEAM_NONE ) ? qtrue : qfalse );

  ent->client->pers.teamChangeTime = level.time;
  ent->client->pers.teamSelection = newTeam;
  ent->client->pers.classSelection = PCL_NONE;
  ClientSpawn( ent, NULL, NULL, NULL );
  
  if( oldTeam == TEAM_HUMANS && newTeam == TEAM_ALIENS )
  {
    // Convert from human to alien credits
    ent->client->pers.credit =
      (int)( ent->client->pers.credit *
             ALIEN_MAX_CREDITS / HUMAN_MAX_CREDITS + 0.5f );
  }
  else if( oldTeam == TEAM_ALIENS && newTeam == TEAM_HUMANS )
  {
    // Convert from alien to human credits
    ent->client->pers.credit =
      (int)( ent->client->pers.credit *
             HUMAN_MAX_CREDITS / ALIEN_MAX_CREDITS + 0.5f );
  }

  // Copy credits to ps for the client
  ent->client->ps.persistant[ PERS_CREDIT ] = ent->client->pers.credit;

  ClientUserinfoChanged( ent->client->ps.clientNum, qfalse );

  G_UpdateTeamConfigStrings( );

  G_LogPrintf( "ChangeTeam: %d %s: %s" S_COLOR_WHITE " switched teams\n",
    ent - g_entities, BG_TeamName( newTeam ), ent->client->pers.netname );

  G_namelog_update_score( ent->client );
  TeamplayInfoMessage( ent );

}


/*
  Call this method to balance teams
*/
void G_BalanceTeams( void )
{
  team_t sourceTeam;
  gentity_t *ent;
  int clientId,lastTime,i;
  if( level.numAlienSpawns > 0 && level.numHumanClients - level.numAlienClients >= 2 ) {
    sourceTeam = TEAM_HUMANS;
  } else if( level.numHumanSpawns > 0 && level.numAlienClients - level.numHumanClients >= 2 ) {
    sourceTeam = TEAM_ALIENS;
  } else return;
  clientId = -1;
  lastTime = 0;
  for( i = 0; i < g_maxclients.integer; i++ )
    {
      ent = g_entities + i;

      if( ent->client->pers.connected != CON_CONNECTED )
        continue;
      
      if( ent->inuse && ent->client->pers.teamSelection == sourceTeam && ent->client->pers.teamChangeTime > lastTime) {
       clientId = i;
       lastTime = ent->client->pers.teamChangeTime;
      }
    }
  if (clientId != -1) {
    ent = g_entities + clientId;

    switch(sourceTeam) {
    case TEAM_HUMANS:
      // Refund all weapons and equipment before team change
      for( i = WP_NONE+1; i < WP_NUM_WEAPONS; ++i )
      {
        if ( i == WP_HBUILD && ent->client->ps.stats[ STAT_MISC ] > 0 ) 
		continue;
        if (BG_InventoryContainsWeapon( i, ent->client->ps.stats ) && BG_Weapon( i )->purchasable )
        {
         G_AddCreditToClient( ent->client, (short)BG_Weapon( i )->price, qfalse );
        }
      }
      for( i = UP_NONE+1; i < UP_NUM_UPGRADES; ++i )
      {
        if (BG_InventoryContainsUpgrade( i, ent->client->ps.stats ) && BG_Upgrade( i )->purchasable )
        {
         G_AddCreditToClient( ent->client, (short)BG_Upgrade( i )->price, qfalse );
        }
      }
      trap_SendServerCommand( -1, "print \"Humans have more players. Moving last joining player to alien team...\n\"");
      break;
    case TEAM_ALIENS:
      // Refund evo-points to aliens
      G_AddCreditToClient( ent->client, (short)(BG_Class( ent->client->ps.stats[ STAT_CLASS ] )->cost * ALIEN_CREDITS_PER_KILL), qfalse );
     
      trap_SendServerCommand( -1, "print \"Aliens have more players. Moving last joining player to human team...\n\"");
      break;
    default:
      break;
    }
    G_ChangeTeam(ent,(sourceTeam == TEAM_HUMANS) ? TEAM_ALIENS : TEAM_HUMANS);
  }
}


/*
===========
Team_GetLocation

Report a location for the player. Uses placed nearby target_location entities
============
*/
gentity_t *Team_GetLocation( gentity_t *ent )
{
  gentity_t   *eloc, *best;
  float       bestlen, len;

  best = NULL;
  bestlen = 3.0f * 8192.0f * 8192.0f;

  for( eloc = level.locationHead; eloc; eloc = eloc->nextTrain )
  {
    len = DistanceSquared( ent->r.currentOrigin, eloc->r.currentOrigin );

    if( len > bestlen )
      continue;

    if( !trap_InPVS( ent->r.currentOrigin, eloc->r.currentOrigin ) )
      continue;

    bestlen = len;
    best = eloc;
  }

  return best;
}


/*---------------------------------------------------------------------------*/

/*
==================
TeamplayInfoMessage

Format:
  clientNum location health weapon upgrade

==================
*/
void TeamplayInfoMessage( gentity_t *ent )
{
  char      entry[ 19 ], string[ 1143 ];
  int       i, j; 
  int       team, stringlength;
  int       sent = 0;
  gentity_t *player;
  gclient_t *cl;
  upgrade_t upgrade = UP_NONE;
  int       curWeaponClass = WP_NONE ; // sends weapon for humans, class for aliens
  char      *tmp;

  if( !g_allowTeamOverlay.integer )
     return;

  if( !ent->client->pers.teamInfo )
     return;

  if( ent->client->pers.teamSelection == TEAM_NONE )
  {
    if( ent->client->sess.spectatorState == SPECTATOR_FREE ||
        ent->client->sess.spectatorClient < 0 )
      return;
    team = g_entities[ ent->client->sess.spectatorClient ].client->
      pers.teamSelection;
  }
  else
    team = ent->client->pers.teamSelection;

  string[ 0 ] = '\0';
  stringlength = 0;

  for( i = 0; i < MAX_CLIENTS; i++)
  {
    player = g_entities + i ;
    cl = player->client;

    if( ent == player || !cl || team != cl->pers.teamSelection ||
        !player->inuse )
      continue;

    if( cl->sess.spectatorState != SPECTATOR_NOT )
    {
      curWeaponClass = WP_NONE;
      upgrade = UP_NONE;
    }
    else if ( cl->pers.teamSelection == TEAM_HUMANS )
    {
      curWeaponClass = cl->ps.weapon;

      if( BG_InventoryContainsUpgrade( UP_BATTLESUIT, cl->ps.stats ) )
        upgrade = UP_BATTLESUIT;
      else if( BG_InventoryContainsUpgrade( UP_JETPACK, cl->ps.stats ) )
        upgrade = UP_JETPACK;
      else if( BG_InventoryContainsUpgrade( UP_BATTPACK, cl->ps.stats ) )
        upgrade = UP_BATTPACK;
      else if( BG_InventoryContainsUpgrade( UP_HELMET, cl->ps.stats ) )
        upgrade = UP_HELMET;
      else if( BG_InventoryContainsUpgrade( UP_LIGHTARMOUR, cl->ps.stats ) )
        upgrade = UP_LIGHTARMOUR;
      else
        upgrade = UP_NONE;
    }
    else if( cl->pers.teamSelection == TEAM_ALIENS )
    {
      curWeaponClass = cl->ps.stats[ STAT_CLASS ];
      upgrade = UP_NONE;
    }
 
    tmp = va( "%i %i %i %i",
      player->client->pers.location,
      player->client->ps.stats[ STAT_HEALTH ] < 1 ? 0 :
        player->client->ps.stats[ STAT_HEALTH ],
      curWeaponClass, 
      upgrade );

    if( !strcmp( ent->client->pers.cinfo[ i ], tmp ) )
      continue;

    Q_strncpyz( ent->client->pers.cinfo[ i ], tmp,
      sizeof( ent->client->pers.cinfo[ i ] ) );

    Com_sprintf( entry, sizeof( entry ), " %i %s", i, tmp );

    j = strlen( entry );

    if( stringlength + j > sizeof( string ) )
      break;

    strcpy( string + stringlength, entry );
    stringlength += j;
    sent++;
  }

  if( !sent )
    return; 

  trap_SendServerCommand( ent - g_entities, va( "tinfo%s", string ) );
}

void CheckTeamStatus( void )
{
  int i;
  gentity_t *loc, *ent;

  if( level.time - level.lastTeamLocationTime > TEAM_LOCATION_UPDATE_TIME )
  {
    level.lastTeamLocationTime = level.time;

    for( i = 0; i < g_maxclients.integer; i++ )
    {
      ent = g_entities + i;
      if( ent->client->pers.connected != CON_CONNECTED )
        continue;

      if( ent->inuse && ( ent->client->ps.stats[ STAT_TEAM ] == TEAM_HUMANS ||
                          ent->client->ps.stats[ STAT_TEAM ] == TEAM_ALIENS ) )
      {

        loc = Team_GetLocation( ent );

        if( loc )
          ent->client->pers.location = loc->s.generic1;
        else
          ent->client->pers.location = 0;
      }
    }

    for( i = 0; i < g_maxclients.integer; i++ )
    {
      ent = g_entities + i;
      if( ent->client->pers.connected != CON_CONNECTED )
        continue;

      if( ent->inuse )
        TeamplayInfoMessage( ent );
    }
  }

  // Warn on imbalanced teams
  if( !level.humanTeamLocked && !level.alienTeamLocked &&
      g_teamForceBalance.integer == 1 &&
      g_teamImbalanceWarnings.integer && !level.intermissiontime &&
      ( level.time - level.lastTeamImbalancedTime >
        ( g_teamImbalanceWarnings.integer * 1000 ) ) &&
      level.numTeamImbalanceWarnings < 3 && !level.restarted )
  {
    level.lastTeamImbalancedTime = level.time;
    if( level.numAlienSpawns > 0 && 
        level.numHumanClients - level.numAlienClients >= 2 )
    {
      trap_SendServerCommand( -1, "print \"^5Teams are imbalanced. "
                                  "^5Humans have more players.\n\"");
      level.numTeamImbalanceWarnings++;
      G_BalanceTeams();
    }
    else if( level.numHumanSpawns > 0 && level.numHumanArmouries > 0 &&
             level.numAlienClients - level.numHumanClients >= 2 )
    {
      trap_SendServerCommand ( -1, "print \"^5Teams are imbalanced. "
                                   "^5Aliens have more players.\n\"");
      level.numTeamImbalanceWarnings++;
      G_BalanceTeams();
    }
    else
    {
      level.numTeamImbalanceWarnings = 0;
    }
  }
}