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

#include "tier0/vcrmode.h"
#undef PROTECT_FILEIO_FUNCTIONS
#include "tier0/vprof.h"
#include "utldict.h"
#include "client.h"
#include "cmd.h"
#include "filesystem_engine.h"
#include "vprof_record.h"


#ifdef VPROF_ENABLED

#if defined( _XBOX )

	extern CVProfile *g_pVProfileForDisplay;

#else

	CVProfile *g_pVProfileForDisplay = &g_VProfCurrentProfile;

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

#endif


long GetFileSize( FILE *fp )
{
	int curPos = ftell( fp );
	fseek( fp, 0, SEEK_END );
	long ret = ftell( fp );
	fseek( fp, curPos, SEEK_SET );
	return ret;
}


// ------------------------------------------------------------------------------------------------------------------------------------ //
// VProf record mode. Turn it on to record all the vprof data, then when you're playing back, the engine's budget and vprof panels 
// show the data from the recording instead of the real data.
// ------------------------------------------------------------------------------------------------------------------------------------ //

class CVProfRecorder : public CVProfile
{
public:
	CVProfRecorder()
	{
		m_Mode = Mode_None;
		m_hFile = NULL;
		m_nQueuedStarts = 0;
		m_nQueuedStops = 0;
		m_iPlaybackTick = -1;
	}

	~CVProfRecorder()
	{
		Assert( m_Mode == Mode_None );
	}

	void Shutdown()
	{
		Stop();
	}

	void Stop()
	{
		if ( (m_Mode == Mode_Record || m_Mode == Mode_Playback) && m_hFile != NULL )
		{
			if ( m_Mode == Mode_Record )
				++m_nQueuedStops;

			g_pFileSystem->Close( m_hFile );
		}

		m_Mode = Mode_None;
		m_hFile = NULL;
		g_pVProfileForDisplay = &g_VProfCurrentProfile;	// Stop using us for vprofile displays.
		m_iPlaybackTick = -1;
		m_bNodesChanged = true;
		Term(); // clear the vprof data
	}


	bool IsPlayingBack()
	{
		return m_Mode == Mode_Playback;
	}


// RECORD FUNCTIONS.
public:

	bool Record_Start( const char *pFilename )
	{
		Stop();

		char tempFilename[512];
		if ( !strchr( pFilename, '.' ) )
		{
			Q_snprintf( tempFilename, sizeof( tempFilename ), "%s.vprof", pFilename );
			pFilename = tempFilename;
		}

		m_iLastUniqueNodeID = -1;
		m_hFile = g_pFileSystem->Open( pFilename, "wb" );
		m_Mode = Mode_Record;
		if ( m_hFile == NULL )
		{
			return false;
		}
		else
		{
			// Write the version number.
			int version = VPROF_FILE_VERSION;
			g_pFileSystem->Write( &version, sizeof( version ), m_hFile );

			// Write the root node ID.
			int nodeID = g_VProfCurrentProfile.GetRoot()->GetUniqueNodeID();
			g_pFileSystem->Write( &nodeID, sizeof( nodeID ), m_hFile );

			++m_nQueuedStarts;
			
			// Make sure vprof is recrding.
			Cbuf_AddText( "vprof_on\n" );
			return true;
		}
	}

	void Record_WriteToken( char val )
	{
		g_pFileSystem->Write( &val, sizeof( val ), m_hFile );
	}		

