//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#include "cbase.h"

#include "tf_pvp_rank_panel.h"
#include "tf_matchmaking_shared.h"
#include "basemodel_panel.h"
#include "vgui_controls/ProgressBar.h"
#include "tf_ladder_data.h"
#include "iclientmode.h"
#include <vgui_controls/AnimationController.h>
#include "tf_controls.h"
#include "vgui/ISurface.h"
#include "animation.h"
#include "clientmode_tf.h"
#include "vgui/IInput.h"
#include "vgui_controls/MenuItem.h"
#include "tf_gc_client.h"
#include "tf_xp_source.h"

using namespace vgui;

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

#ifdef STAGING_ONLY
	extern ConVar tf_test_pvp_rank_xp_change;
#endif

ConVar tf_xp_breakdown_interval( "tf_xp_breakdown_interval", "1.85", FCVAR_DEVELOPMENTONLY );
ConVar tf_xp_breakdown_lifetime( "tf_xp_breakdown_lifetime", "3.5", FCVAR_DEVELOPMENTONLY );

extern const char *s_pszMatchGroups[];

CPvPRankPanel::XPState_t::XPState_t( EMatchGroup eMatchGroup )
	: m_nStartXP( 0u )
	, m_nTargetXP( 0u )
	, m_nActualXP( 0u )
	, m_bCurrentDeltaViewed( true )
	, m_eMatchGroup( eMatchGroup )
{
	Assert( m_eMatchGroup != k_nMatchGroup_Invalid );

	// Default to level 1's starting XP value
	const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eMatchGroup );
	if ( pMatchDesc && pMatchDesc->m_pProgressionDesc )
	{
		auto level = pMatchDesc->m_pProgressionDesc->GetLevelByNumber( 1 );
		m_nStartXP = level.m_nStartXP;
		m_nActualXP = m_nStartXP;
		m_nTargetXP = m_nStartXP;
	}

	ListenForGameEvent( "experience_changed" );
	ListenForGameEvent( "server_spawn" );
}

void CPvPRankPanel::XPState_t::FireGameEvent( IGameEvent *pEvent )
{
	if ( FStrEq( pEvent->GetName(), "experience_changed" ) ) // For changing tf_progression_set_xp_to_level
	{
		UpdateXP( false );
	}
	else if ( FStrEq( pEvent->GetName(), "server_spawn" ) && GTFGCClientSystem()->BHaveLiveMatch()
		&& GTFGCClientSystem()->GetLiveMatchGroup() == m_eMatchGroup )
	{
		UpdateXP( false );
		// Acknowledge any outstanding XP sources when we start a match.  It looks really weird when
		// you see old match xp sources at the end of a match.
		GTFGCClientSystem()->AcknowledgePendingXPSources( m_eMatchGroup );
	}
}

void CPvPRankPanel::XPState_t::UpdateXP( bool bInitial )
{
	uint32 nStartXP = 0u;
	uint32 nNewXP = 0u;

	const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eMatchGroup );
	// Should only be creating this object for matches that have progressions
	Assert( pMatchDesc && pMatchDesc->m_pProgressionDesc );
	if ( pMatchDesc && pMatchDesc->m_pProgressionDesc )
	{
		// Default to level 1's lowest XP value
		auto level = pMatchDesc->m_pProgressionDesc->GetLevelByNumber( 1 );
		nNewXP = level.m_nStartXP;
		nStartXP = level.m_nStartXP;

		if ( steamapicontext && steamapicontext->SteamUser() )
		{
			nStartXP = pMatchDesc->m_pProgressionDesc->GetLocalPlayerLastAckdExperience();
			// Get the actual user's XP
			nNewXP = pMatchDesc->m_pProgressionDesc->GetPlayerExperienceBySteamID( steamapicontext->SteamUser()->GetSteamID() );
		}
	}

	// If there's no change, don't do anything
	if ( nNewXP != m_nActualXP || bInitial )
	{
		m_nActualXP = nNewXP;
		m_nStartXP = nStartXP;
		m_bCurrentDeltaViewed = false;
	}

	if ( bInitial )
	{
		m_progressTimer.Start( 1.f );
		m_bCurrentDeltaViewed = true;
		m_nTargetXP = m_nStartXP;
	}
}

