//=========== (C) Copyright 1999 Valve, L.L.C. All rights reserved. ===========
//
// The copyright to the contents herein is the property of Valve, L.L.C.
// The contents may be used and/or copied only with the written permission of
// Valve, L.L.C., or in accordance with the terms and conditions stipulated in
// the agreement/contract under which the contents have been supplied.
//
// Purpose: VGUI scoreboard
//
// $Workfile:     $
// $Date:         $
//
//-----------------------------------------------------------------------------
// $Log: $
//
// $NoKeywords: $
//=============================================================================


#include<VGUI_LineBorder.h>

#include "hud.h"
#include "cl_util.h"
#include "const.h"
#include "entity_state.h"
#include "cl_entity.h"
#include "vgui_TeamFortressViewport.h"
#include "vgui_ScorePanel.h"

hud_player_info_t	 g_PlayerInfoList[MAX_PLAYERS+1];	   // player info from the engine
extra_player_info_t  g_PlayerExtraInfo[MAX_PLAYERS+1];   // additional player info sent directly to the client dll
team_info_t			 g_TeamInfo[MAX_TEAMS+1];
int					 g_IsSpectator[MAX_PLAYERS+1];

int HUD_IsGame( const char *game );
int EV_TFC_IsAllyTeam( int iTeam1, int iTeam2 );

// Scoreboard dimensions
#define SBOARD_TITLE_SIZE_Y			YRES(24)
#define SBOARD_HEADER_SIZE_Y		YRES(40)
#define SBOARD_TABLE_X				XRES(16)
#define SBOARD_TABLE_Y				(SBOARD_TITLE_SIZE_Y + SBOARD_HEADER_SIZE_Y)

#define SBOARD_TEAM_CELL_SIZE_Y		YRES(20)
#define SBOARD_CELL_SIZE_Y			YRES(13)

// Column sizes
#define CSIZE_NAME				XRES(140)
#define CSIZE_CLASS				CSIZE_NAME + XRES(64)
#define CSIZE_KILLS				CSIZE_CLASS + XRES(60)
#define CSIZE_DEATHS			CSIZE_KILLS + XRES(64)

#define SMALL_CSIZE_NAME		XRES(124)					
#define SMALL_CSIZE_CLASS		SMALL_CSIZE_NAME + XRES(60)	
#define SMALL_CSIZE_KILLS		SMALL_CSIZE_CLASS + XRES(64)
#define SMALL_CSIZE_DEATHS		SMALL_CSIZE_KILLS + XRES(70)

#define TEAM_NO				0
#define TEAM_YES			1
#define TEAM_UNASSIGNED		2
#define TEAM_SPECTATORS		3

// Team Colors used in the scoreboard
int ScoreColorsBG[5][3] =
{
	{ 0, 0, 0 },
	{ 66, 114, 247 },
	{ 220, 51, 38 },
	{ 236, 212, 48 },
	{ 68, 199, 42 },
};

int ScoreColorsFG[5][3] =
{
	{ 255, 255, 255 },
	{ 170, 193, 251 },
	{ 215, 151, 146 },
	{ 227, 203, 46 },
	{ 143, 215, 142 },
};

//-----------------------------------------------------------------------------
// Purpose: Set different cell heights for Teams and Players
//-----------------------------------------------------------------------------
int ScoreTablePanel::getCellTall(int row)
{
	if ( m_iIsATeam[row] )
		return SBOARD_TEAM_CELL_SIZE_Y;

	return SBOARD_CELL_SIZE_Y;
}

