//========= Copyright (c), Valve LLC, All rights reserved. ============
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================

#include "stdafx.h"
#include "gcsystemaccess.h"
#include "gcjob.h"
#include "gcsdk/gcreportprinter.h"


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

namespace GCSDK
{


static GCConVar gcaccess_enable( "gcaccess_enable", "1", "Kill switch that disables all gcaccess tracking and systems" );

DECLARE_GC_EMIT_GROUP( g_EGAccess, access );


//-----------------------------------------------------------------------------------------------------------------------------------------
GCAccessSystem_t GenerateUniqueGCAccessID()
{
	static GCAccessSystem_t s_nID = 0;
	return s_nID++;
}

//-----------------------------------------------------------------------------------------------------------------------------------------
// CGCAccessSystem
//-----------------------------------------------------------------------------------------------------------------------------------------

//a GCAccess system that has been registered
class CGCAccessSystem
{
public:
	//the name of the system for display
	CUtlString			m_sName;
	//the unique ID for this system
	GCAccessSystem_t	m_nID;
	//which actions should be enabled or disabled for this system in particular
	uint32				m_nActions;
	uint32				m_nSuppressActions;

	struct AccessStats_t
	{
		AccessStats_t();
		void Add( const AccessStats_t& rhs );
		//how many raw accesses have we had?
		uint32		m_nNumValid;
		//how many failed because the context didn't have rights?
		uint32		m_nNumFail;
		//how many failed because the associated ID wasn't valid?
		uint32		m_nNumFailID;
	};

	struct TrackedJob_t
	{
		CUtlString		m_sContext;
		AccessStats_t	m_Stats;
	};