uint32 CPvPRankPanel::XPState_t::GetCurrentXP() const
{
	float flTimeProgress = 0.f;
	if ( m_progressTimer.HasStarted() )
	{
		flTimeProgress = Clamp( m_progressTimer.GetElapsedTime(), 0.f, m_progressTimer.GetCountdownDuration() );
		flTimeProgress = Gain( flTimeProgress / m_progressTimer.GetCountdownDuration(), 0.9f );
	}

	return RemapValClamped( flTimeProgress, 0.f, 1.f, m_nStartXP, m_nTargetXP );
}

bool CPvPRankPanel::XPState_t::BeginXPDeltaLerp()
{
	if ( !m_bCurrentDeltaViewed )
	{
		m_bCurrentDeltaViewed = true;
		// Change our target
		m_nTargetXP = m_nActualXP;
		m_progressTimer.Start( 5.f );

		return true;
	}

	return false;
}

void CPvPRankPanel::XPState_t::SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent ) 
{
	if ( pObject->GetTypeID() == CSOTFLadderData::k_nTypeID )
	{
		CSOTFLadderData* pLadderObject = (CSOTFLadderData*)pObject;
		if( (EMatchGroup)pLadderObject->Obj().match_group() != m_eMatchGroup )
			return;

		// We'll get a eSOCacheEvent_Incremental when we're actually creating the 
		// first CSOTFLadderData object.  We dont want that to come through as
		// "initializing" because it'll skip the leveling effects
		UpdateXP( eEvent == eSOCacheEvent_ListenerAdded );
	}

	if ( pObject->GetTypeID() == CXPSource::k_nTypeID )
	{
		CXPSource *pXPSource = (CXPSource*)pObject;
		if ( pXPSource->Obj().match_group() == m_eMatchGroup )
		{
			UpdateXP( false );
		}
	}
}

void CPvPRankPanel::XPState_t::SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
{
	if ( pObject->GetTypeID() == CSOTFLadderData::k_nTypeID )
	{
		CSOTFLadderData* pLadderObject = (CSOTFLadderData*)pObject;
		if( (EMatchGroup)pLadderObject->Obj().match_group() != m_eMatchGroup )
			return;

		// If the GC comes on after we've already subscribed to the cache,
		// eSOCacheEvent_Subscribed when the object is created.  This one
		// we want to skip the effects.
		UpdateXP( eEvent == eSOCacheEvent_Subscribed );
	}

	if ( pObject->GetTypeID() == CXPSource::k_nTypeID )
	{
		CXPSource *pXPSource = (CXPSource*)pObject;
		if ( pXPSource->Obj().match_group() == m_eMatchGroup )
		{
			UpdateXP( false );
		}
	}
}

class CMiniPvPRankPanel : public CPvPRankPanel
{
public:
	CMiniPvPRankPanel( Panel* pParent, const char* pszPanelName ) : CPvPRankPanel( pParent, pszPanelName ) 
	{} 

private:
	virtual KeyValues* GetConditions() const OVERRIDE
	{
		KeyValues* pConditions = new KeyValues( "conditions" );
		pConditions->AddSubKey( new KeyValues( "if_mini" ) );

		return pConditions;
	}
};

class CXPSourcePanel : public vgui::EditablePanel
{
public:
	DECLARE_CLASS_SIMPLE( CXPSourcePanel , vgui::EditablePanel);
	CXPSourcePanel( Panel *pParent, const char* panelName, const CMsgTFXPSource& source )
		: BaseClass( pParent, panelName )
		, m_source( source )
	{}

	virtual void ApplySchemeSettings( vgui::IScheme *pScheme ) OVERRIDE
	{
		BaseClass::ApplySchemeSettings( pScheme );

		LoadControlSettings( "resource/ui/XPSourcePanel.res" );
	}

	virtual void PerformLayout() OVERRIDE
	{
		BaseClass::PerformLayout();

		static wchar_t wszOutString[ 128 ];
		wchar_t wszCount[ 16 ];
		_snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", m_source.amount() );
		const wchar_t *wpszFormat = g_pVGuiLocalize->Find( g_XPSourceDefs[ m_source.type() ].m_pszFormattingLocToken );
		g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 2, g_pVGuiLocalize->Find( g_XPSourceDefs[ m_source.type() ].m_pszTypeLocToken ), wszCount );