//-----------------------------------------------------------------------------
// Purpose: Render each of the cells in the Table
//-----------------------------------------------------------------------------
Panel* ScoreTablePanel::getCellRenderer(int column,int row,bool columnSelected,bool rowSelected,bool cellSelected)
{
	char sz[128];
	hud_player_info_t *pl_info = NULL;
	team_info_t *team_info = NULL;

	if ( m_iIsATeam[row] == TEAM_YES )
	{
		// Get the team's data
		team_info = &g_TeamInfo[ m_iSortedRows[row] ];

		// White text for team names
		m_pLabel->setFgColor(Scheme::sc_white);

		// Set background color
		m_pLabel->setBgColor( ScoreColorsBG[ team_info->teamnumber ][0], ScoreColorsBG[ team_info->teamnumber ][1], ScoreColorsBG[ team_info->teamnumber ][2], 128 );
	}
	else if ( m_iIsATeam[row] == TEAM_UNASSIGNED || m_iIsATeam[row] == TEAM_SPECTATORS )
	{
		// White text for team names
		m_pLabel->setFgColor(Scheme::sc_white);

		// Set background color
		m_pLabel->setBgColor( 0,0,0, 255 );
	}
	else
	{
		// Grey text for player names
		m_pLabel->setFgColor( ScoreColorsFG[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][0], ScoreColorsFG[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][1], ScoreColorsFG[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][2], 128 );

		// Get the player's data
		pl_info = &g_PlayerInfoList[ m_iSortedRows[row] ];

		// Set background color
		if ( pl_info->thisplayer ) // if it is their name, draw it a different color
		{
			// Highlight this player
			m_pLabel->setFgColor(Scheme::sc_white);
			m_pLabel->setBgColor( ScoreColorsBG[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][0], ScoreColorsBG[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][1], ScoreColorsBG[ g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ][2], 196 );
		}
		else if ( m_iSortedRows[row] == m_iLastKilledBy && m_fLastKillTime && m_fLastKillTime > gHUD.m_flTime )
		{
			// Killer's name
			m_pLabel->setBgColor( 255,0,0, 255 - ((float)15 * (float)(m_fLastKillTime - gHUD.m_flTime)) );
		}
		else
		{
			m_pLabel->setBgColor( 0,0,0, 255 );
		}
	}

	// Align 
	if (column <= 1)
	{
		if ( m_iIsATeam[row] )
			m_pLabel->setContentAlignment( vgui::Label::a_southwest );
		else
			m_pLabel->setContentAlignment( vgui::Label::a_west );
	}
	else
	{
		if ( m_iIsATeam[row] )
			m_pLabel->setContentAlignment( vgui::Label::a_south );
		else
			m_pLabel->setContentAlignment( vgui::Label::a_center );
	}

	// Fill out with the correct data
	if ( m_iIsATeam[row] )
	{
		int i;
		char sz2[128];
		strcpy(sz, "");

		switch (column)
		{
		case 0:
			if ( m_iIsATeam[row] == TEAM_UNASSIGNED )
			{
				sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Unassigned" ) );
			}
			else if ( m_iIsATeam[row] == TEAM_SPECTATORS )
			{
				sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( "#Spectators" ) );
			}
			else
			{
				sprintf( sz2, CHudTextMessage::BufferedLocaliseTextString( team_info->name ) );
			}

			// Uppercase it
			for (i = 0; i < (int)strlen(sz2); i++)
			{
				if ( *(sz2 + i) )
					sz[i] = toupper( *(sz2 + i) );
			}
			sz[i] = '\0';

			// Append the number of players
			if ( m_iIsATeam[row] == TEAM_YES )
			{
				if (team_info->players == 1)
					sprintf(sz, "%s (1 player)", sz );
				else
					sprintf(sz, "%s (%d players)", sz, team_info->players );
			}
			break;
		case 1:
			// No class for teams
			break;
		case 2:
			if ( m_iIsATeam[row] == TEAM_YES )
				sprintf(sz, "%d",  team_info->frags );
			break;
		case 3:
			if ( m_iIsATeam[row] == TEAM_YES )
				sprintf(sz, "%d",  team_info->deaths );
			break;
		case 4:
			if ( m_iIsATeam[row] == TEAM_YES )
				sprintf(sz, "%d", team_info->ping );
			break;
		default:
			break;
		}

		m_pLabel->setText(sz);
	}
	else
	{
		bool bShowClass = false;

		switch (column)
		{
		case 0:
			sprintf(sz, "  %s", pl_info->name);
			break;
		case 1:
			// No class for other team's members (unless allied or spectator)
			if ( gViewPort && EV_TFC_IsAllyTeam( g_iTeamNumber, g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber )  )
				bShowClass = true;
			// Don't show classes if this client hasnt picked a team yet
			if ( g_iTeamNumber == 0 )
				bShowClass = false;
			if ( g_iUser1 )
				bShowClass = true;

			if (bShowClass)
			{
				// Only print Civilian if this team are all civilians
				bool bNoClass = false;
				if ( g_PlayerExtraInfo[ m_iSortedRows[row] ].playerclass == 0 )
				{
					if ( gViewPort->GetValidClasses( g_PlayerExtraInfo[ m_iSortedRows[row] ].teamnumber ) != -1 )
						bNoClass = true;
				}

				if (bNoClass)
					sprintf(sz, "");
				else
					sprintf( sz, "%s", CHudTextMessage::BufferedLocaliseTextString( sLocalisedClasses[ g_PlayerExtraInfo[ m_iSortedRows[row] ].playerclass ] ) );
			}
			else
			{
				strcpy(sz, "");
			}
			break;
		case 2:
			sprintf(sz, "%d",  g_PlayerExtraInfo[ m_iSortedRows[row] ].frags );
			break;
		case 3:
			sprintf(sz, "%d",  g_PlayerExtraInfo[ m_iSortedRows[row] ].deaths );
			break;
		case 4:
			sprintf(sz, "%d", g_PlayerInfoList[ m_iSortedRows[row] ].ping );
			break;
		default:
			strcpy(sz, "");
		}

		m_pLabel->setText(sz);
	}

	return m_pLabel;
}

