//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================

#include "pch_serverbrowser.h"

using namespace vgui;

static const long RETRY_TIME = 10000;		// refresh server every 10 seconds
static const long CHALLENGE_ENTRIES = 1024;

extern "C"
{
	DLL_EXPORT bool JoiningSecureServerCall()
	{
		return true;
	}
}

//-----------------------------------------------------------------------------
// Purpose: Comparison function used in query redblack tree
//-----------------------------------------------------------------------------
bool QueryLessFunc( const struct challenge_s &item1, const struct challenge_s &item2 )
{
	// compare port then ip
	if ( item1.addr.GetPort() < item2.addr.GetPort() )
		return true;
	else if ( item1.addr.GetPort() > item2.addr.GetPort() )
		return false;

	int ip1 = item1.addr.GetIPNetworkByteOrder();
	int ip2 = item2.addr.GetIPNetworkByteOrder();

	return ip1 < ip2;
}


//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
CDialogGameInfo::CDialogGameInfo( vgui::Panel *parent, int serverIP, int queryPort, unsigned short connectionPort, const char *pszConnectCode ) : 
	Frame(parent, "DialogGameInfo"),
	m_CallbackPersonaStateChange( this, &CDialogGameInfo::OnPersonaStateChange ),
	m_sConnectCode( pszConnectCode )
{
	SetBounds(0, 0, 512, 512);
	SetMinimumSize(416, 340);
	SetDeleteSelfOnClose(true);
	m_bConnecting = false;
	m_bServerFull = false;
	m_bShowAutoRetryToggle = false;
	m_bServerNotResponding = false;
	m_bShowingExtendedOptions = false;
	m_SteamIDFriend = 0;
	m_hPingQuery = HSERVERQUERY_INVALID;
	m_hPlayersQuery = HSERVERQUERY_INVALID;
	m_bPlayerListUpdatePending = false;

	m_szPassword[0] = 0;

	m_pConnectButton = new Button(this, "Connect", "#ServerBrowser_JoinGame");
	m_pCloseButton = new Button(this, "Close", "#ServerBrowser_Close");
	m_pRefreshButton = new Button(this, "Refresh", "#ServerBrowser_Refresh");
	m_pInfoLabel = new Label(this, "InfoLabel", "");
	m_pAutoRetry = new ToggleButton(this, "AutoRetry", "#ServerBrowser_AutoRetry");
	m_pAutoRetry->AddActionSignalTarget(this);

	m_pAutoRetryAlert = new RadioButton(this, "AutoRetryAlert", "#ServerBrowser_AlertMeWhenSlotOpens");
	m_pAutoRetryJoin = new RadioButton(this, "AutoRetryJoin", "#ServerBrowser_JoinWhenSlotOpens");
	m_pPlayerList = new ListPanel(this, "PlayerList");
	m_pPlayerList->AddColumnHeader(0, "PlayerName", "#ServerBrowser_PlayerName", 156);
	m_pPlayerList->AddColumnHeader(1, "Score", "#ServerBrowser_Score", 64);
	m_pPlayerList->AddColumnHeader(2, "Time", "#ServerBrowser_Time", 64);

	m_pPlayerList->SetSortFunc(2, &PlayerTimeColumnSortFunc);

	// set the defaults for sorting
	// hack, need to make this more explicit functions in ListPanel
	PostMessage(m_pPlayerList, new KeyValues("SetSortColumn", "column", 2));
	PostMessage(m_pPlayerList, new KeyValues("SetSortColumn", "column", 1));
	PostMessage(m_pPlayerList, new KeyValues("SetSortColumn", "column", 1));

	m_pAutoRetryAlert->SetSelected(true);

	m_pConnectButton->SetCommand(new KeyValues("Connect"));
	m_pCloseButton->SetCommand(new KeyValues("Close"));
	m_pRefreshButton->SetCommand(new KeyValues("Refresh"));

	m_iRequestRetry = 0;

	// create a new server to watch
	memset(&m_Server, 0, sizeof(m_Server) );
	m_Server.m_NetAdr.Init( serverIP, queryPort, connectionPort );

	// refresh immediately
	RequestInfo();

	// let us be ticked every frame
	ivgui()->AddTickSignal(this->GetVPanel());

	LoadControlSettings("Servers/DialogGameInfo.res");
	RegisterControlSettingsFile( "Servers/DialogGameInfo_SinglePlayer.res" );
	RegisterControlSettingsFile( "Servers/DialogGameInfo_AutoRetry.res" );
	MoveToCenterOfScreen();
}