		SetDialogVariable( "source", wszOutString );
	}

protected:
	CMsgTFXPSource m_source;
};

class CScrollingXPSourcePanel : public CXPSourcePanel
{
public:
	DECLARE_CLASS_SIMPLE( CScrollingXPSourcePanel , CXPSourcePanel );
	CScrollingXPSourcePanel( Panel *pParent, const char* panelName, const CMsgTFXPSource& source, float flStartTime )
		: BaseClass( pParent, panelName, source )
		, m_flStartTime( flStartTime )
		, m_bStarted( false )
	{
		vgui::ivgui()->AddTickSignal( GetVPanel() );

		SetAutoDelete( false );
	}

	virtual ~CScrollingXPSourcePanel()
	{
	}

	virtual void OnTick() OVERRIDE
	{
		BaseClass::OnTick();

		if ( Plat_FloatTime() > m_flStartTime && !m_bStarted )
		{
			m_bStarted = true;
			SetVisible( true );

			// Do starting stuff
			g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( this, m_source.amount() >= 0 ? "XPSourceShow_Positive" : "XPSourceShow_Negative", false );

			if ( g_XPSourceDefs[ m_source.type() ].m_pszSoundName )
			{
				PlaySoundEntry( g_XPSourceDefs[ m_source.type() ].m_pszSoundName );
			}
		}

		SetVisible( m_bStarted );

		if ( Plat_FloatTime() > ( m_flStartTime + tf_xp_breakdown_lifetime.GetFloat() ) )
		{
			// We're done!  Delete ourselves
			MarkForDeletion();
		}
	}

private:

	float m_flStartTime;
	bool m_bStarted;
};

DECLARE_BUILD_FACTORY( CMiniPvPRankPanel );
DECLARE_BUILD_FACTORY( CPvPRankPanel );

CPvPRankPanel::CPvPRankPanel( Panel *parent, const char *panelName )
	: BaseClass( parent, panelName )
	, m_eMatchGroup( k_nMatchGroup_Invalid )
	, m_pProgressionDesc( NULL )
	, m_pContinuousProgressBar( NULL )
	, m_pModelPanel( NULL )
	, m_pXPBar( NULL )
	, m_pBGPanel( NULL )
	, m_nLastLerpXP( 0 )
	, m_nLastSeenLevel( 0 )
	, m_bClicked( false )
{
	ListenForGameEvent( "begin_xp_lerp" );
}

void CPvPRankPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	BaseClass::ApplySchemeSettings( pScheme );

	KeyValues* pConditions = GetConditions();

	LoadControlSettings( GetResFile(), NULL, NULL, pConditions );

	if ( pConditions )
	{
		pConditions->deleteThis();
		pConditions = NULL;
	}

	m_pBGPanel = FindControl< EditablePanel >( "BGPanel", true );
	m_pContinuousProgressBar = FindControl< ContinuousProgressBar >( "ContinuousProgressBar", true );
	m_pXPBar = FindControl< EditablePanel >( "XPBar", true );
	m_pModelPanel = FindControl< CBaseModelPanel >( "RankModel", true );
	m_pModelPanel->AddActionSignalTarget( this );

	m_pModelButton = FindChildByName( "MedalButton", true );

#ifdef STAGING_ONLY
	if ( m_pBGPanel )
	{
		Panel* pDebugButton = m_pBGPanel->FindChildByName( "TestLevelDownButton", true );
		if ( pDebugButton ) { pDebugButton->SetVisible( true ); }
		pDebugButton = m_pBGPanel->FindChildByName( "LevelDebugButton", true );
		if ( pDebugButton ) { pDebugButton->SetVisible( true ); }
		pDebugButton = m_pBGPanel->FindChildByName( "TestLevelUpButton", true );
		if ( pDebugButton ) { pDebugButton->SetVisible( true ); }
	}
#endif
}