//-----------------------------------------------------------------------------
// Purpose: Create the ScoreBoard panel
//-----------------------------------------------------------------------------
ScorePanel::ScorePanel(int x,int y,int wide,int tall) : Panel(x,y,wide,tall)
{
	setBorder( new LineBorder( Color(255 * 0.7,170 * 0.7,0,0) ) );
	setBgColor( 0,0,0, 100 );

	m_pTitleLabel = new Label( "  SCORES", 0,0, wide, SBOARD_TITLE_SIZE_Y );
	m_pTitleLabel->setBgColor( Scheme::sc_primary2 );
	m_pTitleLabel->setFgColor( Scheme::sc_primary1 );
	m_pTitleLabel->setContentAlignment( vgui::Label::a_west );
	m_pTitleLabel->setParent(this);

	_headerPanel = new HeaderPanel( SBOARD_TABLE_X, SBOARD_TITLE_SIZE_Y, wide - (SBOARD_TABLE_X * 2), SBOARD_HEADER_SIZE_Y);
	_headerPanel->setParent(this);

	// BUGBUG: This isn't working. gHUD.m_Teamplay hasn't been initialized yet.
	if ( gHUD.m_Teamplay )
		_headerPanel->addSectionPanel( new CLabelHeader( CHudTextMessage::BufferedLocaliseTextString( "#TEAMS" ), true) );
	else
		_headerPanel->addSectionPanel( new CLabelHeader( CHudTextMessage::BufferedLocaliseTextString( "#PLAYERS" ), true) );

	if ( HUD_IsGame( "tfc" ) )
		_headerPanel->addSectionPanel( new CLabelHeader( CHudTextMessage::BufferedLocaliseTextString( "#CLASS" ), true) );
	else
		_headerPanel->addSectionPanel( new CLabelHeader("", true) );

	_headerPanel->addSectionPanel( new CLabelHeader( CHudTextMessage::BufferedLocaliseTextString( "#SCORE" )) );
	_headerPanel->addSectionPanel( new CLabelHeader( CHudTextMessage::BufferedLocaliseTextString( "#DEATHS")) );
	_headerPanel->addSectionPanel( new CLabelHeader( CHudTextMessage::BufferedLocaliseTextString( "#LATENCY")) );
	_headerPanel->setBgColor( 0,0,0, 255 );

	// Need to special case 400x300, because the titles just wont fit otherwise
	if ( ScreenWidth == 400 )
	{
		_headerPanel->setSliderPos( 0, SMALL_CSIZE_NAME );
		_headerPanel->setSliderPos( 1, SMALL_CSIZE_CLASS );
		_headerPanel->setSliderPos( 2, SMALL_CSIZE_KILLS );
		_headerPanel->setSliderPos( 3, SMALL_CSIZE_DEATHS );
		_headerPanel->setSliderPos( 4, wide - (SBOARD_TABLE_X * 2) - 1 );
	}
	else
	{
		_headerPanel->setSliderPos( 0, CSIZE_NAME );
		_headerPanel->setSliderPos( 1, CSIZE_CLASS );
		_headerPanel->setSliderPos( 2, CSIZE_KILLS );
		_headerPanel->setSliderPos( 3, CSIZE_DEATHS );
		_headerPanel->setSliderPos( 4, wide - (SBOARD_TABLE_X * 2) - 1 );
	}

	_tablePanel = new ScoreTablePanel(SBOARD_TABLE_X, SBOARD_TABLE_Y, wide - (SBOARD_TABLE_X * 2), tall - SBOARD_TABLE_Y, NUM_COLUMNS);
	_tablePanel->setParent(this);
	_tablePanel->setHeaderPanel(_headerPanel);
	_tablePanel->setBgColor( 0,0,0, 255 );

	Initialize();
}