	void Record_MatchTree_R( CVProfNode *pOut, const CVProfNode *pIn, CVProfile *pInProfile )
	{
		// Add any new nodes at the beginning of the list..
		if ( pIn->m_pChild )
		{
			while ( !pOut->m_pChild || pIn->m_pChild->GetUniqueNodeID() != pOut->m_pChild->GetUniqueNodeID() )
			{
				// Find the last new node in the list.
				const CVProfNode *pToAdd = NULL;
				const CVProfNode *pCur = pIn->m_pChild;
				while ( pCur )
				{
					// If the out node has no children then we add the last one in the input node.
					if ( pOut->m_pChild && pCur->GetUniqueNodeID() == pOut->m_pChild->GetUniqueNodeID() )
						break;

					pToAdd = pCur;
					pCur = pCur->m_pSibling;
				}

				Assert( pToAdd );

				// Write this to the file.
				int budgetGroupID = pToAdd->m_BudgetGroupID;
				int parentNodeID = pIn->GetUniqueNodeID();
				int nodeID = pToAdd->GetUniqueNodeID();
				
				Record_WriteToken( Token_AddNode );
				g_pFileSystem->Write( &parentNodeID, sizeof( parentNodeID ), m_hFile );						// Parent node ID.
				g_pFileSystem->Write( pToAdd->m_pszName, strlen( pToAdd->m_pszName ) + 1, m_hFile );	// Name of the new node.
				g_pFileSystem->Write( &budgetGroupID, sizeof( budgetGroupID ), m_hFile );
				g_pFileSystem->Write( &nodeID, sizeof( nodeID ), m_hFile );

				// There's a new one here.
				const char *pBudgetGroupName = g_VProfCurrentProfile.GetBudgetGroupName( pToAdd->m_BudgetGroupID );
				int budgetGroupFlags = g_VProfCurrentProfile.GetBudgetGroupFlags( pToAdd->m_BudgetGroupID );
				CVProfNode *pNewNode = pOut->GetSubNode( pToAdd->m_pszName, 0, pBudgetGroupName, budgetGroupFlags );
				pNewNode->SetBudgetGroupID( pToAdd->m_BudgetGroupID );
				pNewNode->SetUniqueNodeID( pToAdd->GetUniqueNodeID() );
			}
		}
		
		// Recurse.
		CVProfNode *pOutChild = pOut->m_pChild;
		const CVProfNode *pInChild = pIn->m_pChild;
		while ( pOutChild && pInChild )
		{
			Assert( Q_stricmp( pInChild->m_pszName, pOutChild->m_pszName ) == 0 );
			Assert( pInChild->GetUniqueNodeID() == pOutChild->GetUniqueNodeID() );
			Record_MatchTree_R( pOutChild, pInChild, pInProfile );
			
			pOutChild = pOutChild->m_pSibling;
			pInChild = pInChild->m_pSibling;
		}
	}

	void Record_MatchBudgetGroups( CVProfile *pInProfile )
	{
		Assert( GetNumBudgetGroups() <= pInProfile->GetNumBudgetGroups() );

		int nOriginalGroups = GetNumBudgetGroups();
		for ( int i=nOriginalGroups; i < pInProfile->GetNumBudgetGroups(); i++ )
		{
			const char *pName = pInProfile->GetBudgetGroupName( i );
			int flags = pInProfile->GetBudgetGroupFlags( i );
			Record_WriteToken( Token_AddBudgetGroup );
			g_pFileSystem->Write( pName, strlen( pName ) + 1, m_hFile );
			g_pFileSystem->Write( &flags, sizeof( flags ), m_hFile );

			AddBudgetGroupName( pName, flags );
		}
	}

	void Record_WriteTimings_R( const CVProfNode *pIn )
	{
		unsigned short curCalls = min( pIn->m_nCurFrameCalls, (unsigned)0xFFFF );
		if ( curCalls >= 255 )
		{
			unsigned char token = 255;
			g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
			g_pFileSystem->Write( &curCalls, sizeof( curCalls ), m_hFile );
		}
		else
		{
			// Get away with one byte if we can.
			unsigned char token = (char)curCalls;
			g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
		}

		// This allows us to write 2 bytes unless it's > 256 milliseconds (unlikely).
		unsigned long nMicroseconds = pIn->m_CurFrameTime.GetMicroseconds() / 4; 
		if ( nMicroseconds >= 0xFFFF )
		{
			unsigned short token = 0xFFFF;
			g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
			g_pFileSystem->Write( &nMicroseconds, sizeof( nMicroseconds ), m_hFile );
		}
		else
		{
			unsigned short token = (unsigned short)nMicroseconds;
			g_pFileSystem->Write( &token, sizeof( token ), m_hFile );
		}

		for ( const CVProfNode *pChild = pIn->m_pChild; pChild; pChild = pChild->m_pSibling )
			Record_WriteTimings_R( pChild );
	}