	//which jobs have violated this system access rights
	CUtlHashMapLarge< uintp, TrackedJob_t >	m_Jobs;
};

//-----------------------------------------------------------------------------------------------------------------------------------------
// CGCAccessContext
//-----------------------------------------------------------------------------------------------------------------------------------------

CGCAccessContext::CGCAccessContext() :
	m_nActions( 0 ),
	m_nSuppressActions( 0 )
{
}

void CGCAccessContext::Init( const char* pszName, uint32 nActions, uint32 nSuppressActions )
{
	m_sName = pszName;
	m_nActions = nActions;
	m_nSuppressActions = nSuppressActions;
}

void CGCAccessContext::AddSystem( GCAccessSystem_t nSystem )
{
	m_nSystems.InsertIfNotFound( nSystem );
}

bool CGCAccessContext::HasSystem( GCAccessSystem_t system ) const
{
	return ( m_nSystems.Find( system ) != m_nSystems.InvalidIndex() );
}

bool CGCAccessContext::IsSubsetOf( const CGCAccessContext& context ) const
{
	FOR_EACH_VEC( m_nSystems, nCurrSystem )
	{
		if( !context.HasSystem( m_nSystems[ nCurrSystem ] ) )
			return false;
	}
	return true;
}

void CGCAccessContext::AddSystemsFrom( const CGCAccessContext& context )
{
	FOR_EACH_VEC( context.m_nSystems, nCurrSystem )
	{
		AddSystem( context.m_nSystems[ nCurrSystem ] );
	}
}

void CGCAccessContext::SetAction( EGCAccessAction eAction, bool bSet )
{
	if( bSet )
		m_nActions |= eAction;
	else
		m_nActions &= ~( uint32 )eAction;
}

void CGCAccessContext::SetSuppressAction( EGCAccessAction eAction, bool bSet )
{
	if( bSet )
		m_nSuppressActions |= eAction;
	else
		m_nSuppressActions &= ~( uint32 )eAction;
}

//-----------------------------------------------------------------------------------------------------------------------------------------
// CGCAccess
//-----------------------------------------------------------------------------------------------------------------------------------------

static CGCAccess g_GCAccess;
CGCAccess& GGCAccess()
{
	return g_GCAccess;
}

CGCAccessSystem::AccessStats_t::AccessStats_t()
	: m_nNumFail( 0 )
	, m_nNumFailID( 0 )
	, m_nNumValid( 0 )
{}

void CGCAccessSystem::AccessStats_t::Add( const AccessStats_t& rhs )
{
	m_nNumFail += rhs.m_nNumFail;
	m_nNumFailID += rhs.m_nNumFailID;
	m_nNumValid += rhs.m_nNumValid;
}

CGCAccess::CGCAccess() :
	m_nActions( GCAccessAction_TrackSuccess | GCAccessAction_TrackFail| GCAccessAction_ReturnFail ),
	m_nSuppressActions( 0 )
{
	m_GlobalContext.Init( "Global" );
}

void CGCAccess::RegisterSystem( const char* pszName, GCAccessSystem_t nID, uint32 nActions, uint32 nSupressActions )
{
	//make sure that we don't have a conflict
	int nIndex = m_Systems.Find( nID );
	if( nIndex != m_Systems.InvalidIndex() )
	{
		// !FIXME!
		// DOTAMERGE AssertMsg varargs
		AssertMsg3( false, "Multiple systems conflciting in Register System: ID %d, original: %s, new %s", nID, m_Systems[ nIndex ]->m_sName.String(), pszName );
		return;
	}

	CGCAccessSystem* pSystem = new CGCAccessSystem;
	pSystem->m_sName = pszName;
	pSystem->m_nID = nID;
	pSystem->m_nActions = nActions;
	pSystem->m_nSuppressActions = nSupressActions;
	m_Systems.Insert( nID, pSystem );
}

bool CGCAccess::ValidateAccess( GCAccessSystem_t nSystem )
{
	//global kill switch
	if( !gcaccess_enable.GetBool() )
		return true;

	return InternalValidateAccess( nSystem, CSteamID(), CSteamID() );	
}

bool CGCAccess::ValidateSteamIDAccess( GCAccessSystem_t nSystem, CSteamID steamID )
{
	//global kill switch
	if( !gcaccess_enable.GetBool() )
		return true;

	CSteamID expectedID = ( g_pJobCur ) ? g_pJobCur->SOVALIDATE_GetSteamID() : steamID;
	return InternalValidateAccess( nSystem, steamID, expectedID );	
}

bool CGCAccess::InternalValidateAccess( GCAccessSystem_t nSystem, CSteamID steamID, CSteamID expectedID )
{
	//see if tracking for this system is disabled. This list is almost always empty, so just use a linear search for now.
	FOR_EACH_VEC( m_SuppressAccess, nAction )
	{
		if( m_SuppressAccess[ nAction ].m_nSystem == nSystem )
			return true;
	}

	//assume the global context
	const CGCAccessContext* pContext = &m_GlobalContext;
	const char* pszJobName = "[global]";

	//if we have a job, use it's context
	if( g_pJobCur )
	{
		const CGCJob* pJob = static_cast< const CGCJob* >( g_pJobCur );
		pszJobName = pJob->GetName();
		if( pJob->GetContext() )
			pContext = pJob->GetContext();
	}

	//is this a valid system to access
	bool bValidSteamID = ( steamID == expectedID );
	bool bValidSystem = pContext->HasSystem( nSystem );
	bool bValidAccess = ( bValidSystem && bValidSteamID );

	//determine the actions we want to do for tracking
	uint32 nActions = pContext->GetActions() | m_nActions;
	uint32 nSuppressActions = pContext->GetSuppressActions() | m_nSuppressActions;

	//look up the system that we are accessing
	CGCAccessSystem* pSystem = NULL;
	int nSystemIndex = m_Systems.Find( nSystem );
	if( nSystemIndex == m_Systems.InvalidIndex() )
	{
		// !FIXME! DOTAMERGE
		// AssertMsg varargs
		AssertMsg1( false, "Error: Tracking a system that has not been registered. Make sure to register system %u", nSystem );
	}
	else
	{
		//make sure that we have a stat entry for this job
		pSystem = m_Systems[ nSystemIndex ];
		nActions |= pSystem->m_nActions;
		nSuppressActions |= pSystem->m_nSuppressActions;
	}

	//remove any suppressed actions
	nActions &= ~nSuppressActions;

	//see if we need to track this access
	bool bTrackSuccess = ( nActions & GCAccessAction_TrackSuccess ) && bValidAccess;
	bool bTrackFail    = ( nActions & GCAccessAction_TrackFail ) && !bValidAccess;
	if( ( bTrackSuccess || bTrackFail ) && pSystem )
	{
		//make sure that we have a stat entry for this job
		int nJobIndex = pSystem->m_Jobs.Find( ( uintp )pszJobName );
		if( nJobIndex == pSystem->m_Jobs.InvalidIndex() )
		{
			nJobIndex = pSystem->m_Jobs.Insert( ( uintp )pszJobName );
			pSystem->m_Jobs[ nJobIndex ].m_sContext = pContext->GetName();
		}
		
		//update our stats based upon what was valid and what wasn't
		CGCAccessSystem::AccessStats_t& stats = pSystem->m_Jobs[ nJobIndex ].m_Stats; 
		if( bTrackSuccess )
			stats.m_nNumValid++;

		if( bTrackFail )
		{
			if( !bValidSteamID )
				stats.m_nNumFailID++;
			if( !bValidSystem )
				stats.m_nNumFail++;
		}
	}

	//see if this is a single access we want to try and track
	FOR_EACH_VEC( m_SingleAsserts, nCurrAssert )
	{
		SingleAssert_t* pAssert = m_SingleAsserts[ nCurrAssert ];
		if( nSystem == pAssert->m_System )
		{
			if( V_stricmp( ( pAssert->m_bContext ) ? pContext->GetName() : pszJobName, pAssert->m_sContextOrJob ) == 0 )
			{
				//log this assert
				{
					// !FIXME! DOTAMERGE
					//CGCInterface::CDisableAssertRateLimit disableAssertLimit;

					// !FIXME! DOTAMERGE
					// AssertMsg varargs
					AssertMsg3( false, "GCSystemAccess Caught %s (context %s) calling into %s", pszJobName, pContext->GetName(), pSystem ? pSystem->m_sName.String() : "unknown" );
				}

				//and clear it
				delete pAssert;
				m_SingleAsserts.FastRemove( nCurrAssert );
			}
		}
	}

	//at this point, if it is a valid access, just bail
	if( bValidAccess )
		return true;

	//otherwise, handle the failure case
	if( nActions & ( GCAccessAction_Msg | GCAccessAction_Assert ) )
	{
		int nSystemIndex = m_Systems.Find( nSystem );
		const char* pszSystem = ( nSystemIndex != m_Systems.InvalidIndex() ) ? m_Systems[ nSystemIndex ]->m_sName.String() : "<unregistered>";

		//display a message based upon if it was a system or ID violation
		CFmtStr1024 szMsg;
		if( !bValidSystem )
		{
			szMsg.sprintf( "Job %s Accessed invalid system %s (%u) while in context %s\n", pszJobName, pszSystem, nSystem, pContext->GetName() );
		}
		else
		{
			szMsg.sprintf( "Job %s Accessed invalid steam ID %s but expected %s in system %s (%u) while in context %s\n", pszJobName, steamID.RenderLink(), expectedID.RenderLink(), pszSystem, nSystem, pContext->GetName() );
		}

		if( nActions & GCAccessAction_Msg )
			EG_MSG( g_EGAccess, "%s", szMsg.String() );
		if( nActions & GCAccessAction_Assert )
		{
			// !FIXME! DOTAMERGE
			// AssertMsg varargs
			AssertMsg1( false, "%s", szMsg.String() );
		}
	}

	return !( nActions & GCAccessAction_ReturnFail );
}

void CGCAccess::SuppressAccess( GCAccessSystem_t nSystem, bool bEnable )
{
	//see if it is an existing item already suppressed that we need to modify the ref count of
	FOR_EACH_VEC( m_SuppressAccess, nAccess )
	{
		if( m_SuppressAccess[ nAccess ].m_nSystem == nSystem )
		{
			if( bEnable )
			{
				//to enable, we want to decrease the disable count, or remove if we are fully re-enabled
				if( m_SuppressAccess[ nAccess ].m_nCount <= 1 )
					m_SuppressAccess.FastRemove( nAccess );
				else
					m_SuppressAccess[ nAccess ].m_nCount--;
			}
			else
			{
				m_SuppressAccess[ nAccess ].m_nCount++;
			}
			return;
		}
	}

	//only add it to the list if we are disabling
	if( !bEnable )
	{
		SuppressAccess_t access;
		access.m_nSystem = nSystem;
		access.m_nCount = 1;
		m_SuppressAccess.AddToTail( access );
	}
}

void CGCAccess::ClearSystemStats()
{
	FOR_EACH_MAP_FAST( m_Systems, nSystem )
	{
		m_Systems[ nSystem ]->m_Jobs.Purge();
	}
}

bool CGCAccess::CatchSingleAssert( const char* pszSystem, bool bContext, const char* pszContextOrJob )
{
	//find the system we are referencing so we don't add invalid breakpoints if we can avoid it
	GCAccessSystem_t nSystemID = ( GCAccessSystem_t )-1;
	FOR_EACH_MAP_FAST( m_Systems, nSystem )
	{
		if( V_stricmp( m_Systems[ nSystem ]->m_sName, pszSystem ) == 0 )
		{
			nSystemID = m_Systems.Key( nSystem );
			break;
		}
	}

	if( nSystemID == ( GCAccessSystem_t )-1 )
		return false;

	SingleAssert_t* pAssert = new SingleAssert_t;
	pAssert->m_System			= nSystemID;
	pAssert->m_bContext			= bContext;
	pAssert->m_sContextOrJob	= pszContextOrJob;
	m_SingleAsserts.AddToTail( pAssert );
	return true;
}

void CGCAccess::ClearSingleAsserts()
{
	m_SingleAsserts.PurgeAndDeleteElements();
}


//given a display type and a stats object, determines if it meets the criteria
static bool ShouldDisplayStats( CGCAccess::EDisplay eDisplay, const CGCAccessSystem::AccessStats_t& stats )
{
	if( eDisplay == CGCAccess::eDisplay_Referenced )
		return ( stats.m_nNumFail + stats.m_nNumFailID + stats.m_nNumValid ) > 0;
	if( eDisplay == CGCAccess::eDisplay_Violations )
		return ( stats.m_nNumFail + stats.m_nNumFailID ) > 0;
	if( eDisplay == CGCAccess::eDisplay_IDViolations )
		return stats.m_nNumFailID > 0;

	//unknown, so just assume all
	return true;
}


void CGCAccess::ReportJobs( const char* pszContext, EDisplay eDisplay ) const
{
	//collect all of the job stats into a single summary table
	CUtlHashMapLarge< uintp, CGCAccessSystem::TrackedJob_t > jobs;
	FOR_EACH_MAP_FAST( m_Systems, nSystem )
	{
		const CGCAccessSystem* pSystem = m_Systems[ nSystem ];
		FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob )
		{
			const CGCAccessSystem::TrackedJob_t& job = pSystem->m_Jobs[ nJob ];
			//skip any contexts we don't care about
			if( pszContext && ( V_stricmp( pszContext, job.m_sContext ) != 0 ) )
				continue;
			if( !ShouldDisplayStats( eDisplay, job.m_Stats ) )
				continue;

			int nIndex = jobs.Find( pSystem->m_Jobs.Key( nJob ) );
			if( nIndex == jobs.InvalidIndex() )
			{
				nIndex = jobs.Insert( pSystem->m_Jobs.Key( nJob ) );
				jobs[ nIndex ].m_sContext = job.m_sContext;
			}

			jobs[ nIndex ].m_Stats.Add( job.m_Stats );
		}
	}