//-----------------------------------------------------------------------------
// Purpose: Called each time a new level is started.
//-----------------------------------------------------------------------------
void ScorePanel::Initialize( void )
{
	// Clear out scoreboard data
	_tablePanel->m_iLastKilledBy = 0;
	_tablePanel->m_fLastKillTime = 0;
	m_iPlayerNum = 0;
	m_iNumTeams = 0;
	memset( g_PlayerExtraInfo, 0, sizeof g_PlayerExtraInfo );
	memset( g_TeamInfo, 0, sizeof g_TeamInfo );
}

//-----------------------------------------------------------------------------
// Purpose: Set the size of the header and table when this panel's size changes
//-----------------------------------------------------------------------------
void ScorePanel::setSize(int wide,int tall)
{
	Panel::setSize(wide,tall);

	_headerPanel->setBounds(0,0,wide,SBOARD_HEADER_SIZE_Y);
	_tablePanel->setBounds(0,20,wide,tall - SBOARD_HEADER_SIZE_Y);
}

//-----------------------------------------------------------------------------
// Purpose: Recalculate the internal scoreboard data
//-----------------------------------------------------------------------------
void ScorePanel::Update()
{
	// Set the title
	if (gViewPort->m_szServerName)
	{
		char sz[MAX_SERVERNAME_LENGTH + 16];
		sprintf(sz, "  SCORES: %s", gViewPort->m_szServerName );
		m_pTitleLabel->setText(sz);
	}

	_tablePanel->m_iRows = 0;
	gViewPort->GetAllPlayersInfo();

	// Clear out sorts
	for (int i = 0; i < MAX_PLAYERS; i++)
	{
		_tablePanel->m_iSortedRows[i] = 0;
		_tablePanel->m_iIsATeam[i] = TEAM_NO;
		_tablePanel->m_bHasBeenSorted[i] = false;
	}

	// If it's not teamplay, sort all the players. Otherwise, sort the teams.
	if ( !gHUD.m_Teamplay )
		SortPlayers( 0, NULL );
	else
		SortTeams();
}

