//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:  Displays a leaderboard
//
//=============================================================================//

#include "leaderboarddialog.h"
#include "vgui_controls/Label.h"
#include "vgui/ILocalize.h"
#include "hl2orange.spa.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

#define NUM_ROWS_PER_QUERY	100

CLeaderboardDialog *g_pLeaderboardDialog;

//----------------------------------------------------------
// CLeaderboardDialog
//----------------------------------------------------------
CLeaderboardDialog::CLeaderboardDialog( vgui::Panel *pParent ) : BaseClass( pParent, "LeaderboardDialog" )
{
	g_pLeaderboardDialog = this;
	m_iBaseRank = 0;
	m_iActiveRank = 0;
	m_iMaxRank = 0;
	m_cColumns = 0;
	m_iRangeBase = 0;
#if defined( _X360 )
	m_pStats = NULL;
#endif

	m_pProgressBg		= new vgui::Panel( this, "ProgressBg" );
	m_pProgressBar		= new vgui::Panel( this, "ProgressBar" );
	m_pProgressPercent	= new vgui::Label( this, "ProgressPercent", "" );
	m_pNumbering		= new vgui::Label( this, "Numbering", "" ); 
	m_pUpArrow			= new vgui::Label( this, "UpArrow", "" );
	m_pDownArrow		= new vgui::Label( this, "DownArrow", "" );
	m_pBestMoments		= new vgui::Label( this, "BestMoments", "" );
} 

CLeaderboardDialog::~CLeaderboardDialog()
{
	CleanupStats();

	delete m_pProgressBg;
	delete m_pProgressBar;
	delete m_pProgressPercent;
	delete m_pNumbering;
	delete m_pUpArrow;
	delete m_pDownArrow;
}

//----------------------------------------------------------
// Clean up the stats array
//----------------------------------------------------------
void CLeaderboardDialog::CleanupStats()
{
#if defined( _X360 )
	if ( m_pStats )
	{
		delete [] m_pStats;
		m_pStats = NULL;
	}
#endif
}

//----------------------------------------------------------
// Position the dialogs elements
//----------------------------------------------------------
void CLeaderboardDialog::PerformLayout( void )
{
	BaseClass::PerformLayout();

	if ( m_cColumns )
	{
		int x, y, wide, tall;
		m_pProgressBg->GetBounds( x, y, wide, tall );

		int columnWide = wide / m_cColumns;
		int lockedColumns = m_Menu.GetFirstUnlockedColumnIndex();
		int visibleColumns = m_Menu.GetVisibleColumnCount() - lockedColumns;
		int iColumn = m_Menu.GetActiveColumnIndex() - lockedColumns;

		if ( iColumn < 0 )
		{
			iColumn = 0;
		}
		else if ( iColumn < m_iRangeBase )
		{
			m_iRangeBase = iColumn;
		}
		else if ( iColumn >= m_iRangeBase + visibleColumns )
		{
			m_iRangeBase = iColumn - visibleColumns + 1;
		}

		m_pProgressBg->SetBounds( x, y, columnWide * m_cColumns, tall );
		m_pProgressBar->SetBounds( x + columnWide * m_iRangeBase, y, columnWide * visibleColumns, tall );
	}
	else
	{
		m_pProgressBg->SetVisible( false );
		m_pProgressBar->SetVisible( false );
	}

	int menux, menuy;
	m_Menu.GetPos( menux, menuy );

	// Do a perform layout on the menu so we get the correct height now
	m_Menu.InvalidateLayout( true, false );

	m_pNumbering->SizeToContents();

	wchar_t wszNumbering[64];
	wchar_t *wzNumberingFmt = g_pVGuiLocalize->Find( "#GameUI_Achievement_Menu_Range" );
	wchar_t wzActiveItem[8];
	wchar_t wzTotal[8];

	int iActive = m_iBaseRank + m_Menu.GetActiveItemIndex();
	if ( iActive < 0 )
	{
		iActive = 0;
	}
	V_snwprintf( wzActiveItem, ARRAYSIZE( wzActiveItem ), L"%d", iActive );
	V_snwprintf( wzTotal, ARRAYSIZE( wzTotal ), L"%d", m_iMaxRank );
	g_pVGuiLocalize->ConstructString( wszNumbering, sizeof( wszNumbering ), wzNumberingFmt, 2, wzActiveItem, wzTotal );
	m_pNumbering->SetText( wszNumbering );
	m_pNumbering->SetWide( GetWide() );

	MoveToCenterOfScreen();
}

