//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: VGUI scoreboard
//
// $Workfile:     $
// $Date:         $
// $NoKeywords: $
//===========================================================================//

#include "client_pch.h"
#include "vgui_vprofpanel.h"
#include <KeyValues.h>
#include "vgui_budgetpanel.h"
#include "vprof_engine.h"
#include "vprof_record.h"
#include "ivideomode.h"

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

using namespace vgui;

// positions
#define VPROF_INDENT_X			XRES(20)
#define VPROF_INDENT_Y			YRES(10)

// Scoreboard dimensions
#define VPROF_TITLE_SIZE_Y			YRES(22)

#define X_BORDER					XRES(4)
#define Y_BORDER					YRES(4)

static ConVar vprof_verbose( "vprof_verbose", "1", FCVAR_ARCHIVE, "Set to one to show average and peak times" );
static ConVar vprof_unaccounted_limit( "vprof_unaccounted_limit", "0.3", FCVAR_ARCHIVE, 
									  "number of milliseconds that a node must exceed to turn red in the vprof panel" );
static ConVar vprof_warningmsec( "vprof_warningmsec", "10", FCVAR_ARCHIVE, "Above this many milliseconds render the label red to indicate slow code." );

#ifdef VPROF_ENABLED

//-----------------------------------------------------------------------------
// Singleton accessor
//-----------------------------------------------------------------------------
static CVProfPanel *g_pVProfPanel = NULL;
CVProfPanel *GetVProfPanel( void )
{
	return g_pVProfPanel;
}


static void ClearNodeClientData( CVProfNode *pNode )
{
	pNode->SetClientData( -1 );
	if( pNode->GetSibling() )
	{
		ClearNodeClientData( pNode->GetSibling() );
	}
	
	if( pNode->GetChild() )
	{
		ClearNodeClientData( pNode->GetChild() );
	}
}

void CVProfPanel::Reset()
{
	m_pHierarchy->RemoveAll();
	m_RootItem = -1;
	ClearNodeClientData( m_pVProfile->GetRoot() );
}


CON_COMMAND( vprof_expand_all, "Expand the whole vprof tree" )
{
	Msg("VProf expand all.\n");
	GetVProfPanel()->ExpandAll();
}

CON_COMMAND( vprof_collapse_all, "Collapse the whole vprof tree" )
{
	Msg("VProf collapse all.\n");
	GetVProfPanel()->CollapseAll();
}

CON_COMMAND( vprof_expand_group, "Expand a budget group in the vprof tree by name" )
{
	Msg("VProf expand group.\n");
	if ( args.ArgC() >= 2 )
	{
		GetVProfPanel()->ExpandGroup( args[ 1 ] );
	}
}

void IN_VProfDown(void)
{
	GetVProfPanel()->UserCmd_ShowVProf();
}

void IN_VProfUp(void)
{
	GetVProfPanel()->UserCmd_HideVProf();
}

static ConCommand startshowvprof("+showvprof", IN_VProfDown);
static ConCommand endshowvprof("-showvprof", IN_VProfUp);

void ChangeVProfScopeCallback( IConVar *pConVar, const char *pOldString, float flOldValue )
{
	ConVarRef var( pConVar );
	Msg( "VProf setting scope to %s\n", var.GetString( ) );

	if ( g_pVProfPanel )
	{
		g_pVProfPanel->Reset();
	}
}

ConVar vprof_scope( 
	"vprof_scope", 
	"", 
	0, 
	"Set a specific scope to start showing vprof tree",
	0, 0, 0, 0,
	ChangeVProfScopeCallback );

#define PROF_FONT "DefaultFixed"

class CProfileTree : public TreeView
{
	DECLARE_CLASS_SIMPLE( CProfileTree, TreeView );
public:
	CProfileTree( CVProfPanel *parent, const char *panelName );
	~CProfileTree();

	virtual void OnCommand( const char *cmd );

	virtual void InvalidateLayout( bool layoutNow = false, bool reloadScheme = false );

	virtual void ApplySchemeSettings( IScheme *pScheme )
	{
		BaseClass::ApplySchemeSettings( pScheme );
		SetFont( pScheme->GetFont( PROF_FONT ) );
	}

	virtual void SetBgColor( Color color )
	{
		BaseClass::SetBgColor( color );
	}

private:

	Menu		*m_pEditMenu;
	CVProfPanel	*m_pParent;
};

CProfileTree::CProfileTree( CVProfPanel *parent, const char *panelName ) :
	BaseClass( (Panel *)parent, panelName ),
	m_pEditMenu( 0 ),
	m_pParent( parent )
{
}

CProfileTree::~CProfileTree()
{
	delete m_pEditMenu;
}