	void Record_Snapshot()
	{
		CVProfile *pInProfile = &g_VProfCurrentProfile;

		// Don't record the overhead of writing in the filesystem here.
		pInProfile->Pause();

		// Record the tick count and start of frame.
		Record_WriteToken( Token_StartFrame );
#ifdef SWDS
		g_pFileSystem->Write( &host_tickcount, sizeof( host_tickcount ), m_hFile );		
#else
		g_pFileSystem->Write( &g_ClientGlobalVariables.tickcount, sizeof( g_ClientGlobalVariables.tickcount ), m_hFile );
#endif
		
		// Record all the changes to get our tree and budget groups to g_VProfCurrentProfile.
		Record_MatchBudgetGroups( pInProfile );
		if ( m_iLastUniqueNodeID != CVProfNode::s_iCurrentUniqueNodeID )
		{
			Record_MatchTree_R( GetRoot(), pInProfile->GetRoot(), pInProfile );
		}
		
		// Now that we have a matching tree, write all the timings.
		Record_WriteToken( Token_Timings );
		Record_WriteTimings_R( pInProfile->GetRoot() );
		Record_WriteToken( Token_EndOfFrame );

		pInProfile->Resume();
	}

	
// PLAYBACK FUNCTIONS.
public:

	#define Playback_Assert( theTest ) Playback_AssertFn( !!(theTest), __LINE__ )
	bool Playback_AssertFn( bool bTest, int iLine )
	{
		if ( bTest )
		{
			return true;
		}
		else
		{
			Stop();
			Warning( "VPROF PLAYBACK ASSERT (%s, line %d) - stopping playback.\n", __FILE__, iLine );
			return false;
		}
	}


	bool Playback_Start( const char *pFilename )
	{
		Stop();

		char tempFilename[512];
		if ( !strchr( pFilename, '.' ) )
		{
			Q_snprintf( tempFilename, sizeof( tempFilename ), "%s.vprof", pFilename );
			pFilename = tempFilename;
		}

		m_iLastUniqueNodeID = -1;
		m_hFile = g_pFileSystem->Open( pFilename, "rb" );
		m_Mode = Mode_Playback;
		m_bPlaybackPaused = true;
		if ( m_hFile == NULL )
		{
			Warning( "vprof_playback_start: Open( %s ) failed.\n", pFilename );
			return false;
		}
		else
		{
			int version;
			g_pFileSystem->Read( &version, sizeof( version ), m_hFile );
			if ( !Playback_Assert( version == VPROF_FILE_VERSION ) )
				return false;

			// Read the root node ID.
			int nodeID;
			g_pFileSystem->Read( &nodeID, sizeof( nodeID ), m_hFile );
			GetRoot()->SetUniqueNodeID( nodeID );

			m_iSkipPastHeaderPos = g_pFileSystem->Tell( m_hFile );
			m_iLastTick = -1;	// We don't know the last tick in the file yet.
			m_FileLen = g_pFileSystem->Size( m_hFile );

			m_enabled = true; // So IsEnabled() returns true..
			Playback_ReadTick();
			g_pVProfileForDisplay = this;	// Start using this CVProfile for displays.
			return true;
		}
	}

	void Playback_Restart()
	{
		if ( m_Mode != Mode_Playback )
		{
			Assert( false );
			return;
		}
		
		// Clear the data and restart playback.
		m_iPlaybackTick = -1;
		Term(); // clear the vprof data
		m_bNodesChanged = true;

		g_pFileSystem->Seek( m_hFile, m_iSkipPastHeaderPos, FILESYSTEM_SEEK_HEAD );
		Playback_ReadTick();	// Read in one tick's worth of data.
	}

	char Playback_ReadToken()
	{
		Assert( m_Mode == Mode_Playback );
		char token;
		if ( g_pFileSystem->Read( &token, 1, m_hFile ) != 1 )
			token = TOKEN_FILE_FINISHED;
		
		return token;
	}

	
	bool Playback_ReadString( char *pOut, int maxLen )
	{
		int i = 0;
		while ( 1 )
		{
			char ch;
			if ( g_pFileSystem->Read( &ch, 1, m_hFile ) == 0 )
			{
				Playback_Assert( false );
				return false;
			}
			if ( ch == 0 )
			{
				pOut[i] = 0;
				break;
			}
			else
			{
				if ( i < (maxLen-1) )
				{
					pOut[i] = ch;
					++i;
				}
			}
		}
		return true;
	}