//----------------------------------------------------------
// 
//----------------------------------------------------------
void CLeaderboardDialog::ApplySettings( KeyValues *pResourceData )
{
	BaseClass::ApplySettings( pResourceData );

	m_KeyRepeat.SetKeyRepeatTime( KEY_XBUTTON_DOWN, 0.05f );
	m_KeyRepeat.SetKeyRepeatTime( KEY_XSTICK1_DOWN, 0.05f );
	m_KeyRepeat.SetKeyRepeatTime( KEY_XBUTTON_UP, 0.05f );
	m_KeyRepeat.SetKeyRepeatTime( KEY_XSTICK1_UP, 0.05f );
}

//----------------------------------------------------------
// 
//----------------------------------------------------------
void CLeaderboardDialog::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );

	m_pProgressBg->SetBgColor( Color( 200, 184, 151, 255 ) );
	m_pProgressBar->SetBgColor( Color( 179, 82, 22, 255 ) );
	m_pNumbering->SetFgColor( pScheme->GetColor( "MatchmakingMenuItemDescriptionColor", Color( 64, 64, 64, 255 ) ) );
	m_pBestMoments->SetFgColor( pScheme->GetColor( "MatchmakingMenuItemDescriptionColor", Color( 64, 64, 64, 255 ) ) );
}

//----------------------------------------------------------
// 
//----------------------------------------------------------
void CLeaderboardDialog::OnCommand( const char *pCommand )
{
	if ( !Q_stricmp( pCommand, "CenterOnPlayer" ) )
	{
		if ( GetPlayerStats( -1 ) == 0 )
		{
			// Player isn't on the board, just start at rank 1
			GetPlayerStats( 1 );
		}
	}
	else if ( !Q_stricmp( pCommand, "Friends" ) )
	{
		GetPlayerStats( -1, true );
	}

	BaseClass::OnCommand( pCommand );
}

//----------------------------------------------------------
// 
//----------------------------------------------------------
void CLeaderboardDialog::AddLeaderboardEntry( const char **ppEntries, int ct )
{
	m_Menu.AddSectionedItem( ppEntries, ct );
}