	CGCReportPrinter rp;
	rp.AddStringColumn( "Job" );
	rp.AddStringColumn( "Context" );
	rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total );
	rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total );
	rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total );

	FOR_EACH_MAP_FAST( jobs, nJob )
	{
		rp.StrValue( ( const char* )jobs.Key( nJob ), CFmtStr( "gcaccess_dump_job \"%s\" %d", ( const char* )jobs.Key( nJob ), eDisplay ).String() );
		rp.StrValue( jobs[ nJob ].m_sContext, CFmtStr( "gcaccess_dump_context \"%s\" %d", jobs[ nJob ].m_sContext.String(), eDisplay ).String() );
		rp.IntValue( jobs[ nJob ].m_Stats.m_nNumValid );
		rp.IntValue( jobs[ nJob ].m_Stats.m_nNumFail );
		rp.IntValue( jobs[ nJob ].m_Stats.m_nNumFailID );
		rp.CommitRow();
	}

	rp.SortReport( "Job", false );
	rp.PrintReport( SPEW_CONSOLE );
}

void CGCAccess::ReportSystems( const char* pszContext, EDisplay eDisplay ) const
{
	CGCReportPrinter rp;
	rp.AddStringColumn( "System" );
	rp.AddIntColumn( "ID", CGCReportPrinter::eSummary_None );
	rp.AddIntColumn( "Jobs", CGCReportPrinter::eSummary_None );
	rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total );
	rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total );
	rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total );

	FOR_EACH_MAP_FAST( m_Systems, nSystem )
	{
		const CGCAccessSystem* pSystem = m_Systems[ nSystem ];
		CGCAccessSystem::AccessStats_t stats;
		FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob )
		{
			const CGCAccessSystem::TrackedJob_t& job = pSystem->m_Jobs[ nJob ];
			//skip any contexts we don't care about
			if( pszContext && ( V_stricmp( pszContext, job.m_sContext ) != 0 ) )
				continue;		

			stats.Add( job.m_Stats );
		}

		if( !ShouldDisplayStats( eDisplay, stats ) )
			continue;

		rp.StrValue( pSystem->m_sName, CFmtStr( "gcaccess_dump_system \"%s\" %d", pSystem->m_sName.String(), eDisplay ).String() );
		rp.IntValue( pSystem->m_nID );
		rp.IntValue( pSystem->m_Jobs.Count() );
		rp.IntValue( stats.m_nNumValid );
		rp.IntValue( stats.m_nNumFail );
		rp.IntValue( stats.m_nNumFailID );
		rp.CommitRow();
	}

	rp.SortReport( "System", false );
	rp.PrintReport( SPEW_CONSOLE );
}