	bool Playback_ReadAddBudgetGroup()
	{
		char name[512];
		if ( !Playback_ReadString( name, sizeof( name ) ) )
			return false;

		int flags = 0;
		g_pFileSystem->Read( &flags, sizeof( flags ), m_hFile );

		AddBudgetGroupName( name, flags );
		return true;
	}


	CVProfNode* FindVProfNodeByID_R( CVProfNode *pNode, int id )
	{
		if ( pNode->GetUniqueNodeID() == id )
			return pNode;
		
		for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
		{
			CVProfNode *pTest = FindVProfNodeByID_R( pCur, id );
			if ( pTest )
				return pTest;
		}
		
		return NULL;
	}


	bool Playback_ReadAddNode()
	{
		int budgetGroupID;
		int parentNodeID;
		int nodeID;
		
		char nodeName[512];

		g_pFileSystem->Read( &parentNodeID, sizeof( parentNodeID ), m_hFile );					// Parent node ID.
		if ( !Playback_ReadString( nodeName, sizeof( nodeName ) ) )
			return false;
		g_pFileSystem->Read( &budgetGroupID, sizeof( budgetGroupID ), m_hFile );
		g_pFileSystem->Read( &nodeID, sizeof( nodeID ), m_hFile );

		// Now find the parent node.
		CVProfNode *pParentNode = FindVProfNodeByID_R( GetRoot(), parentNodeID );
		if ( !Playback_Assert( pParentNode != NULL ) )
			return false;

		const char *pBudgetGroupName = GetBudgetGroupName( 0 );
		int budgetGroupFlags = 0;
		CVProfNode *pNewNode = pParentNode->GetSubNode( PoolString( nodeName ), 0, pBudgetGroupName, budgetGroupFlags );
		pNewNode->SetBudgetGroupID( budgetGroupID );
		pNewNode->SetUniqueNodeID( nodeID );

		m_bNodesChanged = true;
		return true;
	}


	bool Playback_ReadTimings_R( CVProfNode *pNode )
	{
		// Read the timing.
		unsigned char token;
		if ( g_pFileSystem->Read( &token, sizeof( token ), m_hFile ) != sizeof( token ) )
			return false;

		if ( token == 255 )
		{
			unsigned short curCalls;
			if ( g_pFileSystem->Read( &curCalls, sizeof( curCalls ), m_hFile ) != sizeof( curCalls ) )
				return false;

			pNode->m_nCurFrameCalls = curCalls;
		}
		else
		{
			pNode->m_nCurFrameCalls = token;
		}
		pNode->m_nPrevFrameCalls = pNode->m_nCurFrameCalls;

		// This allows us to write 2 bytes unless it's > 256 milliseconds (unlikely).
		unsigned short microsecondsToken;
		if ( g_pFileSystem->Read( &microsecondsToken, sizeof( microsecondsToken ), m_hFile ) != sizeof( microsecondsToken ) )
			return false;

		if ( microsecondsToken == 0xFFFF )
		{
			unsigned long nMicroseconds;
			if ( g_pFileSystem->Read( &nMicroseconds, sizeof( nMicroseconds ), m_hFile ) != sizeof( nMicroseconds ) )
				return false;

			pNode->m_CurFrameTime.SetMicroseconds( nMicroseconds * 4 );
		}
		else
		{
			pNode->m_CurFrameTime.SetMicroseconds( (unsigned long)microsecondsToken * 4 );
		}
		pNode->m_PrevFrameTime = pNode->m_CurFrameTime;

		// Recurse.
		for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
		{
			if ( !Playback_ReadTimings_R( pCur ) )
				return false;
		}

		return true;
	}