void CPvPRankPanel::ApplySettings(KeyValues *inResourceData)
{
	BaseClass::ApplySettings( inResourceData );

	SetMatchGroup( (EMatchGroup)StringFieldToInt( inResourceData->GetString( "matchgroup" ), s_pszMatchGroups, (int)k_nMatchGroup_Count, false ) );
}

void CPvPRankPanel::PerformLayout()
{
	BaseClass::PerformLayout();

	if ( !m_pProgressionDesc )
		return;

	EditablePanel* pStatsContainer = FindControl< EditablePanel >( "Stats", true );
	if ( !pStatsContainer )
		return;

	if ( !m_pModelPanel )
		return;

	if ( !m_pXPBar )
		return;

	ProgressBar* pProgressBar = FindControl< ProgressBar > ( "ProgressBar", true );
	if ( !pProgressBar )
		return;

	if ( !m_pContinuousProgressBar )
		return;

	// Chop up the progress bar into 10th's and use it as a mask overtop
	pProgressBar->SetSegmentInfo( ( pProgressBar->GetWide() / 10 ) - ( ( 9 * 4 ) / 10 ) , 4 );

	const XPState_t& xpstate = GetXPState();
	uint32 nCurrentXP = xpstate.GetCurrentXP();
	const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( nCurrentXP );
	m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur );

	// Update labels
	UpdateControls( xpstate.GetStartXP(), nCurrentXP, levelCur );

	SetMatchStats();
}

void CPvPRankPanel::OnThink()
{
	 BaseClass::OnThink();

	 if ( m_pContinuousProgressBar && m_pXPBar && m_pModelPanel && m_pProgressionDesc && m_pBGPanel )
	 {

		// SUPER HACKS.  I dont have time to figure out popups
		if ( m_pModelButton && vgui::input()->IsMouseDown( MOUSE_LEFT ) )
		{
			int nMouseX, nMouseY; 
			input()->GetCursorPos( nMouseX, nMouseY );
			if ( !m_bClicked && m_pModelButton->IsWithin( nMouseX, nMouseY ) )
			{
				OnCommand( "medal_clicked" );
			}

			m_bClicked = true;
		}
		else
		{
			m_bClicked = false;
		}

		const XPState_t& xpstate = GetXPState();
		uint32 nCurrentXP = xpstate.GetCurrentXP();

		// Check if the last XP we lerp'd to isn't the current XP.  If it's not, then we want to animate over to it.
		if ( m_nLastLerpXP != nCurrentXP )
		{
			uint32 nPrevXP = xpstate.GetStartXP();
			const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( nCurrentXP );

			UpdateControls( nPrevXP, nCurrentXP, levelCur );

			// We only want to do level up effects if we are thinking (visible) when the current XP passes the level boundary
			if ( m_nLastLerpXP != xpstate.GetStartXP() )
			{
				const LevelInfo_t& levelPrevThink = m_pProgressionDesc->GetLevelForExperience( m_nLastLerpXP );
				if ( levelCur.m_nLevelNum > levelPrevThink.m_nLevelNum ) // Level up :)
				{
					PlayLevelUpEffects( levelCur );
				}
				else if ( levelCur.m_nLevelNum < levelPrevThink.m_nLevelNum ) // Level down :(
				{
					PlayLevelDownEffects( levelCur );
				}
			}

			m_nLastLerpXP = nCurrentXP;
		}
		else 
		{
			// Our last lerp XP is caught up with current XP, but our last seen level is not up to date with what
			// our current level actually is.  This can happen if we haven't been thinking, but we did level up somewhere
			// else.  In this case, we need to update our badge.
			const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( nCurrentXP );

			if ( m_nLastSeenLevel != levelCur.m_nLevelNum )
			{
				m_nLastSeenLevel = levelCur.m_nLevelNum;
				m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur );
			}
		}
	 }
}