void CGCAccess::FullReport( const char* pszSystemFilter, const char* pszContextFilter, const char* pszJobFilter, EDisplay eDisplay ) const
{
	CGCReportPrinter rp;
	rp.AddStringColumn( "Job" );
	rp.AddStringColumn( "Context" );
	rp.AddStringColumn( "System" );
	rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total );
	rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total );
	rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total );

	FOR_EACH_MAP_FAST( m_Systems, nSystem )
	{
		const CGCAccessSystem* pSystem = m_Systems[ nSystem ];
		if( pszSystemFilter && V_stricmp( pszSystemFilter, pSystem->m_sName ) )
			continue;

		FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob )
		{
			const CGCAccessSystem::AccessStats_t& stats = pSystem->m_Jobs[ nJob ].m_Stats; 
			const char* pszJob = ( const char* )pSystem->m_Jobs.Key( nJob );
			const char* pszContext = ( const char* )pSystem->m_Jobs[ nJob ].m_sContext;

			if( !ShouldDisplayStats( eDisplay, stats ) )
				continue;			
			if( pszJobFilter && V_stricmp( pszJobFilter, pszJob ) )
				continue;
			if( pszContextFilter && V_stricmp( pszContextFilter, pszContext ) )
				continue;

			rp.StrValue( pszJob, CFmtStr( "gcaccess_dump_job \"%s\" %d", pszJob, eDisplay ).String() );
			rp.StrValue( pszContext, CFmtStr( "gcaccess_dump_context \"%s\" %d", pszContext, eDisplay ).String() );
			rp.StrValue( pSystem->m_sName, CFmtStr( "gcaccess_dump_system \"%s\" %d", pSystem->m_sName.String(), eDisplay ).String() );
			rp.IntValue( stats.m_nNumValid );
			rp.IntValue( stats.m_nNumFail );
			rp.IntValue( stats.m_nNumFailID );
			rp.CommitRow();
		}
	}

	rp.SortReport( "Context", false );
	rp.PrintReport( SPEW_CONSOLE );
}