	// Read the next tick. If iDontGoPast is set, then it will abort IF the next tick's index
	// is greater than iDontGoPast. In that case, sets pWouldHaveGonePast to true, 
	// stays where it was before the call, and returns true.
	bool Playback_ReadTick( int iDontGoPast = -1, bool *pWouldHaveGonePast = NULL )
	{
		if ( pWouldHaveGonePast )
			*pWouldHaveGonePast = false;

		if ( m_Mode != Mode_Playback )
			return false;

		// Read the next tick..
		int token = Playback_ReadToken();
		if ( token == TOKEN_FILE_FINISHED )
		{
			Msg( "VPROF playback finished.\n" );
			m_iLastTick = m_iPlaybackTick;	// Now we know our last tick.
			return true;
		}
			
		if ( !Playback_Assert( token == Token_StartFrame ) )
			return false;

		int iPlaybackTick = m_iPlaybackTick;
		g_pFileSystem->Read( &iPlaybackTick, sizeof( iPlaybackTick ), m_hFile );
		
		// First test if this tick would go past the number they don't want us to go past.
		if ( iDontGoPast != -1 && iPlaybackTick > iDontGoPast )
		{
			*pWouldHaveGonePast = true;
			g_pFileSystem->Seek( m_hFile, -5, FILESYSTEM_SEEK_CURRENT );
			return true;
		}
		else
		{
			m_iPlaybackTick = iPlaybackTick;
		}

		while ( 1 )
		{
			token = Playback_ReadToken();
			if ( token == Token_EndOfFrame )
				break;

			if ( token == Token_AddBudgetGroup )
			{
				if ( !Playback_ReadAddBudgetGroup() )
					return false;
			}
			else if ( token == Token_AddNode )
			{
				if ( !Playback_ReadAddNode() )
					return false;
			}
			else if ( token == Token_Timings )
			{
				if ( !Playback_ReadTimings_R( GetRoot() ) )
					return false;
			}
			else
			{
				Playback_Assert( false );
				return false;
			}
		}

		return true;
	}

	void Playback_Snapshot()
	{
		if ( m_Mode == Mode_Playback && !m_bPlaybackPaused )
			Playback_ReadTick();
	}

	
	void Playback_Step()
	{
		Playback_ReadTick();
	}


	class CNodeAverage
	{
	public:
		CVProfNode *m_pNode;
		
		CCycleCount m_CurFrameTime_Total;
		int m_nCurFrameCalls_Total;
		
		int m_nSamples;
	};