void CPvPRankPanel::UpdateControls( uint32 nPreviousXP, uint32 nCurrentXP, const LevelInfo_t& levelCurrent )
{
	if ( m_pContinuousProgressBar )
	{
		// Calculate progress bar percentages.  PrevBarProgress is the bar that shows where you started.
		// CurrentBarProgress is the bar that lerps from the start to your actual, current XP
		float flPrevBarProgress		= RemapValClamped( (float)nPreviousXP,	  (float)levelCurrent.m_nStartXP, (float)levelCurrent.m_nEndXP, 0.f, 1.f );
		float flCurrentBarProgress	= RemapValClamped( (float)nCurrentXP, (float)levelCurrent.m_nStartXP, (float)levelCurrent.m_nEndXP, 0.f, 1.f );

		m_pContinuousProgressBar->SetPrevProgress( flPrevBarProgress );
		m_pContinuousProgressBar->SetProgress( flCurrentBarProgress );
	}

	if ( m_pXPBar )
	{
		m_pXPBar->SetDialogVariable( "current_xp", LocalizeNumberWithToken( "TF_Competitive_XP_Current", nCurrentXP ) );

		if ( levelCurrent.m_nLevelNum < m_pProgressionDesc->GetNumLevels() )
		{
			m_pXPBar->SetDialogVariable( "next_level_xp", LocalizeNumberWithToken( "TF_Competitive_XP", levelCurrent.m_nEndXP ) );
		}
		else // Hide the next level XP value at max level
		{
			m_pXPBar->SetDialogVariable( "next_level_xp", "" );
		}

		static wchar_t wszOutString[ 128 ];
		wchar_t wszCount[ 16 ];
		_snwprintf( wszCount, ARRAYSIZE( wszCount ), L"%d", levelCurrent.m_nLevelNum );
		const wchar_t *wpszFormat = g_pVGuiLocalize->Find( m_pProgressionDesc->m_pszLevelToken );
		g_pVGuiLocalize->ConstructString_safe( wszOutString, wpszFormat, 2, wszCount, g_pVGuiLocalize->Find( levelCurrent.m_pszLevelTitle ) );

		m_pXPBar->SetDialogVariable( "level", wszOutString );
	}
}

void CPvPRankPanel::OnCommand( const char *command )
{
	if ( FStrEq( "medal_clicked", command ) )
	{
		// Default effects
		const char *pszSeqName = "click_A";
		const char *pszSoundName = "ui/mm_medal_click.wav";

		// Roll for a crit
		int nRandomRoll = RandomInt( 0, 9 );
		if ( nRandomRoll == 0 )
		{
			// CRIT!
			pszSeqName = "click_B";
			pszSoundName = "MatchMaking.MedalClickRare";
		}
	
		m_pModelPanel->PlaySequence( pszSeqName );
		PlaySoundEntry( pszSoundName );

		EditablePanel* pModelContainer = FindControl< EditablePanel >( "ModelContainer" );
		if ( pModelContainer )
		{
			g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pModelContainer, "PvPRankModelClicked", false);
		}

		return;
	}
	else if ( FStrEq( "begin_xp_lerp", command ) )
	{
		BeginXPLerp();
		return;
	}
	else if ( FStrEq( "update_base_state", command ) )
	{
		UpdateBaseState();
		return;
	}
	BaseClass::OnCommand( command );
}

void CPvPRankPanel::FireGameEvent( IGameEvent *pEvent )
{
	// This is really only for tf_test_pvp_rank_xp_change
	if ( FStrEq( pEvent->GetName(), "begin_xp_lerp" ) )
	{
		BeginXPLerp();
	}
}

void CPvPRankPanel::SetVisible( bool bVisible )
{
	 if ( IsVisible() != bVisible )
	 {
		 UpdateBaseState();
	 }

	 BaseClass::SetVisible( bVisible );
}

int SortXPSources( CXPSource* const* pLeft, CXPSource* const* pRight )
{
	return (*pLeft)->Obj().type() - (*pRight)->Obj().type();
}