//----------------------------------------------------------
// Get some portion of the leaderboard. This should ideally live
// in the client, since it's very mod-specific
//----------------------------------------------------------
bool CLeaderboardDialog::GetPlayerStats( int rank, bool bFriends )
{
#if defined _X360
	HANDLE handle;

	// Retrieve the necessary buffer size
	DWORD cbResults = 0;

	bool bRanked = false;
	const char *pName = GetName();
	if ( !Q_stricmp( pName, "LeaderboardDialog_Ranked" ) )
	{
		bRanked = true;
	}

	XUSER_STATS_SPEC spec;
	if ( !bRanked )
	{
		spec.dwViewId        = STATS_VIEW_PLAYER_MAX_UNRANKED;
		spec.dwNumColumnIds  = 15;
		spec.rgwColumnIds[0] = STATS_COLUMN_PLAYER_MAX_UNRANKED_POINTS_SCORED;
		spec.rgwColumnIds[1] = STATS_COLUMN_PLAYER_MAX_UNRANKED_KILLS;
		spec.rgwColumnIds[2] = STATS_COLUMN_PLAYER_MAX_UNRANKED_POINTS_CAPPED;
		spec.rgwColumnIds[3] = STATS_COLUMN_PLAYER_MAX_UNRANKED_POINT_DEFENSES;
 		spec.rgwColumnIds[4] = STATS_COLUMN_PLAYER_MAX_UNRANKED_DOMINATIONS;
 		spec.rgwColumnIds[5] = STATS_COLUMN_PLAYER_MAX_UNRANKED_REVENGE;
		spec.rgwColumnIds[6] = STATS_COLUMN_PLAYER_MAX_UNRANKED_BUILDINGS_DESTROYED;
		spec.rgwColumnIds[7] = STATS_COLUMN_PLAYER_MAX_UNRANKED_HEADSHOTS;
		spec.rgwColumnIds[8] = STATS_COLUMN_PLAYER_MAX_UNRANKED_HEALTH_POINTS_HEALED;
		spec.rgwColumnIds[9] = STATS_COLUMN_PLAYER_MAX_UNRANKED_INVULNS;
		spec.rgwColumnIds[10] = STATS_COLUMN_PLAYER_MAX_UNRANKED_KILL_ASSISTS;
		spec.rgwColumnIds[11] = STATS_COLUMN_PLAYER_MAX_UNRANKED_BACKSTABS;
		spec.rgwColumnIds[12] = STATS_COLUMN_PLAYER_MAX_UNRANKED_HEALTH_POINTS_LEACHED;
		spec.rgwColumnIds[13] = STATS_COLUMN_PLAYER_MAX_UNRANKED_SENTRY_KILLS;
		spec.rgwColumnIds[14] = STATS_COLUMN_PLAYER_MAX_UNRANKED_TELEPORTS;
		m_cColumns = 15;
	}
	else
	{
		spec.dwViewId          = STATS_VIEW_PLAYER_MAX_RANKED;
		spec.dwNumColumnIds    = 1;
		spec.rgwColumnIds[ 0 ] = STATS_COLUMN_PLAYER_MAX_RANKED_POINTS_SCORED;

		// set to zero to hide the progress bar
		m_cColumns = 0;
	}

	DWORD ret;
	XUID xuid = 0u;
	XUID xuidFriends[NUM_ROWS_PER_QUERY];
	int xuidCount = 1;

	if ( !bFriends )
	{
		if ( rank == -1 )
		{
			// Center on the player's xuid
			XUserGetXUID( XBX_GetPrimaryUserId(), &xuid );

			ret = XUserCreateStatsEnumeratorByXuid( 
				0,
				xuid,
				NUM_ROWS_PER_QUERY,
				1,
				&spec,
				&cbResults,
				&handle );
		}
		else
		{
			// Start at the requested rank
			ret = XUserCreateStatsEnumeratorByRank( 
				0,
				rank,
				NUM_ROWS_PER_QUERY,
				1,
				&spec,
				&cbResults,
				&handle );
		}

		if( ret != ERROR_SUCCESS )
		{
			Warning( "Error getting stats\n" );
			return false;
		}

		// Allocate the buffer
		CleanupStats();
		m_pStats = ( XUSER_STATS_READ_RESULTS* ) new char[cbResults];

		DWORD cpReturned;
		ret = XEnumerate( handle, m_pStats, cbResults, &cpReturned, NULL );
	}
	else
	{
		// Get Friends leaderboard
		int id = XBX_GetPrimaryUserId();
		ret = XFriendsCreateEnumerator( id, 0, 5, &cbResults, &handle );

		if ( ret != ERROR_SUCCESS )
		{
			Warning( "Error getting friends list\n" );
			return false;
		}

		// Allocate the buffer
		XONLINE_FRIEND *pFriends = ( XONLINE_FRIEND* ) new char[cbResults];

		DWORD cpReturned;
		ret = XEnumerate( handle, pFriends, cbResults, &cpReturned, NULL );
		if( ret != ERROR_SUCCESS )
		{
			delete pFriends;
			return false;
		}

		for ( uint i = 0; i < cpReturned; ++i )
		{
			xuidFriends[i] = pFriends[i].xuid;
		}

		// Allocate the buffer
		CleanupStats();
		m_pStats = ( XUSER_STATS_READ_RESULTS* ) new char[cbResults];

		ret = XUserReadStats( 0, xuidCount, xuidFriends, 1, &spec, &cbResults, m_pStats, NULL );
	}

	if( ret == ERROR_SUCCESS )
	{
		const char *pEntries[32];
		char pRowBuffer[MAX_PATH];
		char pBuffers[32][MAX_PATH];

		m_Menu.ClearItems();
		m_iMaxRank = m_pStats->pViews[0].dwTotalViewRows;

		// Did this search return any rows?
		if ( m_pStats->pViews[0].dwNumRows == 0 )
			return false;

		for ( uint i = 0; i < m_pStats->pViews[0].dwNumRows; ++i )
		{
			XUSER_STATS_ROW &row = m_pStats->pViews[0].pRows[i];

			// Save off the first rank in this set of entries
			if ( i == 0 && m_iBaseRank == 0 )
			{
				m_iBaseRank = row.dwRank;
			}

			pEntries[0] = itoa( row.dwRank, pRowBuffer, 10 );
			pEntries[1] = row.szGamertag;
			for ( uint j = 0; j < row.dwNumColumns; ++j )
			{
				XUSER_STATS_COLUMN &col = m_pStats->pViews[0].pRows[i].pColumns[j];
				pEntries[j+2] = itoa( col.Value.nData, pBuffers[j], 10 );
			}

			AddLeaderboardEntry( pEntries, row.dwNumColumns + 2 );

			if ( rank == -1 && row.xuid == xuid )
			{
				m_Menu.SetFocus( i );
				m_iActiveRank = row.dwRank;
			}
		}
	}
	else
	{
		Warning( "Error getting leaderboard stats\n" );
		return false;
	}

	CloseHandle( handle );

	return true;
#endif

	return false;
}