//-----------------------------------------------------------------------------
// Purpose: Destructor
//-----------------------------------------------------------------------------
CDialogGameInfo::~CDialogGameInfo()
{
	if ( !steamapicontext->SteamMatchmakingServers() )
		return;

	if ( m_hPingQuery != HSERVERQUERY_INVALID )
		steamapicontext->SteamMatchmakingServers()->CancelServerQuery( m_hPingQuery );
	if ( m_hPlayersQuery != HSERVERQUERY_INVALID )
		steamapicontext->SteamMatchmakingServers()->CancelServerQuery( m_hPlayersQuery );
}

//-----------------------------------------------------------------------------
// Purpose: send a player query to a server
//-----------------------------------------------------------------------------
void CDialogGameInfo::SendPlayerQuery( uint32 unIP, uint16 usQueryPort )
{
	if ( !steamapicontext->SteamMatchmakingServers() )
		return;

	if ( m_hPlayersQuery != HSERVERQUERY_INVALID )
		steamapicontext->SteamMatchmakingServers()->CancelServerQuery( m_hPlayersQuery );
	m_hPlayersQuery = steamapicontext->SteamMatchmakingServers()->PlayerDetails( unIP, usQueryPort, this );
	m_bPlayerListUpdatePending = true;
}


//-----------------------------------------------------------------------------
// Purpose: Activates the dialog
//-----------------------------------------------------------------------------
void CDialogGameInfo::Run(const char *titleName)
{
	if ( titleName )
	{
		SetTitle( "#ServerBrowser_GameInfoWithNameTitle", true );
	}
	else
	{
		SetTitle( "#ServerBrowser_GameInfoWithNameTitle", true );
	}
	SetDialogVariable( "game", titleName );

	// get the info from the user
	RequestInfo();
	Activate();
}

//-----------------------------------------------------------------------------
// Purpose: Changes which server to watch
//-----------------------------------------------------------------------------
void CDialogGameInfo::ChangeGame( int serverIP, int queryPort, unsigned short connectionPort )
{
	memset( &m_Server, 0x0, sizeof(m_Server) );

	m_Server.m_NetAdr.Init( serverIP, queryPort, connectionPort );

	// remember the dialogs position so we can keep it the same
	int x, y;
	GetPos( x, y );

	// see if we need to change dialog state
	if ( !m_Server.m_NetAdr.GetIP() || !m_Server.m_NetAdr.GetQueryPort() )
	{
		// not in a server, load the simple settings dialog
		SetMinimumSize(0, 0);
		SetSizeable( false );
		LoadControlSettings( "Servers/DialogGameInfo_SinglePlayer.res" );
	}
	else
	{
		// moving from a single-player game -> multiplayer, reset dialog
		SetMinimumSize(416, 340);
		SetSizeable( true );
		LoadControlSettings( "Servers/DialogGameInfo.res" );
	}
	SetPos( x, y );

	// Start refresh immediately
	m_iRequestRetry = 0;
	RequestInfo();
	InvalidateLayout();
}