//-----------------------------------------------------------------------------
// Purpose: Sort all the teams
//-----------------------------------------------------------------------------
void ScorePanel::SortTeams()
{
	// clear out team scores
	for ( int i = 1; i <= m_iNumTeams; i++ )
	{
		if ( !g_TeamInfo[i].scores_overriden )
			g_TeamInfo[i].frags = g_TeamInfo[i].deaths = 0;
		g_TeamInfo[i].ping = g_TeamInfo[i].packetloss = 0;
	}

	// recalc the team scores, then draw them
	for ( i = 1; i < MAX_PLAYERS; i++ )
	{
		if ( g_PlayerInfoList[i].name == NULL )
			continue; // empty player slot, skip

		if ( g_PlayerExtraInfo[i].teamname[0] == 0 )
			continue; // skip over players who are not in a team

		// find what team this player is in
		for ( int j = 1; j <= m_iNumTeams; j++ )
		{
			if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) )
				break;
		}
		if ( j > m_iNumTeams )  // player is not in a team, skip to the next guy
			continue;

		if ( !g_TeamInfo[j].scores_overriden )
		{
			g_TeamInfo[j].frags += g_PlayerExtraInfo[i].frags;
			g_TeamInfo[j].deaths += g_PlayerExtraInfo[i].deaths;
		}

		g_TeamInfo[j].ping += g_PlayerInfoList[i].ping;
		g_TeamInfo[j].packetloss += g_PlayerInfoList[i].packetloss;

		if ( g_PlayerInfoList[i].thisplayer )
			g_TeamInfo[j].ownteam = TRUE;
		else
			g_TeamInfo[j].ownteam = FALSE;

		// Set the team's number (used for team colors)
		g_TeamInfo[j].teamnumber = g_PlayerExtraInfo[i].teamnumber;
	}

	// find team ping/packetloss averages
	for ( i = 1; i <= m_iNumTeams; i++ )
	{
		g_TeamInfo[i].already_drawn = FALSE;

		if ( g_TeamInfo[i].players > 0 )
		{
			g_TeamInfo[i].ping /= g_TeamInfo[i].players;  // use the average ping of all the players in the team as the teams ping
			g_TeamInfo[i].packetloss /= g_TeamInfo[i].players;  // use the average ping of all the players in the team as the teams ping
		}
	}

	// Draw the teams
	while ( 1 )
	{
		int highest_frags = -99999; int lowest_deaths = 99999;
		int best_team = 0;

		for ( i = 1; i <= m_iNumTeams; i++ )
		{
			if ( g_TeamInfo[i].players < 1 )
				continue;

			if ( !g_TeamInfo[i].already_drawn && g_TeamInfo[i].frags >= highest_frags )
			{
				if ( g_TeamInfo[i].frags > highest_frags || g_TeamInfo[i].deaths < lowest_deaths )
				{
					best_team = i;
					lowest_deaths = g_TeamInfo[i].deaths;
					highest_frags = g_TeamInfo[i].frags;
				}
			}
		}

		// draw the best team on the scoreboard
		if ( !best_team )
			break;

		// Put this team in the sorted list
		_tablePanel->m_iSortedRows[ _tablePanel->m_iRows ] = best_team;
		_tablePanel->m_iIsATeam[ _tablePanel->m_iRows ] = TEAM_YES;
		g_TeamInfo[best_team].already_drawn = TRUE;  // set the already_drawn to be TRUE, so this team won't get sorted again
		_tablePanel->m_iRows++;

		// Now sort all the players on this team
		SortPlayers( 0, g_TeamInfo[best_team].name );
	}

	// Now add all the spectators
	SortPlayers( TEAM_SPECTATORS, NULL );

	// Now add all the players who aren't in a team yet
	SortPlayers( TEAM_UNASSIGNED, NULL );
}