	CNodeAverage* FindNodeAverage( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
	{
		for ( int i=0; i < averages.Count(); i++ )
		{
			if ( averages[i].m_pNode == pNode )
				return &averages[i];
		}
		return NULL;
	}

	void UpdateAverages_R( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
	{
		CNodeAverage *pAverage = FindNodeAverage( averages, pNode );
		if ( !pAverage )
		{
			pAverage = &averages[ averages.AddToTail() ];
			memset( pAverage, 0, sizeof( *pAverage ) );
			pAverage->m_pNode = pNode;
		}
		pAverage->m_CurFrameTime_Total += pNode->m_CurFrameTime;
		pAverage->m_nCurFrameCalls_Total += pNode->m_nCurFrameCalls;
		pAverage->m_nSamples++;
		
		// Recurse.
		for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
			UpdateAverages_R( averages, pCur );
	}

	void DumpAverages_R( CUtlVector<CNodeAverage> &averages, CVProfNode *pNode )
	{
		CNodeAverage *pAverage = FindNodeAverage( averages, pNode );
		if ( pAverage )
		{
			pNode->m_CurFrameTime.m_Int64 = pAverage->m_CurFrameTime_Total.m_Int64 / pAverage->m_nSamples;
			pNode->m_nCurFrameCalls = pAverage->m_nCurFrameCalls_Total / pAverage->m_nSamples;
		}
		pNode->m_PrevFrameTime = pNode->m_CurFrameTime;
		pNode->m_nPrevFrameCalls = pNode->m_nCurFrameCalls;

		// Recurse.
		for ( CVProfNode *pCur=pNode->m_pChild; pCur; pCur=pCur->m_pSibling )
			DumpAverages_R( averages, pCur );
	}			


	void Playback_Average( int nFrames )
	{
		// Remember where we started.
		unsigned long seekPos = g_pFileSystem->Tell( m_hFile );
		int iOldLastTick = m_iLastTick;
		int iOldPlaybackTick = m_iPlaybackTick;
		
		// Take the average of the next N ticks.
		CUtlVector<CNodeAverage> averages;
		while ( nFrames > 0 && m_iLastTick == -1 )
		{
			Playback_ReadTick();
			UpdateAverages_R( averages, GetRoot() );
			--nFrames;
		}
		DumpAverages_R( averages, GetRoot() );
		
		// Now seek back to where we started.
		g_pFileSystem->Seek( m_hFile, seekPos, FILESYSTEM_SEEK_HEAD );
		m_iLastTick = iOldLastTick;
		m_iPlaybackTick = iOldPlaybackTick;
	}

	
	int Playback_SetPlaybackTick( int iTick )
	{
		if ( m_Mode != Mode_Playback )
			return 0;

		m_bNodesChanged = false;	// We want to pickup changes to this, so reset it here.
		if ( iTick == m_iPlaybackTick )
		{
			return 1;
		}
		else if ( iTick < m_iPlaybackTick )
		{
			// Crap.. have to go back. Restart and seek to this tick.
			Playback_Restart();
			
			// If this tick has a smaller value than the first tick in the file, then we can't seek forward to it...
			if ( iTick <= m_iPlaybackTick )
			{
				return 1 + m_bNodesChanged;	// return 2 if the nodes changed
			}
		}

		// Now seek forward to the tick they want.
		while ( m_iPlaybackTick < iTick )
		{
			bool bWouldHaveGonePast;
			if ( !Playback_ReadTick( iTick, &bWouldHaveGonePast ) )
				return 0;	// error

			// If reading this tick would have gone past the tick they're asking us to go for,
			// stay on the current tick.
			if ( bWouldHaveGonePast )
				break;
			
			// If we went to the last tick in the file, then stop here.
			if ( m_iLastTick != -1 && m_iPlaybackTick >= m_iLastTick )
				return 1 + m_bNodesChanged;
		}

		return 1 + m_bNodesChanged;
	}


	// 0-1 value.
	float Playback_GetCurrentPercent()
	{
		return (float)g_pFileSystem->Tell( m_hFile ) / m_FileLen;
	}


	int Playback_SeekToPercent( float flWantedPercent )
	{
		if ( m_Mode != Mode_Playback )
			return 0;	// error

		m_bNodesChanged = false;	// We want to pickup changes to this, so reset it here.

		float flCurPercent = Playback_GetCurrentPercent();
		if ( flWantedPercent < flCurPercent )
		{
			// Crap.. have to go back. Restart and seek to this tick.
			Playback_Restart();
			
			// If this tick has a smaller value than the first tick in the file, then we can't seek forward to it...
			if ( flWantedPercent <= 0 )
				return 1 + m_bNodesChanged;	// return 2 if nodes changed
		}

		// Now seek forward to the tick they want.
		while ( Playback_GetCurrentPercent() < flWantedPercent )
		{
			if ( !Playback_ReadTick() )
				return 0;	// error
			
			// If we went to the last tick in the file, then stop here.
			if ( m_iLastTick != -1 && m_iPlaybackTick >= m_iLastTick )
				return 1 + m_bNodesChanged; // return 2 if nodes changed
		}

		return 1 + m_bNodesChanged;
	}


	int Playback_GetCurrentTick()
	{
		return m_iPlaybackTick;
	}



// OTHER FUNCTIONS.
public:

	void Snapshot()
	{
		if ( m_Mode == Mode_Record )
			Record_Snapshot();
		else if ( m_Mode == Mode_Playback )
			Playback_Snapshot();
	}

	void StartOrStop()
	{
		while ( m_nQueuedStarts > 0 )
		{
			--m_nQueuedStarts;
			g_VProfCurrentProfile.Start();
		}

		while ( m_nQueuedStops > 0 )
		{
			--m_nQueuedStops;
			g_VProfCurrentProfile.Stop();
		}
	}

	inline CVProfile* GetActiveProfile()
	{
		if ( m_Mode == Mode_Playback )
			return this;
		else
			return &g_VProfCurrentProfile;
	}


private:

	const char* PoolString( const char *pStr )
	{
		int i = m_PooledStrings.Find( pStr );
		if ( i == m_PooledStrings.InvalidIndex() )
			i = m_PooledStrings.Insert( pStr, 0 );

		return m_PooledStrings.GetElementName( i );
	}			
			

private:
	enum
	{
		Token_StartFrame=0,
		Token_AddNode,
		Token_AddBudgetGroup,
		Token_Timings,
		Token_EndOfFrame,
		TOKEN_FILE_FINISHED
	};

	enum
	{
		VPROF_FILE_VERSION = 1
	};
	
	enum
	{
		Mode_None,
		Mode_Record,
		Mode_Playback
	};

	CUtlDict<int,int> m_PooledStrings;

	int m_Mode;
	FileHandle_t m_hFile;
	int m_iLastUniqueNodeID;
	int m_iPlaybackTick;		// Our current tick.
	int m_iSkipPastHeaderPos;
	int m_iLastTick;			// We only know this when we hit the end of the file.
	int m_FileLen;
	bool m_bNodesChanged;		// Set if the nodes were added or removed.

	int m_nQueuedStarts;
	int m_nQueuedStops;

	bool m_bPlaybackPaused;
};



static CVProfRecorder g_VProfRecorder;




CON_COMMAND( vprof_record_start, "Start recording vprof data for playback later." )
{
	if ( args.ArgC() != 2 )
	{
		Warning( "vprof_record_start requires a filename\n" );
		return;
	}

	g_VProfRecorder.Record_Start( args[1] );
}

CON_COMMAND( vprof_record_stop, "Stop recording vprof data" )
{
	Warning( "Stopping vprof recording...\n" );
	g_VProfRecorder.Stop();
}

CON_COMMAND( vprof_playback_start, "Start playing back a recorded .vprof file." )
{
	if ( args.ArgC() < 2 )
	{
		Warning( "vprof_playback_start requires a filename\n" );
		return;
	}

	// Console parser treats colons as a break, so join all the tokens together here.
	char fullFilename[512];
	fullFilename[0] = 0;
	for ( int i=1; i < args.ArgC(); i++ )
	{
		Q_strncat( fullFilename, args[i], sizeof( fullFilename ), COPY_ALL_CHARACTERS );
	}

	g_VProfRecorder.Playback_Start( fullFilename );
}

CON_COMMAND( vprof_playback_stop, "Stop playing back a recorded .vprof file." )
{
	Warning( "Stopping vprof playback...\n" );
	g_VProfRecorder.Stop();
}

CON_COMMAND( vprof_playback_step, "While playing back a .vprof file, step to the next tick." )
{
	VProfPlayback_Step();
}

CON_COMMAND( vprof_playback_stepback, "While playing back a .vprof file, step to the previous tick." )
{
	VProfPlayback_StepBack();
}

CON_COMMAND( vprof_playback_average, "Average the next N frames." )
{
	if ( args.ArgC() >= 2 )
	{
		int nFrames = atoi( args[ 1 ] );
		if ( nFrames == -1 )
			nFrames = 9999999;
			
		g_VProfRecorder.Playback_Average( nFrames );
	}
	else
	{
		Warning( "vprof_playback_average [# frames]\n" );
		Warning( "If # frames is -1, then it will average all the remaining frames in the vprof file.\n" );
	}	
}


void VProfRecord_Snapshot()
{
	g_VProfRecorder.Snapshot();
}


void VProfRecord_StartOrStop()
{
	g_VProfRecorder.StartOrStop();
}


void VProfRecord_Shutdown()
{
	g_VProfRecorder.Shutdown();
}



bool VProfRecord_IsPlayingBack()
{
	return g_VProfRecorder.IsPlayingBack();
}


int VProfPlayback_GetCurrentTick()
{
	return g_VProfRecorder.Playback_GetCurrentTick();
}


float VProfPlayback_GetCurrentPercent()
{
	return g_VProfRecorder.Playback_GetCurrentPercent();
}


int VProfPlayback_SetPlaybackTick( int iTick )
{
	return g_VProfRecorder.Playback_SetPlaybackTick( iTick );
}


int VProfPlayback_SeekToPercent( float percent )
{
	return g_VProfRecorder.Playback_SeekToPercent( percent );
}

void VProfPlayback_Step()
{
	g_VProfRecorder.Playback_Step();
}

int VProfPlayback_StepBack()
{
	return g_VProfRecorder.Playback_SetPlaybackTick( g_VProfRecorder.Playback_GetCurrentTick() - 1 );
}


#endif // VPROF_ENABLED