void CProfileTree::InvalidateLayout( bool layoutNow, bool reloadScheme )
{
	BaseClass::InvalidateLayout( layoutNow, reloadScheme );
	if ( GetParent() )
	{
		GetParent()->InvalidateLayout( layoutNow, reloadScheme );
	}
}

void CProfileTree::OnCommand( const char *cmd )
{
	// Relay to parent
	GetParent()->OnCommand( cmd );
}

CProfileHierarchyPanel::ColumnPanels_t::ColumnPanels_t() :
	treeViewItem( -1 )
{
}

CProfileHierarchyPanel::ColumnPanels_t::ColumnPanels_t( const ColumnPanels_t& src )
{
	treeViewItem = src.treeViewItem;
	int c = src.m_Columns.Count();
	for ( int i = 0; i < c; ++i )
	{
		PanelEntry_t pe;
		pe.dataname = src.m_Columns[ i ].dataname;
		pe.label = src.m_Columns[ i ].label;

		m_Columns.AddToTail( pe );
	}
}

void CProfileHierarchyPanel::ColumnPanels_t::AddColumn( int index, const char *name, vgui::Label *label )
{
	m_Columns.EnsureCount( index + 1 );

	m_Columns[ index ].label = label;
	m_Columns[ index ].dataname = name;
}

void CProfileHierarchyPanel::ColumnPanels_t::Refresh( KeyValues *kv )
{
	VPROF( "CProfileHierarchyPanel::ColumnPanels_t" );

	int c = m_Columns.Count();
	for ( int i = 0; i < c; ++i )
	{
		vgui::Label *label = m_Columns[ i ].label;
		if ( !label )
			continue;

		const char *name = m_Columns[ i ].dataname.String();
		if ( name && name[ 0 ] )
		{
			const char *value = kv->GetString( name, "" );
			if ( value )
			{
				if ( !value[ 0 ] )
				{
					label->SetText( "" );
					label->SetVisible( false );
				}
				else
				{
					label->SetVisible( true );
					label->SetText( value );
				}
			}
			else
			{
				label->SetVisible( false );
			}
		}
	}
}


CProfileHierarchyPanel::CProfileHierarchyPanel(vgui::Panel *parent, const char *panelName)
 : BaseClass(parent,panelName),
	m_Panels( 0, 0, PanelsLessFunc )
{
}

CProfileHierarchyPanel::~CProfileHierarchyPanel()
{
}

void CProfileHierarchyPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{
	//SetProportional( true );
	BaseClass::ApplySchemeSettings( pScheme );
	m_itemFont = pScheme->GetFont( PROF_FONT );
	SetTitleBarInfo( m_itemFont, 20 );
	SetBgColor( Color(0, 0, 0, 176) );
	(( CProfileTree *)GetTree())->SetBgColor( Color( 0, 0, 0, 176 ) );
}

void CProfileHierarchyPanel::SetItemColors( int id, const Color& fg, const Color& bg )
{
	VPROF( "CProfileHierarchyPanel::SetItemColors" );
	GetTree()->SetItemFgColor( id, fg );
	GetTree()->SetItemBgColor( id, bg );
	ColumnPanels_t search;
	search.treeViewItem = id;
	int idx = m_Panels.Find( search );
	if ( idx == m_Panels.InvalidIndex() )
	{
		return;
	}
	ColumnPanels_t& info = m_Panels[ idx ];
	int c = info.m_Columns.Count();
	for ( int i = 0; i < c; ++i )
	{
		Label *label = info.m_Columns[ i ].label;
		if ( !label )
			continue;
		label->SetFgColor( fg );
		label->SetBgColor( bg );
	}
}

void CProfileHierarchyPanel::SetItemColumnColors( int id, int col, const Color& fg, const Color& bg )
{
	VPROF( "CProfileHierarchyPanel::SetItemColumnColors" );
	ColumnPanels_t search;
	search.treeViewItem = id;
	int idx = m_Panels.Find( search );
	if ( idx == m_Panels.InvalidIndex() )
	{
		return;
	}
	ColumnPanels_t& info = m_Panels[ idx ];
	int c = info.m_Columns.Count();
	if ( col < 0 || col >= c )
		return;

	Label *label = info.m_Columns[ col ].label;
	if ( !label )
		return;

	label->SetFgColor( fg );
	label->SetBgColor( bg );
}

void CProfileHierarchyPanel::ModifyItem( KeyValues *data, int itemIndex )
{
	GetTree()->SetItemFgColor( itemIndex, GetFgColor() );
	GetTree()->SetItemBgColor( itemIndex, GetBgColor() );

	ColumnPanels_t search;
	search.treeViewItem = itemIndex;
	int idx = m_Panels.Find( search );
	if ( idx == m_Panels.InvalidIndex() )
	{
		Assert( 0 );
		return;
	}

	ColumnPanels_t& info = m_Panels[ idx ];
	info.Refresh( data );
}