void CPvPRankPanel::BeginXPLerp()
{
	XPState_t& xpstate = GetXPState();
	if ( xpstate.BeginXPDeltaLerp() )
	{
		// Play sounds if this is a change
		if ( xpstate.GetTargetXP() > xpstate.GetStartXP() )
		{
			PlaySoundEntry( "MatchMaking.RankProgressTickUp" );
		}
		else if ( xpstate.GetTargetXP() < xpstate.GetStartXP() )
		{
			PlaySoundEntry( "MatchMaking.RankProgressTickDown" );
		}
	}

	if ( steamapicontext && steamapicontext->SteamUser() )
	{
		GCSDK::CGCClientSharedObjectCache *pSOCache = GCClientSystem()->GetSOCache( steamapicontext->SteamUser()->GetSteamID() );

		if ( pSOCache )
		{
			GCSDK::CGCClientSharedObjectTypeCache *pTypeCache = pSOCache->FindTypeCache( CXPSource::k_nTypeID );

			if ( pTypeCache )
			{
				CUtlVector< CXPSource* > vecSources;

				// Grab all the XP sources we want to show
				for ( uint32 i = 0; i < pTypeCache->GetCount(); ++i )
				{
					CXPSource *pXPSource = (CXPSource*)pTypeCache->GetObject( i );

					if ( pXPSource->Obj().match_group() == m_eMatchGroup )
					{
						vecSources.AddToTail( pXPSource );
					}
				}
					
				// Sort them so users get a consistent experience
				vecSources.Sort( &SortXPSources );
					
				// Show the sources
				FOR_EACH_VEC( vecSources, i )
				{
					CXPSource *pXPSource = vecSources[ i ];
					CScrollingXPSourcePanel* pSourcePanel = new CScrollingXPSourcePanel( this, "XPSourcePanel", pXPSource->Obj(), Plat_FloatTime() + ( i * tf_xp_breakdown_interval.GetFloat() ) );
					pSourcePanel->MakeReadyForUse();

					// Offset from the BGPanel.
					pSourcePanel->SetPos( m_pBGPanel->GetXPos() + m_iXPSourceNotificationCenterX - ( pSourcePanel->GetWide() * 0.5f ), m_pBGPanel->GetYPos() + m_pXPBar->GetYPos() + YRES( 22 ) - pSourcePanel->GetTall() );
				}
			}
		}
	}

	GTFGCClientSystem()->AcknowledgePendingXPSources( m_eMatchGroup );
}

void CPvPRankPanel::UpdateBaseState()
{
	if ( !m_pProgressionDesc || m_pModelPanel == NULL )
		return;

	// Set our "last seen" variables so things dont try to interpolate
	m_nLastLerpXP = GetXPState().GetCurrentXP();
	LevelInfo_t levelCur = m_pProgressionDesc->GetLevelForExperience( m_nLastLerpXP );
	m_nLastSeenLevel = levelCur.m_nLevelNum;

	m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur );

	// Update progress bars and labels
	UpdateControls( GetXPState().GetStartXP(), m_nLastLerpXP, levelCur );
}

void CPvPRankPanel::OnAnimEvent( KeyValues *pParams )
{
	// This gets fired by the model panel that has the badge model in it when we want
	// to do our bodygroup changes.  This is so we can time it to when the model is doing
	// a flashy maneuver to mask the bodygroup change pop
	if ( FStrEq( pParams->GetString( "name" ), "AE_CL_BODYGROUP_SET_VALUE" ) && m_pProgressionDesc )
	{
		const LevelInfo_t& levelCur = m_pProgressionDesc->GetLevelForExperience( m_nLastLerpXP );
		m_pProgressionDesc->SetupBadgePanel( m_pModelPanel, levelCur );
	}
}

void CPvPRankPanel::PlayLevelUpEffects( const LevelInfo_t& level ) const
{
	m_pModelPanel->PlaySequence( "level_up" );
	PlaySoundEntry( level.m_pszLevelUpSound );
	g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pXPBar, "PvPRankLevelUpXPBar", false);
	
	EditablePanel* pModelContainer = const_cast< CPvPRankPanel* >( this )->FindControl< EditablePanel >( "ModelContainer" );
	if ( pModelContainer )
	{
		g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pModelContainer, "PvPRankLevelUpModel", false );
	}
}

void CPvPRankPanel::PlayLevelDownEffects( const LevelInfo_t& level ) const
{
	g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( m_pXPBar, "PvPRankLevelDownXPBar", false);
	m_pModelPanel->PlaySequence( "level_down" );

	EditablePanel* pModelContainer = const_cast< CPvPRankPanel* >( this )->FindControl< EditablePanel >( "ModelContainer" );
	if ( pModelContainer )
	{
		g_pClientMode->GetViewportAnimationController()->StartAnimationSequence( pModelContainer, "PvPRankLevelDownModel", false );
	}
}