void CGCAccess::DependencyReport( const char* pszSystem, EDisplay eDisplay ) const
{
	//first off, we need to find the system we are scanning for dependencies on
	const CGCAccessSystem* pMatchSystem = NULL;
	FOR_EACH_MAP_FAST( m_Systems, nSystem )
	{
		const CGCAccessSystem* pSystem = m_Systems[ nSystem ];
		if( V_stricmp( pszSystem, pSystem->m_sName ) == 0 )
		{
			pMatchSystem = pSystem;
			break;
		}
	}

	if( !pMatchSystem )
	{
		EG_MSG( SPEW_CONSOLE, "Unable to find system %s for dependency report\n", pszSystem );
		return;
	}

	//now generate our report
	CGCReportPrinter rp;
	rp.AddStringColumn( "Job" );
	rp.AddStringColumn( "Context" );
	rp.AddStringColumn( "System" );
	rp.AddIntColumn( "Valid", CGCReportPrinter::eSummary_Total );
	rp.AddIntColumn( "Invalid", CGCReportPrinter::eSummary_Total );
	rp.AddIntColumn( "InvalidID", CGCReportPrinter::eSummary_Total );

	FOR_EACH_MAP_FAST( m_Systems, nSystem )
	{
		const CGCAccessSystem* pSystem = m_Systems[ nSystem ];
		//skip ourself
		if( pSystem == pMatchSystem )
			continue;

		FOR_EACH_MAP_FAST( pSystem->m_Jobs, nJob )
		{
			const CGCAccessSystem::AccessStats_t& stats = pSystem->m_Jobs[ nJob ].m_Stats; 
			const char* pszJob = ( const char* )pSystem->m_Jobs.Key( nJob );
			const char* pszContext = ( const char* )pSystem->m_Jobs[ nJob ].m_sContext;

			//skip any job that isn't using our match system
			if( !pMatchSystem->m_Jobs.HasElement( ( uintp )pszJob ) )
				continue;
			if( !ShouldDisplayStats( eDisplay, stats ) )
				continue;	

			rp.StrValue( pszJob, CFmtStr( "gcaccess_dump_job \"%s\" %d", pszJob, eDisplay ).String() );
			rp.StrValue( pszContext, CFmtStr( "gcaccess_dump_context \"%s\" %d", pszContext, eDisplay ).String() );
			rp.StrValue( pSystem->m_sName, CFmtStr( "gcaccess_dump_system \"%s\" %d", pSystem->m_sName.String(), eDisplay ).String() );
			rp.IntValue( stats.m_nNumValid );
			rp.IntValue( stats.m_nNumFail );
			rp.IntValue( stats.m_nNumFailID );
			rp.CommitRow();
		}
	}

	rp.SortReport( "System", false );
	rp.PrintReport( SPEW_CONSOLE );


}