//-----------------------------------------------------------------------------
// Purpose: updates the dialog if it's watching a friend who changes servers
//-----------------------------------------------------------------------------
void CDialogGameInfo::OnPersonaStateChange( PersonaStateChange_t *pPersonaStateChange )
{
#if 0 // TBD delete this func
	if ( m_SteamIDFriend && m_SteamIDFriend == pPersonaStateChange->m_ulSteamID )
	{
		// friend may have changed servers
		uint64 nGameID;
		uint32 unGameIP;
		uint16 usGamePort;
		uint16 usQueryPort;
		
		if ( SteamFriends()->GetFriendGamePlayed( m_SteamIDFriend, &nGameID, &unGameIP, &usGamePort, &usQueryPort ) )
		{
			if ( pPersonaStateChange->m_nChangeFlags & k_EPersonaChangeGamePlayed )
			{
				ChangeGame( unGameIP, usQueryPort, usGamePort );
			}
		}
		else
		{
			// bugbug johnc: change to not be in a game anymore
		}
	}
#endif
}


//-----------------------------------------------------------------------------
// Purpose: Associates a user with this dialog
//-----------------------------------------------------------------------------
void CDialogGameInfo::SetFriend( uint64 ulSteamIDFriend )
{
	// set the title to include the friends name
	SetTitle( "#ServerBrowser_GameInfoWithNameTitle", true );
	SetDialogVariable( "game", steamapicontext->SteamFriends()->GetFriendPersonaName( ulSteamIDFriend ) );
	SetDialogVariable( "friend", steamapicontext->SteamFriends()->GetFriendPersonaName( ulSteamIDFriend ) );

	// store the friend we're associated with
	m_SteamIDFriend = ulSteamIDFriend;

	FriendGameInfo_t friendGameInfo;
	if ( steamapicontext->SteamFriends()->GetFriendGamePlayed( ulSteamIDFriend, &friendGameInfo ) )
	{
		uint16 usConnPort = friendGameInfo.m_usGamePort;
		if ( friendGameInfo.m_usQueryPort < QUERY_PORT_ERROR )
			usConnPort = friendGameInfo.m_usQueryPort;
		ChangeGame( friendGameInfo.m_unGameIP, usConnPort, friendGameInfo.m_usGamePort );
	}
}


//-----------------------------------------------------------------------------
// Purpose: data access
//-----------------------------------------------------------------------------
uint64 CDialogGameInfo::GetAssociatedFriend()
{
	return m_SteamIDFriend;
}


//-----------------------------------------------------------------------------
// Purpose: lays out the data
//-----------------------------------------------------------------------------
void CDialogGameInfo::PerformLayout()
{
	BaseClass::PerformLayout();

	SetControlString( "ServerText", m_Server.GetName() );
	SetControlString( "GameText", m_Server.m_szGameDescription );
	SetControlString( "MapText", m_Server.m_szMap );
	SetControlString( "GameTags", m_Server.m_szGameTags );


	if ( !m_Server.m_bHadSuccessfulResponse )
	{
		SetControlString("SecureText", "");
	}
	else if ( m_Server.m_bSecure )
	{
		SetControlString("SecureText", "#ServerBrowser_Secure");
	}
	else
	{
		SetControlString("SecureText", "#ServerBrowser_NotSecure");
	}

	char buf[128];
	if ( m_Server.m_nMaxPlayers > 0)
	{
		Q_snprintf(buf, sizeof(buf), "%d / %d", m_Server.m_nPlayers, m_Server.m_nMaxPlayers);
	}
	else
	{
		buf[0] = 0;
	}
	SetControlString("PlayersText", buf);

	if ( m_Server.m_NetAdr.GetIP() && m_Server.m_NetAdr.GetQueryPort() )
	{
		SetControlString("ServerIPText", m_Server.m_NetAdr.GetConnectionAddressString() );
		m_pConnectButton->SetEnabled(true);
		if ( m_pAutoRetry->IsSelected() )
		{
			m_pAutoRetryAlert->SetVisible(true);
			m_pAutoRetryJoin->SetVisible(true);
		}
		else
		{
			m_pAutoRetryAlert->SetVisible(false);
			m_pAutoRetryJoin->SetVisible(false);
		}
	}
	else
	{
		SetControlString("ServerIPText", "");
		m_pConnectButton->SetEnabled(false);
	}

	if ( m_Server.m_bHadSuccessfulResponse )
	{
		Q_snprintf(buf, sizeof(buf), "%d", m_Server.m_nPing );
		SetControlString("PingText", buf);
	}
	else
	{
		SetControlString("PingText", "");
	}

	// set the info text
	if ( m_pAutoRetry->IsSelected() )
	{
		if ( m_Server.m_nPlayers < m_Server.m_nMaxPlayers )
		{
			m_pInfoLabel->SetText("#ServerBrowser_PressJoinToConnect");
		}
		else if (m_pAutoRetryJoin->IsSelected())
		{
			m_pInfoLabel->SetText("#ServerBrowser_JoinWhenSlotIsFree");
		}
		else
		{
			m_pInfoLabel->SetText("#ServerBrowser_AlertWhenSlotIsFree");
		}
	}
	else if (m_bServerFull)
	{
		m_pInfoLabel->SetText("#ServerBrowser_CouldNotConnectServerFull");
	}
	else if (m_bServerNotResponding)
	{
		m_pInfoLabel->SetText("#ServerBrowser_ServerNotResponding");
	}
	else
	{
		// clear the status
		m_pInfoLabel->SetText("");
	}

	if ( m_Server.m_bHadSuccessfulResponse && !(m_Server.m_nPlayers + m_Server.m_nBotPlayers) )
	{
		m_pPlayerList->SetEmptyListText("#ServerBrowser_ServerHasNoPlayers");
	}
	else
	{
		m_pPlayerList->SetEmptyListText("#ServerBrowser_ServerNotResponding");
	}

	// auto-retry layout
	m_pAutoRetry->SetVisible(m_bShowAutoRetryToggle);

	Repaint();
}