//-----------------------------------------------------------------------------
// Purpose: Sort a list of players
//-----------------------------------------------------------------------------
void ScorePanel::SortPlayers( int iTeam, char *team )
{
	bool bCreatedTeam = false;

	// draw the players, in order,  and restricted to team if set
	while ( 1 )
	{
		// Find the top ranking player
		int highest_frags = -99999;	int lowest_deaths = 99999;
		int best_player;
		best_player = 0;

		for ( int i = 1; i < MAX_PLAYERS; i++ )
		{
			if ( _tablePanel->m_bHasBeenSorted[i] == false && g_PlayerInfoList[i].name && g_PlayerExtraInfo[i].frags >= highest_frags )
			{
				cl_entity_t *ent = gEngfuncs.GetEntityByIndex( i );

				if ( ent && ((iTeam == TEAM_SPECTATORS && g_IsSpectator[i] != 0) || (iTeam != TEAM_SPECTATORS && !(team && stricmp(g_PlayerExtraInfo[i].teamname, team)))) )  
				{
					extra_player_info_t *pl_info = &g_PlayerExtraInfo[i];
					if ( pl_info->frags > highest_frags || pl_info->deaths < lowest_deaths )
					{
						best_player = i;
						lowest_deaths = pl_info->deaths;
						highest_frags = pl_info->frags;
					}
				}
			}
		}

		if ( !best_player )
			break;

		// If we haven't created the Team yet, do it first
		if (!bCreatedTeam && iTeam)
		{
			_tablePanel->m_iIsATeam[ _tablePanel->m_iRows ] = iTeam;
			_tablePanel->m_iRows++;

			bCreatedTeam = true;
		}

		// Put this player in the sorted list
		_tablePanel->m_iSortedRows[ _tablePanel->m_iRows ] = best_player;
		_tablePanel->m_bHasBeenSorted[ best_player ] = true;
		_tablePanel->m_iRows++;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Recalculate the existing teams in the match
//-----------------------------------------------------------------------------
void ScorePanel::RebuildTeams()
{
	// clear out player counts from teams
	for ( int i = 1; i <= m_iNumTeams; i++ )
	{
		g_TeamInfo[i].players = 0;
	}

	// rebuild the team list
	gViewPort->GetAllPlayersInfo();
	m_iNumTeams = 0;
	for ( i = 1; i < MAX_PLAYERS; i++ )
	{
		if ( g_PlayerInfoList[i].name == NULL )
			continue;

		if ( g_PlayerExtraInfo[i].teamname[0] == 0 )
			continue; // skip over players who are not in a team

		// is this player in an existing team?
		for ( int j = 1; j <= m_iNumTeams; j++ )
		{
			if ( g_TeamInfo[j].name[0] == '\0' )
				break;

			if ( !stricmp( g_PlayerExtraInfo[i].teamname, g_TeamInfo[j].name ) )
				break;
		}

		if ( j > m_iNumTeams )
		{ // they aren't in a listed team, so make a new one
			// search through for an empty team slot
			for ( int j = 1; j <= m_iNumTeams; j++ )
			{
				if ( g_TeamInfo[j].name[0] == '\0' )
					break;
			}
			m_iNumTeams = max( j, m_iNumTeams );

			strncpy( g_TeamInfo[j].name, g_PlayerExtraInfo[i].teamname, MAX_TEAM_NAME );
			g_TeamInfo[j].players = 0;
		}

		g_TeamInfo[j].players++;
	}

	// clear out any empty teams
	for ( i = 1; i <= m_iNumTeams; i++ )
	{
		if ( g_TeamInfo[i].players < 1 )
			memset( &g_TeamInfo[i], 0, sizeof(team_info_t) );
	}

	// Update the scoreboard
	Update();
}

//-----------------------------------------------------------------------------
// Purpose: Setup highlights for player names in scoreboard
//-----------------------------------------------------------------------------
void ScorePanel::DeathMsg( int killer, int victim )
{
	// if we were the one killed,  or the world killed us, set the scoreboard to indicate suicide
	if ( victim == m_iPlayerNum || killer == 0 )
	{
		_tablePanel->m_iLastKilledBy = killer ? killer : m_iPlayerNum;
		_tablePanel->m_fLastKillTime = gHUD.m_flTime + 10;	// display who we were killed by for 10 seconds

		if ( killer == m_iPlayerNum )
			_tablePanel->m_iLastKilledBy = m_iPlayerNum;
	}
}