void CPvPRankPanel::SetMatchGroup( EMatchGroup eMatchGroup )
{
	if ( m_eMatchGroup != eMatchGroup )
	{
		m_eMatchGroup = eMatchGroup;
		m_pProgressionDesc = NULL;

		const IMatchGroupDescription* pMatchDesc = GetMatchGroupDescription( m_eMatchGroup );
		if ( pMatchDesc && pMatchDesc->m_pProgressionDesc )
		{
			// Snag the progression desc.  We use it often
			m_pProgressionDesc = pMatchDesc->m_pProgressionDesc;
			UpdateBaseState();
		}

		Assert( m_pProgressionDesc );

		// Many things needs to change
		InvalidateLayout( true, true );
		UpdateBaseState();
	}
}

void CPvPRankPanel::SOCreated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
{
	if ( pObject->GetTypeID() != CSOTFLadderData::k_nTypeID )
		return;

	SetMatchStats();
}

void CPvPRankPanel::SOUpdated( const CSteamID & steamIDOwner, const CSharedObject *pObject, ESOCacheEvent eEvent )
{
	if ( pObject->GetTypeID() != CSOTFLadderData::k_nTypeID )
		return;

	SetMatchStats();
}

void CPvPRankPanel::SetMatchStats( void )
{
	EditablePanel* pStatsContainer = FindControl< EditablePanel >( "Stats", true );
	if ( !pStatsContainer )
		return;

	CSOTFLadderData *pData = GetLocalPlayerLadderData( m_eMatchGroup );

	// Update all stats.  Default to 0 incase we dont have ladder data
	int nGames = 0, nKills = 0, nDeaths = 0, nDamage = 0, nHealing = 0, nSupport = 0, nScore = 0;

	if ( pData )
	{
		nGames = pData->Obj().games();
		nKills = pData->Obj().kills();
		nDeaths = pData->Obj().deaths();
		nDamage = pData->Obj().damage();
		nHealing = pData->Obj().healing();
		nSupport = pData->Obj().support();
		nScore = pData->Obj().score();
	}

	pStatsContainer->SetDialogVariable( "stat_games", LocalizeNumberWithToken( "TF_Competitive_Games", nGames ) );
	pStatsContainer->SetDialogVariable( "stat_kills", LocalizeNumberWithToken( "TF_Competitive_Kills", nKills ) );
	pStatsContainer->SetDialogVariable( "stat_deaths", LocalizeNumberWithToken( "TF_Competitive_Deaths", nDeaths ) );
	pStatsContainer->SetDialogVariable( "stat_damage", LocalizeNumberWithToken( "TF_Competitive_Damage", nDamage ) );
	pStatsContainer->SetDialogVariable( "stat_healing", LocalizeNumberWithToken( "TF_Competitive_Healing", nHealing ) );
	pStatsContainer->SetDialogVariable( "stat_support", LocalizeNumberWithToken( "TF_Competitive_Support", nSupport ) );
	pStatsContainer->SetDialogVariable( "stat_score", LocalizeNumberWithToken( "TF_Competitive_Score", nScore ) );
}

CPvPRankPanel::XPState_t& CPvPRankPanel::GetXPState() const
{
	// Singletons for each XPState_t
	static CUtlMap< EMatchGroup, CPvPRankPanel::XPState_t* > s_mapXPStates( DefLessFunc( EMatchGroup ) );

	auto idx = s_mapXPStates.Find( m_eMatchGroup );
	if ( idx == s_mapXPStates.InvalidIndex() )
	{
		idx = s_mapXPStates.Insert( m_eMatchGroup, new CPvPRankPanel::XPState_t( m_eMatchGroup ) );
	}

	return *s_mapXPStates[ idx ];
}

const char* CPvPRankPanel::GetResFile() const
{
	return m_pProgressionDesc ? m_pProgressionDesc->m_pszProgressionResFile : "resource/ui/PvPCompRankPanel.res";
}

KeyValues* CPvPRankPanel::GetConditions() const
{
	return NULL;
}