int CProfileHierarchyPanel::AddItem( KeyValues *data, int parentItemIndex, ColumnPanels_t& columnPanels )
{
	int itemIndex = GetTree()->AddItem( data, parentItemIndex );
	columnPanels.treeViewItem = itemIndex;
	
	ColumnPanels_t search;
	search.treeViewItem = itemIndex;

	int idx = m_Panels.Find( search );
	if ( idx == m_Panels.InvalidIndex() )
	{
		m_Panels.Insert( columnPanels );

		int c = columnPanels.m_Columns.Count();
		for ( int i = 0; i < c; ++i )
		{
			if ( columnPanels.m_Columns[ i ].label )
			{
				columnPanels.m_Columns[ i ].label->SetParent( this );
			}
		}
	}

	ModifyItem( data, itemIndex );

	return itemIndex;
}

void CProfileHierarchyPanel::PostChildPaint()
{
}

void CProfileHierarchyPanel::PerformLayout()
{
	VPROF( "CProfileHierarchyPanel::PerformLayout" );
	BaseClass::PerformLayout();

	// Assume all invisible at first
	HideAll();

	int tall = GetTall();

	int rowheight = GetTree()->GetRowHeight();
	int top, visitems;
	bool hbarVisible = false;
	GetTree()->GetVBarInfo( top, visitems, hbarVisible );

	int offset = -top * rowheight;

	int numColumns = GetNumColumns();
	// Now position column panels into the correct spot
	int rows = GetNumRows();
	for ( int row = 0; row < rows; ++row )
	{
		int tvi = GetTreeItemAtRow( row );

		for ( int col = 0; col < numColumns; ++col )
		{
			int left, right, bottom;
			GetGridElementBounds( col, row, left, top, right, bottom );

			ColumnPanels_t search;
			search.treeViewItem = tvi;

			int idx = m_Panels.Find( search );
			if ( idx != m_Panels.InvalidIndex() )
			{
				ColumnPanels_t& info = m_Panels[ idx ];

				if ( col >= info.m_Columns.Count() )
					continue;

				vgui::Label *p = info.m_Columns[ col ].label;
				if ( !p )
				{
					continue;
				}
				
				bool vis = ( top + offset - 20 ) >= 0 && ( bottom + offset ) < tall;

				p->SetParent( vis ? this : NULL );
				p->SetVisible( vis );
				p->SetBounds( left, top + offset, right - left, bottom - top );
				p->InvalidateLayout();
			}
			else
			{
				Assert( 0 );
			}
		}
	}
}

void CProfileHierarchyPanel::HideAll()
{
	for ( int i = m_Panels.FirstInorder(); i != m_Panels.InvalidIndex(); i = m_Panels.NextInorder( i ) )
	{
		ColumnPanels_t& info = m_Panels[ i ];
		int c = info.m_Columns.Count();
		for ( int j = 0 ; j < c; ++j )
		{
			Label *panel = info.m_Columns[ j ].label;
			if ( !panel )
			{
				continue;
			}
			panel->SetVisible( false );
		}
	}
}

void CProfileHierarchyPanel::RemoveAll()
{
	GetTree()->RemoveAll();

	for ( int i = m_Panels.FirstInorder(); i != m_Panels.InvalidIndex(); i = m_Panels.NextInorder( i ) )
	{
		ColumnPanels_t& info = m_Panels[ i ];
		int c = info.m_Columns.Count();
		for ( int j = 0 ; j < c; ++j )
		{
			delete info.m_Columns[ j ].label;
		}
		info.m_Columns.RemoveAll();
	}
	m_Panels.RemoveAll();
	InvalidateLayout();
}

void CProfileHierarchyPanel::ExpandItem(int itemIndex, bool bExpand)
{
	GetTree()->ExpandItem( itemIndex, bExpand );
}

bool CProfileHierarchyPanel::IsItemExpanded( int itemIndex )
{
	return GetTree()->IsItemExpanded( itemIndex );
}

KeyValues *CProfileHierarchyPanel::GetItemData(int itemIndex)
{
	return GetTree()->GetItemData( itemIndex );
}