GC_CON_COMMAND_PARAMS( gcaccess_dump_job, 1, "<Job Name> Dumps all of the accesses associated with the specified job" )
{
	CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
	GGCAccess().FullReport( NULL, NULL, args[ 1 ], eDisplay );
}

GC_CON_COMMAND_PARAMS( gcaccess_dump_context, 1, "<Job Name> Dumps all of the accesses associated with the specified context" )
{
	CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
	GGCAccess().FullReport( NULL, args[ 1 ], NULL, eDisplay );
}

GC_CON_COMMAND_PARAMS( gcaccess_dump_system, 1, "<Job Name> Dumps all of the accesses associated with the specified system" )
{
	CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
	GGCAccess().FullReport( args[ 1 ], NULL, NULL, eDisplay );
}

GC_CON_COMMAND_PARAMS( gcaccess_dump_context_jobs, 1, "<Context Name> Dumps all of the accesses associated with the specified context" )
{
	CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
	GGCAccess().ReportJobs( args[ 1 ], eDisplay );
}

GC_CON_COMMAND_PARAMS( gcaccess_dump_context_systems, 1, "<Context Name> Dumps all of the accesses associated with the specified context" )
{
	CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
	GGCAccess().ReportSystems( args[ 1 ], eDisplay );
}

GC_CON_COMMAND_PARAMS( gcaccess_dump_system_dependencies, 1, "<System Name> This will dump out for all jobs that reference the specified system, what other systems they reference" )
{
	CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 3 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 2 ] ) : CGCAccess::eDisplay_Referenced;
	GGCAccess().DependencyReport( args[ 1 ], eDisplay );
}