//----------------------------------------------------------
// Determine if a new set of stats needs to be downloaded
// Return true if the update has been handled, false otherwise
//----------------------------------------------------------
void CLeaderboardDialog::UpdateLeaderboard( int iNewRank )
{
	// Clamp the input
	if ( iNewRank < 1 )
	{
		iNewRank = 1;
	}
	else if ( iNewRank > m_iMaxRank )
	{
		iNewRank = m_iMaxRank;
	}

	// No action necessary?
	if ( iNewRank == m_iActiveRank )
		return;

	int nInterval = iNewRank - m_iActiveRank;
	int iNewActiveItemIndex = m_Menu.GetActiveItemIndex() + nInterval;

	// Set these "new" values to the current values - they will be conditionally updated.
	int iNewBaseRank = m_iBaseRank;
	int iNewBaseItemIndex = m_Menu.GetBaseRowIndex();
	int nVisibleItems = m_Menu.GetVisibleItemCount();
	int nHiddenItems = NUM_ROWS_PER_QUERY - nVisibleItems;

	// Are we outside the visible range of the menu?
	if ( iNewActiveItemIndex < iNewBaseItemIndex )
	{
		// Do we need to grab another set of columns?
		if ( iNewRank < m_iBaseRank )
		{
			iNewBaseRank = iNewRank - nHiddenItems;
			if ( iNewBaseRank < 1 )
			{
				iNewBaseRank = 1;
			}

			if ( !GetPlayerStats( iNewBaseRank ) )
			{
				// Failed to load player stats, don't change the current index
				return;
			}

			m_iBaseRank = iNewBaseRank;
		}

		int nBaseToActiveInterval = iNewRank - m_iBaseRank;

		// Since we shifted the menu down, both base and active item are at the first visible menu item
		iNewActiveItemIndex = nBaseToActiveInterval;
		iNewBaseItemIndex = nBaseToActiveInterval;
	}
 	else if ( iNewActiveItemIndex >= m_Menu.GetBaseRowIndex() + nVisibleItems )
 	{
		int nHiddenItems = NUM_ROWS_PER_QUERY - nVisibleItems;
		int iTopRank = iNewRank + nHiddenItems;
		if ( iTopRank > m_iMaxRank )
		{
			iTopRank = m_iMaxRank;
		}


		// Do we need to grab another set of columns?
		if ( iNewRank >= m_iBaseRank + NUM_ROWS_PER_QUERY )
		{
			iNewBaseRank = iTopRank - NUM_ROWS_PER_QUERY + 1;
			if ( !GetPlayerStats( iNewBaseRank ) )
			{
				// Failed to load player stats, don't change the current index
				return;
			}
			m_iBaseRank = iNewBaseRank;
		}

		int nBaseToActiveInterval = iNewRank - m_iBaseRank;

		iNewActiveItemIndex = nBaseToActiveInterval;
		iNewBaseItemIndex = iNewActiveItemIndex - nVisibleItems + 1;
 	}

	// Set all the new variables - must set base index before active index.
	m_iActiveRank = iNewRank;
	m_Menu.SetBaseRowIndex( iNewBaseItemIndex );
	m_Menu.SetFocus( iNewActiveItemIndex );

	InvalidateLayout();
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CLeaderboardDialog::HandleKeyRepeated( vgui::KeyCode code )
{
	OnKeyCodePressed( code );
}

//-----------------------------------------------------------------
// Purpose: Send key presses to the dialog's menu
//-----------------------------------------------------------------
void CLeaderboardDialog::OnKeyCodePressed( vgui::KeyCode code )
{

	switch( code )
	{
	case KEY_XBUTTON_A:
	case STEAMCONTROLLER_A:
#ifdef _X360
		{
			int idx = m_Menu.GetActiveItemIndex();
			if ( m_pStats && idx < (int)m_pStats->pViews[0].dwNumRows )
			{
				XUSER_STATS_ROW &row = m_pStats->pViews[0].pRows[idx];
				XShowGamerCardUI( XBX_GetPrimaryUserId(), row.xuid );		
			}
		}
#endif
		break;

	case KEY_XBUTTON_Y:
	case STEAMCONTROLLER_Y:
		break;

	case KEY_XSTICK1_DOWN:
	case KEY_XBUTTON_DOWN:
	case STEAMCONTROLLER_DPAD_DOWN:
		m_KeyRepeat.KeyDown( code );
		UpdateLeaderboard( m_iActiveRank + 1 );
		break;

	case KEY_XSTICK1_UP:
	case KEY_XBUTTON_UP:
	case STEAMCONTROLLER_DPAD_UP:
		m_KeyRepeat.KeyDown( code );
		UpdateLeaderboard( m_iActiveRank - 1 );
		break;

	case KEY_XBUTTON_LEFT_SHOULDER:
		UpdateLeaderboard( 1 );
		break;

	case KEY_XBUTTON_RIGHT_SHOULDER:
		OnCommand( "CenterOnPlayer" );
		break;

		// Disabled until friends enumeration works
// 	case KEY_XBUTTON_RIGHT_SHOULDER:
// 		OnCommand( "Friends" );
// 		break;

	default:
		m_KeyRepeat.KeyDown( code );
		BaseClass::OnKeyCodePressed( code );
		break;
	}

	// Invalidate layout when scrolling through columns
	switch( code )
	{
	case KEY_XSTICK1_LEFT:
	case KEY_XBUTTON_LEFT:
	case STEAMCONTROLLER_DPAD_LEFT:
	case KEY_XSTICK1_RIGHT:
	case KEY_XBUTTON_RIGHT:
	case STEAMCONTROLLER_DPAD_RIGHT:
		InvalidateLayout();
		break;
	}
}


CON_COMMAND( mm_add_item, "Add a stats item" )
{
	if ( args.ArgC() > 1 )
	{
		int ct = atoi( args[1] );
		const char *pEntries[32];
		for ( int i = 0; i < ct; ++i )
		{
			pEntries[i] = args[i+2];
		}
		g_pLeaderboardDialog->AddLeaderboardEntry( pEntries, ct );
	}
}