//-----------------------------------------------------------------------------
// Purpose: Create the VProf panel
//-----------------------------------------------------------------------------
CVProfPanel::CVProfPanel( vgui::Panel *pParent, const char *pElementName ) 
 :	vgui::Frame( pParent, pElementName )
{
	m_pVProfile = g_pVProfileForDisplay;

	Assert( g_pVProfPanel == NULL );
	g_pVProfPanel = this;

	m_RootItem = -1;
	m_fShowVprofHeld = false;

	int x = VPROF_INDENT_X;
	int y = VPROF_INDENT_Y;
	int wide = videomode->GetModeStereoWidth() - x * 2;
	int tall = videomode->GetModeStereoHeight() - y * 2;
	SetBgColor(Color(0, 0, 0, 175));

	// Initialize the top title.
#ifdef VPROF_ENABLED
	SetTitle("VProf", false);
#else
	SetTitle("** VProf is not enabled **", false);
#endif

	SetZPos( 1002 );

	CProfileTree *profileTree = new CProfileTree( this, "ProfileTree" );

	m_pHierarchy = new CProfileHierarchyPanel( this, "Hierarchy" );

	m_pHierarchy->SetTreeView( profileTree );
	m_pHierarchy->SetNumColumns( 3 );

	int treewide = wide - 780;
	m_pHierarchy->SetColumnInfo( 0, "Tree", treewide );

	m_pHierarchy->SetColumnInfo( 1, "Group", 120 );
	m_pHierarchy->SetColumnInfo( 2, "Data", 180 );

	// Treeview of the hierarchical calls
	m_pHierarchy->SetBounds(X_BORDER, VPROF_TITLE_SIZE_Y, wide - X_BORDER*2, tall - Y_BORDER*2 - VPROF_TITLE_SIZE_Y);
	m_pHierarchy->SetParent(this);
	m_pHierarchy->SetPaintBorderEnabled( false );
	m_pHierarchy->SetPaintBackgroundEnabled( false );

	SETUP_PANEL( m_pHierarchy );
	SETUP_PANEL( profileTree );

	// Mode selection used to populate the tree view + lists
	m_pVProfCategory = new ComboBox(this, "CategoryCombo", 5, false);
	m_pVProfCategory->AddItem( "All Categories", NULL );
	m_pVProfCategory->AddActionSignalTarget( this );
	m_pVProfCategory->ActivateItem( 0 );

	m_pVProfSort = new ComboBox( this, "SortCombo", 5, false );
	m_pVProfSort->AddItem( "By Time", NULL );
	m_pVProfSort->AddItem( "By Name", NULL );
	m_pVProfSort->AddItem( "By Budget Group", NULL );
	m_pVProfSort->AddActionSignalTarget( this );
	m_pVProfSort->ActivateItem( 0 );

	m_nLastBudgetGroupCount = 0;
	m_nCurrentBudgetGroup = -1;

	m_pHierarchicalView = new vgui::CheckButton( this, "HierarchicalViewSelection", "" );	
	m_pHierarchicalView->AddActionSignalTarget( this );
	m_pHierarchicalView->SetSelected( true );
	m_bHierarchicalView = true;

	m_pRedoSort = new vgui::Button( this, "RedoSorting", "", this, "redosort" );

	m_pVerbose = new vgui::CheckButton( this, "VerboseCheckbox", "" );	
	m_pVerbose->AddActionSignalTarget( this );
	m_pVerbose->SetSelected( vprof_verbose.GetBool() );


	// Setup the playback controls.
	m_pPlaybackLabel = new vgui::Label( this, "PlaybackLabel", "" );
	m_pPlaybackLabel->SetBgColor( Color( 0, 0, 0, 200 ) );
	m_pPlaybackLabel->SetPaintBackgroundEnabled( true );
	
	m_pStepForward = new vgui::Button( this, "StepForward", "", this, "StepForward" );
	m_pStepBack = new vgui::Button( this, "StepBack", "", this, "StepBack" );
	m_pGotoButton = new vgui::Button( this, "GotoButton", "", this, "GotoButton" );
	
	m_pPlaybackScroll = new vgui::ScrollBar( this, "PlaybackScroll", false );
	m_pPlaybackScroll->SetRange( 0, 1000 );
	m_pPlaybackScroll->SetRangeWindow( 30 );
	m_pPlaybackScroll->AddActionSignalTarget( this );
	
	m_iLastPlaybackTick = -1;


	LoadControlSettings("Resource\\VProfPanel.res");

	SetBounds( x, y, wide, tall );

	vgui::ivgui()->AddTickSignal( GetVPanel() );
}


CVProfPanel::~CVProfPanel( void )
{
	Assert( g_pVProfPanel == this );
	g_pVProfPanel = NULL;
}