GC_CON_COMMAND( gcaccess_dump_jobs, "Dumps all the jobs that have been tracked with the gcaccess system as well as count of violations" )
{
	CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 2 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 1 ] ) : CGCAccess::eDisplay_Referenced;
	GGCAccess().ReportJobs( NULL, eDisplay);
}

GC_CON_COMMAND( gcaccess_dump_systems, "Dumps all the systems that have been tracked with the gcaccess system as well as count of violations" )
{
	CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 2 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 1 ] ) : CGCAccess::eDisplay_Referenced;
	GGCAccess().ReportSystems( NULL, eDisplay );
}

GC_CON_COMMAND( gcaccess_dump_full, "Dumps all the information tracked by the gcaccess" )
{
	CGCAccess::EDisplay eDisplay = ( args.ArgC() >= 2 ) ? ( CGCAccess::EDisplay )V_atoi( args[ 1 ] ) : CGCAccess::eDisplay_Referenced;
	GGCAccess().FullReport( NULL, NULL, NULL, eDisplay );
}

GC_CON_COMMAND_PARAMS( gcaccess_catch_context, 2, "<system> <context> - Catches and generates an assert when the specified context calls into the provided system. This will only generate a single assert" )
{
	if( !GGCAccess().CatchSingleAssert( args[ 1 ], true, args[ 2 ] ) )
		EG_MSG( SPEW_CONSOLE, "Unable to register catch, likely \'%s\' is not a valid system.", args[ 1 ] );
}

GC_CON_COMMAND_PARAMS( gcaccess_catch_job, 2, "<system> <job> - Catches and generates an assert when the specified job calls into the provided system. This will only generate a single assert" )
{
	if( !GGCAccess().CatchSingleAssert( args[ 1 ], false, args[ 2 ] ) )
		EG_MSG( SPEW_CONSOLE, "Unable to register catch, likely \'%s\' is not a valid system.", args[ 1 ] );
}

GC_CON_COMMAND( gcaccess_catch_clear, "Clears all previously specified catches" )
{
	GGCAccess().ClearSingleAsserts();
}

} //namespace GCSDK