void CDialogGameInfo::OnKeyCodePressed( vgui::KeyCode code )
{
	if ( code == KEY_XBUTTON_B || code == KEY_XBUTTON_A || code == STEAMCONTROLLER_A || code == STEAMCONTROLLER_B )
	{
		m_pCloseButton->DoClick();
	}
	else
	{
		BaseClass::OnKeyCodePressed(code);
	}
}

//-----------------------------------------------------------------------------
// Purpose: Forces the game info dialog to try and connect
//-----------------------------------------------------------------------------
void CDialogGameInfo::Connect()
{
	OnConnect();
}

//-----------------------------------------------------------------------------
// Purpose: Connects the user to this game
//-----------------------------------------------------------------------------
void CDialogGameInfo::OnConnect()
{
	// flag that we are attempting connection
	m_bConnecting = true;

	// reset state
	m_bServerFull = false;
	m_bServerNotResponding = false;

	InvalidateLayout();

	// need to refresh server before attempting to connect, to make sure there is enough room on the server
	m_iRequestRetry = 0;
	RequestInfo();
}

//-----------------------------------------------------------------------------
// Purpose: Cancel auto-retry if we connect to the game by other means
//-----------------------------------------------------------------------------
void CDialogGameInfo::OnConnectToGame( int ip, int port )
{
	// if we just connected to the server we were looking at, close the dialog
	// important so that we don't auto-retry a server that we are already on
	if ( m_Server.m_NetAdr.GetIP() == (uint32)ip && m_Server.m_NetAdr.GetConnectionPort() == (uint16)port )
	{
		// close this dialog
		Close();
	}
}

//-----------------------------------------------------------------------------
// Purpose: Handles Refresh button press, starts a re-ping of the server
//-----------------------------------------------------------------------------
void CDialogGameInfo::OnRefresh()
{
	m_iRequestRetry = 0;
	// re-ask the server for the game info
	RequestInfo();
}

//-----------------------------------------------------------------------------
// Purpose: Forces the whole dialog to redraw when the auto-retry button is toggled
//-----------------------------------------------------------------------------
void CDialogGameInfo::OnButtonToggled(Panel *panel)
{
	if (panel == m_pAutoRetry)
	{
		ShowAutoRetryOptions(m_pAutoRetry->IsSelected());
	}

	InvalidateLayout();
}