#define DATA_FMT_STR "%-30.30s %-30.30s %-45.45s %-10.10s"
void CVProfPanel::PerformLayout()
{
	BaseClass::PerformLayout();

	int w, h;
	GetSize( w, h );

	int topoffset = 95;
	int inset = 10;

	m_pHierarchy->SetBounds( inset, topoffset, w - 2 * inset, h - inset - topoffset );

	int treewide = w - 900 - 20;
	treewide = max( treewide, 240 );
	m_pHierarchy->SetColumnInfo( 0, "Tree", treewide );

	m_pHierarchy->SetColumnInfo( 1, "Group", 125 );
	char header[ 512 ];
	Q_snprintf( header, sizeof( header ), DATA_FMT_STR,
		"Frame Calls + Time + NoChild",
		"Avg Calls   + Time + NoChild",
		"Sum Calls   + Time + NoChild + Peak",
		"L2Miss" );

	m_pHierarchy->SetColumnInfo( 2, header, 775, CTreeViewListControl::CI_HEADER_LEFTALIGN );
}

//-----------------------------------------------------------------------------
// Scheme settings!
//-----------------------------------------------------------------------------
void CVProfPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
{			 
	BaseClass::ApplySchemeSettings( pScheme );

	IBorder *border = pScheme->GetBorder( "ToolTipBorder" );
	SetBorder(border);

	SetBgColor(Color(0, 0, 0, 175));
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *command - 
//-----------------------------------------------------------------------------
void CVProfPanel::Close()
{
	UserCmd_HideVProf();
	BaseClass::Close();
}
			  

//-----------------------------------------------------------------------------
// Is it visible?
//-----------------------------------------------------------------------------
void CVProfPanel::OnTick()
{
	BaseClass::OnTick();
	
	// Did the CVProfile we're using switch behind our back?
	if ( g_pVProfileForDisplay != m_pVProfile )
	{
		Reset();
		m_pVProfile = g_pVProfileForDisplay;

		bool bVisible = false;
#ifndef _XBOX
		if ( VProfRecord_IsPlayingBack() )
		{
			bVisible = true;
			m_iLastPlaybackTick = -1;
		}
#endif
		m_pStepForward->SetVisible( bVisible );
		m_pStepBack->SetVisible( bVisible );
		m_pPlaybackLabel->SetVisible( bVisible );
		m_pPlaybackScroll->SetVisible( bVisible );
		m_pGotoButton->SetVisible( bVisible );
	}
#ifndef _XBOX
	if ( VProfRecord_IsPlayingBack() )
	{
		// Update the playback tick.
		int iCur = VProfPlayback_GetCurrentTick();
		if ( iCur != m_iLastPlaybackTick )
		{
			char str[512];
			Q_snprintf( str, sizeof( str ), "VPROF playback (tick %d, %d%%)", iCur, (int)(VProfPlayback_GetCurrentPercent() * 100) );
			m_pPlaybackLabel->SetText( str );
		}
	}
#endif
	SetVisible( m_fShowVprofHeld != 0 );

	m_pRedoSort->SetVisible( !m_bHierarchicalView );
}

// 0  = By Time
// 1 = By "Text"
// 2 = By "group" then by "text"
static int g_nSortType = -1;
//-----------------------------------------------------------------------------
// Visualization changed 
//-----------------------------------------------------------------------------
void CVProfPanel::OnTextChanged( KeyValues *data )
{
	Panel *pPanel = reinterpret_cast<vgui::Panel *>( data->GetPtr("panel") );
	vgui::ComboBox *pBox = dynamic_cast<vgui::ComboBox *>( pPanel );

	if( pBox == m_pVProfCategory ) 
	{
		// The -1 here is for the 'All Categories' item
		m_nCurrentBudgetGroup = pBox->GetActiveItem() - 1;

		// NOTE: We have to reset the tree view so that it 
		// is populated with only the ones we want (need to eliminate everything
		// that's not appropriate)
		Reset();
		return;
	}
	if ( pBox == m_pVProfSort )
	{
		g_nSortType = pBox->GetActiveItem();
		Reset();
		return;
	}
}



//-----------------------------------------------------------------------------
// Sort function for hierarchical data
//-----------------------------------------------------------------------------
bool ChildCostSortFunc(KeyValues *pNode1, KeyValues *pNode2)
{
	switch ( g_nSortType )
	{
	default:
	case 0:
	case -1:
		{
			float flTime1 = pNode1->GetFloat( "Time", -1.0f );
			float flTime2 = pNode2->GetFloat( "Time", -1.0f );
			return (flTime1 > flTime2);
		}
	case 1:
		{
			const char *t1 = pNode1->GetString( "Text", "" );
			const char *t2 = pNode2->GetString( "Text", "" );
			if( Q_stricmp( t1, t2 ) <= 0 )
				return true;
			return false;
		}
		break;
	case 2:
		{
			const char *g1 = pNode1->GetString( "group", "" );
			const char *g2 = pNode2->GetString( "group", "" );
			int val = Q_stricmp( g1, g2 );
			if( val < 0 )
				return true;
			
			if ( val > 0 )
				return false;

			const char *t1 = pNode1->GetString( "Text", "" );
			const char *t2 = pNode2->GetString( "Text", "" );
			if( Q_stricmp( t1, t2 ) <= 0 )
				return true;
			return false;
		}
	}

	return false;
}


//-----------------------------------------------------------------------------
// Changes the visualization method for vprof data 
//-----------------------------------------------------------------------------
void CVProfPanel::OnCheckButtonChecked(Panel *panel)
{
	if ( panel == m_pHierarchicalView )
	{
		bool bSelected = m_pHierarchicalView->IsSelected() ? 1 : 0;
		if ( bSelected != m_bHierarchicalView )
		{
			m_bHierarchicalView = bSelected;
			m_pHierarchy->GetTree()->SetSortFunc( m_bHierarchicalView ? NULL : ChildCostSortFunc );
			m_pRedoSort->SetVisible( !m_bHierarchicalView );
			Reset();
		}
		return;
	}

	if ( panel == m_pVerbose )
	{
		vprof_verbose.SetValue( m_pVerbose->IsSelected() ? 1 : 0 );
		return;
	}
}


//-----------------------------------------------------------------------------
// Methods related to expand/collapse in hierarchy
//-----------------------------------------------------------------------------
void CVProfPanel::ExpandAll( void )
{
	int count = m_pHierarchy->GetTree()->GetHighestItemID();
	int i;
	for( i = 0; i < count; i++ )
	{
		if( m_pHierarchy->GetTree()->IsItemIDValid( i ) )
		{
			m_pHierarchy->GetTree()->ExpandItem( i, true );
		}
	}
}

void CVProfPanel::CollapseAll( void )
{
	int count = m_pHierarchy->GetTree()->GetHighestItemID();
	int i;
	for( i = 1; i < count; i++ )
	{
		if( m_pHierarchy->GetTree()->IsItemIDValid( i ) )
		{
			m_pHierarchy->GetTree()->ExpandItem( i, false );
		}
	}
}

void CVProfPanel::ExpandGroupRecursive( int nBudgetGroupID, CVProfNode *pNode )
{
	if( !pNode )
	{
		return;
	}
	if( pNode->GetBudgetGroupID() == nBudgetGroupID )
	{
		CVProfNode *pTempNode = pNode;
		while( pTempNode )
		{
			if( pTempNode->GetParent() )
			{
				int id = pTempNode->GetParent()->GetClientData();
				m_pHierarchy->ExpandItem( id, true );
			}
			pTempNode = pTempNode->GetParent();
		}
	}

	ExpandGroupRecursive( nBudgetGroupID, pNode->GetChild() );
	ExpandGroupRecursive( nBudgetGroupID, pNode->GetSibling() );
}

void CVProfPanel::ExpandGroup( const char *pGroupName )
{
	int groupID = m_pVProfile->BudgetGroupNameToBudgetGroupID( pGroupName );
	ExpandGroupRecursive( groupID, m_pVProfile->GetRoot() );
}

class CVProfLabel : public Label
{
	DECLARE_CLASS_SIMPLE( CVProfLabel, Label );
public:

	CVProfLabel( Panel *parent, const char *panelName ) :
		BaseClass( parent, panelName, "" )
	{
		//SetPaintBackgroundEnabled( false );
	}

	virtual void ApplySchemeSettings( IScheme *pScheme )
	{
		BaseClass::ApplySchemeSettings( pScheme );
		SetFont( pScheme->GetFont( PROF_FONT ) );
		SetBgColor( Color( 0, 0, 0, 255 ) );
	}
};

Label *AllocateVprofLabel( const char *panelName )
{
	CVProfLabel *l = new CVProfLabel( NULL, panelName );
	l->SetContentAlignment( Label::a_west );
	return l;
}

void CVProfPanel::AddColumns( CProfileHierarchyPanel::ColumnPanels_t& cp )
{
	cp.AddColumn( 1, "group", AllocateVprofLabel( "group" ) );
	cp.AddColumn( 2, "data", AllocateVprofLabel( "data" ) );
}


//-----------------------------------------------------------------------------
// Populate the tree
//-----------------------------------------------------------------------------
int CVProfPanel::UpdateVProfTreeEntry( KeyValues *pKeyValues, CVProfNode *pNode, int parent )
{
	VPROF( "UpdateVProfTreeEntry" );

	CFmtStrN<1024> msg;
	double curTimeLessChildren = pNode->GetCurTimeLessChildren();
	double avgLessChildren = ( pNode->GetTotalCalls() > 0 ) ? pNode->GetTotalTimeLessChildren() / (double)pNode->GetTotalCalls() : 0;

	pKeyValues->SetString( "Text", pNode->GetName() );
	pKeyValues->SetString( "group", m_pVProfile->GetBudgetGroupName( pNode->GetBudgetGroupID() ) );

	CFmtStrN< 256 > frame;
	CFmtStrN< 256 > avgstr;
	CFmtStrN< 256 > sum;
	CFmtStrN< 256 > l2miss;

	frame.sprintf( "[%4d] %7.2f %7.2f", pNode->GetCurCalls(), pNode->GetCurTime(), curTimeLessChildren );
	
	
	pKeyValues->SetString( "frame", msg );

	if( vprof_verbose.GetBool() )
	{											 
		double avgCalls = ( m_pVProfile->NumFramesSampled() > 0 ) ? (double)pNode->GetTotalCalls() / (double)m_pVProfile->NumFramesSampled() : 0;
		double avg = ( m_pVProfile->NumFramesSampled() > 0 ) ? (double)pNode->GetTotalTime() / (double)m_pVProfile->NumFramesSampled() : 0;
		double avgLessChildrenVerbose = ( m_pVProfile->NumFramesSampled() > 0 ) ? (double)pNode->GetTotalTimeLessChildren() / (double)m_pVProfile->NumFramesSampled() : 0;

		avgstr.sprintf( "[%6.2f] %6.3f %6.3f", avgCalls, avg, avgLessChildrenVerbose );
		sum.sprintf( "[%7d] %9.2f %9.2f %8.2fp", pNode->GetTotalCalls(), pNode->GetTotalTime(), pNode->GetTotalTimeLessChildren(), pNode->GetPeakTime() );
	}

	if ( m_pVProfile->UsePME() )
	{
		l2miss.sprintf( "%5d", pNode->GetL2CacheMisses() );
	}

	msg.sprintf( DATA_FMT_STR,
		(const char *)frame,
		(const char *)avgstr,
		(const char *)sum,
		(const char *)l2miss );
	pKeyValues->SetString( "data", msg );

	pKeyValues->SetFloat( "Time", avgLessChildren );

	// Add or modify a line in the hierarchy
	int id = pNode->GetClientData();
	if ( id == -1 )
	{
		CProfileHierarchyPanel::ColumnPanels_t cp;
		AddColumns(  cp );
		id = m_pHierarchy->AddItem( pKeyValues, parent, cp ) ;
		pNode->SetClientData( id );
	}
	else
	{
		VPROF( "UpdateVProfTreeEntry:Modify" );
		m_pHierarchy->ModifyItem( pKeyValues, id );
	}

	// Apply color to the item
	int r,g,b,a;
	m_pVProfile->GetBudgetGroupColor( pNode->GetBudgetGroupID(), r, g, b, a );
	m_pHierarchy->SetItemColors( id, Color( r, g, b, a ), Color( 0, 0, 0, 255 ) );

	if( pNode->GetBudgetGroupID() == VPROF_BUDGET_GROUP_ID_UNACCOUNTED )
	{
		if ( curTimeLessChildren > vprof_unaccounted_limit.GetFloat() )
		{
			m_pHierarchy->SetItemColors( id, Color( 255, 0, 0, 255 ), Color( 0, 0, 0, 255 ) );
		}
	}

	if ( pNode->GetCurTime() > vprof_warningmsec.GetFloat() ||
		 curTimeLessChildren > vprof_warningmsec.GetFloat() )
	{
		m_pHierarchy->SetItemColumnColors( id, 2, Color( 255, 0, 0, 255 ), Color( 63, 0, 0, 255 ) );
	}
	return id;
}

//-----------------------------------------------------------------------------
// Populate the tree
//-----------------------------------------------------------------------------
void CVProfPanel::FillTree( KeyValues *pKeyValues, CVProfNode *pNode, int parent )
{
#ifdef VPROF_ENABLED

	bool fIsRoot = ( pNode == m_pVProfile->GetRoot() );
	if ( fIsRoot )
	{
		if( pNode->GetChild() )
		{
			FillTree( pKeyValues, pNode->GetChild(), parent );
		}
		return;
	}

	int id = parent;
	if (( m_nCurrentBudgetGroup < 0 ) || ( pNode->GetBudgetGroupID() == m_nCurrentBudgetGroup )) 
	{
		id = UpdateVProfTreeEntry( pKeyValues, pNode, parent );
	}

	if( pNode->GetSibling() )
	{
		FillTree( pKeyValues, pNode->GetSibling(), parent );
	}
	
	if( pNode->GetChild() )
	{
		FillTree( pKeyValues, pNode->GetChild(), m_bHierarchicalView ? id : parent );
	}
#endif
}


//-----------------------------------------------------------------------------
// Populates the budget group combo box
//-----------------------------------------------------------------------------
void CVProfPanel::PopulateBudgetGroupComboBox()
{
	int nBudgetGroupCount = m_pVProfile->GetNumBudgetGroups();
	while( m_nLastBudgetGroupCount < nBudgetGroupCount )
	{
		m_pVProfCategory->AddItem( m_pVProfile->GetBudgetGroupName(m_nLastBudgetGroupCount), NULL );
		++m_nLastBudgetGroupCount;
	}
}


//-----------------------------------------------------------------------------
// Populates the tree
//-----------------------------------------------------------------------------
void CVProfPanel::UpdateProfile( float filteredtime )
{
#ifdef VPROF_ENABLED
	//ExecuteDeferredOp();
	if (IsVisible())
	{
		PopulateBudgetGroupComboBox();

		SetTitle( CFmtStr( "VProf (%s) --  %d frames sampled", 
									   m_pVProfile->IsEnabled() ?  "running" : "not running",
										   m_pVProfile->NumFramesSampled() ), false );

		// It's important to cache bEnabled since calling pause can disable.
		bool bEnabled = m_pVProfile->IsEnabled();
		if( bEnabled )
		{
			m_pVProfile->Pause();
		}

		KeyValues * pVal = new KeyValues("");
		
		if ( !m_pHierarchy->GetTree()->GetItemCount() )
		{
			pVal->SetString( "Text", "Call tree" );
			CProfileHierarchyPanel::ColumnPanels_t cp;
			AddColumns( cp );
			m_RootItem = m_pHierarchy->AddItem( pVal, -1, cp );
			m_pHierarchy->SetItemColors( m_RootItem, Color( 255, 255, 255, 255 ), Color( 0, 0, 0, 255 ) );
			m_pHierarchy->ExpandItem( m_RootItem, true );
		}

		m_pHierarchy->ExpandItem( m_RootItem, true );

		const char *pScope = vprof_scope.GetString();
		CVProfNode *pStartNode = ( pScope[0] == 0 ) ? m_pVProfile->GetRoot()  : m_pVProfile->FindNode(m_pVProfile->GetRoot(), pScope );
		
		if ( pStartNode )
		{
			FillTree( pVal, pStartNode, m_RootItem );
		}
		
		pVal->deleteThis();

		if( bEnabled )
		{
			m_pVProfile->Resume();
		}
	}

	if ( m_pVProfile->IsEnabled() )
	{
		Assert( m_pVProfile->AtRoot() );
		
		if ( GetBudgetPanel()->IsBudgetPanelShown() )
			GetBudgetPanel()->SnapshotVProfHistory( filteredtime );

		WriteRemoteVProfData(); // send out the vprof data to remote endpoints
	}
#endif
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CVProfPanel::UserCmd_ShowVProf( void )
{
	m_fShowVprofHeld = true;

	SetVisible( true );
	// This is hacky . . need to at least remember the previous value to set it back.
	ConVarRef cl_mouseenable( "cl_mouseenable" );
	if ( cl_mouseenable.IsValid() )
	{
		cl_mouseenable.SetValue( 0 );
	}
}

//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CVProfPanel::UserCmd_HideVProf( void )
{
	m_fShowVprofHeld = false;

	SetVisible( false );

	// This is hacky . . need to at least remember the previous value to set it back.
	ConVarRef cl_mouseenable( "cl_mouseenable" );
	if ( cl_mouseenable.IsValid() )
	{
		cl_mouseenable.SetValue( 1 );
	}
}


//-----------------------------------------------------------------------------

void CVProfPanel::Paint()
{
	m_pVProfile->Pause();
	BaseClass::Paint();
	m_pVProfile->Resume();
}


void CVProfPanel::OnCommand( const char *pCommand )
{
#ifndef _XBOX
	if ( !Q_stricmp( pCommand, "StepForward" ) )
	{
		VProfPlayback_Step();
	}
	else if ( !Q_stricmp( pCommand, "StepBack" ) )
	{
		int shouldReset = VProfPlayback_StepBack();
		if ( shouldReset == 2 )
		{
			Reset();
		}
	}
	else if ( !Q_stricmp( pCommand, "GotoButton" ) )
	{
		int shouldReset = VProfPlayback_SeekToPercent( (float)m_pPlaybackScroll->GetValue() / 1000.0 );
		if ( shouldReset == 2 )
		{
			Reset();
		}
	}
	else if ( !Q_stricmp( pCommand, "redosort" ) )
	{
		//
		Assert( !m_bHierarchicalView );
		Reset();
	}
#endif
}

#endif