//-----------------------------------------------------------------------------
// Purpose: Sets whether the extended auto-retry options are visible or not
//-----------------------------------------------------------------------------
void CDialogGameInfo::ShowAutoRetryOptions(bool state)
{
	// we need to extend the dialog
	int growSize = 60;
	if (!state)
	{
		growSize = -growSize;
	}

	// alter the dialog size accordingly
	int x, y, wide, tall;
	GetBounds( x, y, wide, tall );

	// load a new layout file depending on the state
	SetMinimumSize(416, 340);
	if ( state )
		LoadControlSettings( "Servers/DialogGameInfo_AutoRetry.res" );
	else
		LoadControlSettings( "Servers/DialogGameInfo.res" );

	// restore size and position as 
	// load control settings will override them
	SetBounds( x, y, wide, tall + growSize );

	// restore other properties of the dialog
	PerformLayout();

	m_pAutoRetryAlert->SetSelected( true );
	
	InvalidateLayout();
}

//-----------------------------------------------------------------------------
// Purpose: Requests the right info from the server
//-----------------------------------------------------------------------------
void CDialogGameInfo::RequestInfo()
{
	if ( !steamapicontext->SteamMatchmakingServers() )
		return;

	if ( m_iRequestRetry == 0 )
	{
		// reset the time at which we auto-refresh
		m_iRequestRetry = system()->GetTimeMillis() + RETRY_TIME;
		if ( m_hPingQuery != HSERVERQUERY_INVALID )
			steamapicontext->SteamMatchmakingServers()->CancelServerQuery( m_hPingQuery );
		m_hPingQuery = steamapicontext->SteamMatchmakingServers()->PingServer( m_Server.m_NetAdr.GetIP(), m_Server.m_NetAdr.GetQueryPort(), this );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Called every frame, handles resending network messages
//-----------------------------------------------------------------------------
void CDialogGameInfo::OnTick()
{
	// check to see if we should perform an auto-refresh
	if ( m_iRequestRetry && m_iRequestRetry < system()->GetTimeMillis() )
	{
		m_iRequestRetry = 0;
		RequestInfo();
	}
}

//-----------------------------------------------------------------------------
// Purpose: called when the server has successfully responded
//-----------------------------------------------------------------------------
void CDialogGameInfo::ServerResponded( gameserveritem_t &server )
{
	if( m_Server.m_NetAdr.GetQueryPort() &&
		m_Server.m_NetAdr.GetQueryPort() != server.m_NetAdr.GetQueryPort() )
	{
		return; // this is not the guy we talked about
	}

	uint16 connectionPort = m_Server.m_NetAdr.GetConnectionPort();

	// FIXME(johns): This is a workaround for a steam bug, where it inproperly reads signed bytes out of the
	//               message. Once the upstream fix makes it into our SteamSDK, this block can be removed.
	server.m_nPlayers    = (uint8)(int8)server.m_nPlayers;
	server.m_nBotPlayers = (uint8)(int8)server.m_nBotPlayers;
	server.m_nMaxPlayers = (uint8)(int8)server.m_nMaxPlayers;

	m_hPingQuery = HSERVERQUERY_INVALID;
	m_Server = server;

	// Preserve our connection port, since we may be querying the sourceTV port but getting a response for the real
	// server. This is a limitation of the steam Matchmaking API where it doesn't properly send us a sourcetv response
	// but instead the main server's response (unless we're connecting to a proxy, THEN we get the sourcetv response!)
	m_Server.m_NetAdr.SetConnectionPort( connectionPort );

	if ( m_bConnecting )
	{
		ConnectToServer();
	}
	else if ( m_pAutoRetry->IsSelected() && server.m_nPlayers < server.m_nMaxPlayers )
	{
		// there is a slot free, we can join

		// make the sound
		surface()->PlaySound("Servers/game_ready.wav");

		// flash this window
		FlashWindow();

		// if it's set, connect right away
		if (m_pAutoRetryJoin->IsSelected())
		{
			ConnectToServer();
		}
	}
	else
	{
		SendPlayerQuery( server.m_NetAdr.GetIP(), server.m_NetAdr.GetQueryPort() );
	}

	m_bServerNotResponding = false;

	InvalidateLayout();
	Repaint();
}


//-----------------------------------------------------------------------------
// Purpose: called when a server response has timed out
//-----------------------------------------------------------------------------
void CDialogGameInfo::ServerFailedToRespond()
{
	// the server didn't respond, mark that in the UI
	// only mark if we haven't ever received a response
	if ( !m_Server.m_bHadSuccessfulResponse )
	{
		m_bServerNotResponding = true;
	}

	InvalidateLayout();
	Repaint();
}



//-----------------------------------------------------------------------------
// Purpose: Constructs a command to send a running game to connect to a server,
// based on the server type
//
// TODO it would be nice to push this logic into the IRunGameEngine interface; that
// way we could ask the engine itself to construct arguments in ways that fit.
// Might be worth the effort as we start to add more engines.
//-----------------------------------------------------------------------------
void CDialogGameInfo::ApplyConnectCommand( const gameserveritem_t &server )
{
	char command[ 256 ];
	// set the server password, if any
	if ( m_szPassword[0] )
	{
		Q_snprintf( command, Q_ARRAYSIZE( command ), "password \"%s\"\n", m_szPassword );
		g_pRunGameEngine->AddTextCommand( command );
	}
	// send engine command to change servers
	Q_snprintf( command, Q_ARRAYSIZE( command ), "connect %s %s\n", server.m_NetAdr.GetConnectionAddressString(), m_sConnectCode.String() );
	g_pRunGameEngine->AddTextCommand( command );
}


//-----------------------------------------------------------------------------
// Purpose: Constructs game options to use when running a game to connect to a server
//-----------------------------------------------------------------------------
void CDialogGameInfo::ConstructConnectArgs( char *pchOptions, int cchOptions, const gameserveritem_t &server )
{
	Q_snprintf( pchOptions, cchOptions, " +connect %s", server.m_NetAdr.GetConnectionAddressString() );
	if ( m_szPassword[0] )
	{
		Q_strcat( pchOptions, " +password \"", cchOptions );
		Q_strcat( pchOptions, m_szPassword, cchOptions );
		Q_strcat( pchOptions, "\"", cchOptions );
	}
}


//-----------------------------------------------------------------------------
// Purpose: Connects to the server
//-----------------------------------------------------------------------------
void CDialogGameInfo::ConnectToServer()
{
	m_bConnecting = false;

	// check VAC status
	if ( m_Server.m_bSecure && ServerBrowser().IsVACBannedFromGame( m_Server.m_nAppID ) )
	{
		// refuse the user
		CVACBannedConnRefusedDialog *pDlg = new CVACBannedConnRefusedDialog( GetVParent(), "VACBannedConnRefusedDialog" );
		pDlg->Activate();
		Close();
		return;
	}


	// check to see if we need a password
	if ( m_Server.m_bPassword && !m_szPassword[0] )
	{
		CDialogServerPassword *box = new CDialogServerPassword(this);
		box->AddActionSignalTarget(this);
		box->Activate( m_Server.GetName(), 0 );
		return;
	}

	// check the player count
	if ( m_Server.m_nPlayers >= m_Server.m_nMaxPlayers )
	{
		// mark why we cannot connect
		m_bServerFull = true;
		// give them access to auto-retry options
		m_bShowAutoRetryToggle = true;
		InvalidateLayout();
		return;
	}

	// tell the engine to connect
	const char *gameDir = m_Server.m_szGameDir;
	if (g_pRunGameEngine->IsRunning())
	{
		ApplyConnectCommand( m_Server );
	}
	else
	{
		char connectArgs[256];
		ConstructConnectArgs( connectArgs, Q_ARRAYSIZE( connectArgs ), m_Server );
		
		if ( ( m_Server.m_bSecure && JoiningSecureServerCall() )|| !m_Server.m_bSecure )
		{
			switch ( g_pRunGameEngine->RunEngine( m_Server.m_nAppID, gameDir, connectArgs ) )
			{
			case IRunGameEngine::k_ERunResultModNotInstalled:
				{
				MessageBox *dlg = new MessageBox( "#ServerBrowser_GameInfoTitle", "#ServerBrowser_ModNotInstalled" );
				dlg->DoModal();
				SetVisible(false);
				return;
				}
				break;
			case IRunGameEngine::k_ERunResultAppNotFound:
				{
				MessageBox *dlg = new MessageBox( "#ServerBrowser_GameInfoTitle", "#ServerBrowser_AppNotFound" );
				dlg->DoModal();
				SetVisible(false);
				return;
				}
				break;
			case IRunGameEngine::k_ERunResultNotInitialized:
				{
				MessageBox *dlg = new MessageBox( "#ServerBrowser_GameInfoTitle", "#ServerBrowser_NotInitialized" );
				dlg->DoModal();
				SetVisible(false);
				return;
				}
				break;
			case IRunGameEngine::k_ERunResultOkay:
			default:
				break;
			};
		}
	}

	// close this dialog
	PostMessage(this, new KeyValues("Close"));
}

//-----------------------------------------------------------------------------
// Purpose: called when the current refresh list is complete
//-----------------------------------------------------------------------------
void CDialogGameInfo::RefreshComplete( EMatchMakingServerResponse response )
{
}

//-----------------------------------------------------------------------------
// Purpose: handles response from the get password dialog
//-----------------------------------------------------------------------------
void CDialogGameInfo::OnJoinServerWithPassword(const char *password)
{
	// copy out the password
	Q_strncpy(m_szPassword, password, sizeof(m_szPassword));

	// retry connecting to the server again
	OnConnect();
}

//-----------------------------------------------------------------------------
// Purpose: player list received
//-----------------------------------------------------------------------------
void CDialogGameInfo::ClearPlayerList()
{
	m_pPlayerList->DeleteAllItems();
	Repaint();
}

//-----------------------------------------------------------------------------
// Purpose: on individual player added
//-----------------------------------------------------------------------------
void CDialogGameInfo::AddPlayerToList(const char *playerName, int score, float timePlayedSeconds)
{
	if ( m_bPlayerListUpdatePending )
	{
		m_bPlayerListUpdatePending = false;
		m_pPlayerList->RemoveAll();
	}

	KeyValues *player = new KeyValues("player");
	player->SetString("PlayerName", playerName);
	player->SetInt("Score", score);
	player->SetInt("TimeSec", (int)timePlayedSeconds);
	
	// construct a time string
	int seconds = (int)timePlayedSeconds;
	int minutes = seconds / 60;
	int hours = minutes / 60;
	seconds %= 60;
	minutes %= 60;
	char buf[64];
	buf[0] = 0;
	if (hours)
	{
		Q_snprintf(buf, sizeof(buf), "%dh %dm %ds", hours, minutes, seconds);	
	}
	else if (minutes)
	{
		Q_snprintf(buf, sizeof(buf), "%dm %ds", minutes, seconds);	
	}
	else
	{
		Q_snprintf(buf, sizeof(buf), "%ds", seconds);	
	}
	player->SetString("Time", buf);
	
	m_pPlayerList->AddItem(player, 0, false, true);
	player->deleteThis();
}

//-----------------------------------------------------------------------------
// Purpose: Sorting function for time column
//-----------------------------------------------------------------------------
int CDialogGameInfo::PlayerTimeColumnSortFunc(ListPanel *pPanel, const ListPanelItem &p1, const ListPanelItem &p2)
{
	int p1time = p1.kv->GetInt("TimeSec");
	int p2time = p2.kv->GetInt("TimeSec");

	if (p1time > p2time)
		return -1;
	if (p1time < p2time)
		return 1;

	return 0;
}