//========= Copyright Valve Corporation, All rights reserved. ============//
//
//	REMOTE_CMDS.CPP
//
//	Remote commands received by an external application and dispatched.
//=====================================================================================//
#include "vxconsole.h"

remoteCommand_t		*g_remoteCommands[MAX_RCMDS];
int					g_numRemoteCommands;

//-----------------------------------------------------------------------------
//	MatchRemoteCommands
//
//-----------------------------------------------------------------------------
int MatchRemoteCommands( char *pCmdStr, const char *cmdList[], int maxCmds )
{
	int numCommands = 0;

	// look in local
	int matchLen = strlen( pCmdStr );
	for ( int i=0; i<g_numRemoteCommands; i++ )
	{
		if ( !strnicmp( pCmdStr, g_remoteCommands[i]->strCommand, matchLen ) )
		{
			cmdList[numCommands++] = g_remoteCommands[i]->strCommand;
			if ( numCommands >= maxCmds )
				break;
		}
	}

	return ( numCommands );
}

//-----------------------------------------------------------------------------
// GetToken
//
//-----------------------------------------------------------------------------
char *GetToken( char **ppTokenStream )
{
	static char token[MAX_TOKENCHARS];
	int			len;
	char		c;
	char		*pData;

	len = 0;
	token[0] = 0;	

	if ( !ppTokenStream )
		return NULL;

	pData = *ppTokenStream;		

	// skip whitespace
skipwhite:
	while ( ( c = *pData ) <= ' ' )
	{
		if ( !c )
			goto cleanup;
		pData++;
	}
	
	// skip // comments
	if ( c=='/' && pData[1] == '/' )
	{
		while ( *pData && *pData != '\n' )
			pData++;
		goto skipwhite;
	}
	
	// handle quoted strings specially
	if ( c == '\"' )
	{
		pData++;
		while ( 1 )
		{
			c = *pData++;
			if ( c=='\"' || !c )
				goto cleanup;

			token[len] = c;
			len++;
			if ( len > MAX_TOKENCHARS-1 )
				goto cleanup;
		}
	}

	// parse a regular word
	do
	{
		token[len] = c;
		pData++;
		len++;
		if ( len > MAX_TOKENCHARS-1 )
			break;
		c = *pData;
	}
	while ( c > ' ' && c <= '~' );
	
cleanup:
	token[len] = 0;
	*ppTokenStream = pData;
	return ( token );
}

//-----------------------------------------------------------------------------
// CommandCompleted
//
//-----------------------------------------------------------------------------
void CommandCompleted( int errCode )
{
	char	cmdString[MAX_PATH];

	// send command complete
	sprintf( cmdString, "%s!__complete__%d", VXCONSOLE_COMMAND_PREFIX, errCode );
	DmAPI_SendCommand( cmdString, true );
}

//-----------------------------------------------------------------------------
// DebugCommand
//-----------------------------------------------------------------------------
void DebugCommand( const char *pStrFormat, ... )
{
	char	buffer[MAX_QUEUEDSTRINGLEN];
	va_list arglist;

	if ( !g_debugCommands )
		return;

    va_start( arglist, pStrFormat );
    _vsnprintf( buffer, MAX_QUEUEDSTRINGLEN, pStrFormat, arglist );
    va_end( arglist );

	PrintToQueue( RGB( 0,128,0 ), "[CMD]: %s", buffer );
}

//-----------------------------------------------------------------------------
// Remote_NotifyPrintFunc
//
//-----------------------------------------------------------------------------
DWORD __stdcall Remote_NotifyPrintFunc( const CHAR *pStrNotification )
{
	int	color;

	if ( !strnicmp( pStrNotification, VXCONSOLE_PRINT_PREFIX, strlen( VXCONSOLE_PRINT_PREFIX ) ) )
	{
		// skip past prefix!
		pStrNotification += strlen( VXCONSOLE_PRINT_PREFIX )+1;
	}

	color = XBX_CLR_DEFAULT;

	if ( !strnicmp( pStrNotification, VXCONSOLE_COLOR_PREFIX, strlen( VXCONSOLE_COLOR_PREFIX ) ) )
	{
		// skip past prefix[12345678]
		char	buff[16];
		pStrNotification += strlen( VXCONSOLE_COLOR_PREFIX );
		memcpy( buff, pStrNotification, 10 );
		if ( buff[0] == '[' && buff[9] == ']' )
		{
			buff[0] = ' ';
			buff[9] = ' ';
			buff[10] = '\0';

			sscanf( buff, "%x", &color );
			pStrNotification += 10;
		}
	}

    PrintToQueue( color, "%s\n", pStrNotification );
    return S_OK;
}

//-----------------------------------------------------------------------------
// Remote_NotifyDebugString
//
// Print as [DBG]:xxxx
//-----------------------------------------------------------------------------
DWORD __stdcall Remote_NotifyDebugString( ULONG dwNotification, DWORD dwParam )
{
    if ( g_captureDebugSpew )
    {
        PDMN_DEBUGSTR p = ( PDMN_DEBUGSTR )dwParam;
		int len;

		// strip all terminating cr
		len = p->Length-1;
		while ( len > 0 )
		{
			if ( p->String[len] != '\n' )
			{
				len++;
				break;
			}
			len--;
		}

		// for safety, terminate
		CHAR* strTemp = new CHAR[len+1];
		memcpy( strTemp, p->String, len*sizeof( CHAR ) );
		strTemp[len] = '\0';

		PrintToQueue( RGB( 0,0,255 ), "[DBG]: %s\n", strTemp );

		delete[] strTemp;
    }

    // Don't let the compiler complain about unused parameters
    ( VOID )dwNotification;

    return 0;
}

//-----------------------------------------------------------------------------
//	Remote_CompareCommands
//
//-----------------------------------------------------------------------------
int Remote_CompareCommands( const void *pElem1, const void *pElem2 )
{
	remoteCommand_t	*pCmd1;
	remoteCommand_t	*pCmd2;

	pCmd1 = *( remoteCommand_t** )( pElem1 );
	pCmd2 = *( remoteCommand_t** )( pElem2 );

	return ( strcmp( pCmd1->strCommand, pCmd2->strCommand ) );
}

//-----------------------------------------------------------------------------
//	Remote_DeleteCommands
//
//-----------------------------------------------------------------------------
void Remote_DeleteCommands()
{
	if ( !g_numRemoteCommands )
		return;

	for ( int i=0; i<g_numRemoteCommands; i++ )
	{
		delete [] g_remoteCommands[i]->strCommand;
		delete [] g_remoteCommands[i]->strHelp;
		delete g_remoteCommands[i];
	
		g_remoteCommands[i] = NULL;
	}
	
	g_numRemoteCommands = 0;
}

//-----------------------------------------------------------------------------
//	Remote_AddCommand
//
//-----------------------------------------------------------------------------
bool Remote_AddCommand( char *command, char *helptext )
{
	if ( g_numRemoteCommands == MAX_RCMDS )
	{
		// full
		return false;
	}

	// look for duplicate
	int i;
	for ( i = 0; i < g_numRemoteCommands; i++ )
	{
		if ( !stricmp( command, g_remoteCommands[i]->strCommand ) )
			break;
	}
	if ( i < g_numRemoteCommands )
	{
		// already in list, skip - not an error
		return true;
	}

	// add new command to list
	g_remoteCommands[g_numRemoteCommands] = new remoteCommand_t;
	g_remoteCommands[g_numRemoteCommands]->strCommand = new char[strlen( command )+1];
	strcpy( g_remoteCommands[g_numRemoteCommands]->strCommand, command );
		
	g_remoteCommands[g_numRemoteCommands]->strHelp = new char[strlen( helptext )+1];
	strcpy( g_remoteCommands[g_numRemoteCommands]->strHelp, helptext );

	g_numRemoteCommands++;

	// success
	return true;
}

//-----------------------------------------------------------------------------
//	rc_AddCommands
//
//	Exposes an app's list of remote commands
//-----------------------------------------------------------------------------
int rc_AddCommands( char *commandPtr )
{
	char*			cmdToken;
	int				numCommands;
	int				cmdList;
	int				retAddr;
	int				retVal;
	int				errCode;
	xrCommand_t*	locallist;

	errCode = -1;

	// pacifier for lengthy operation
	ConsoleWindowPrintf( RGB( 0, 0, 0 ), "Receiving Console Commands From Game..." );

	// get number of commands
	cmdToken = GetToken( &commandPtr );
	if ( !cmdToken[0] )
		goto cleanUp;
	sscanf( cmdToken, "%x", &numCommands );

	// get command list
	cmdToken = GetToken( &commandPtr );
	if ( !cmdToken[0] )
		goto cleanUp;
	sscanf( cmdToken, "%x", &cmdList );

	// get retAddr
	cmdToken = GetToken( &commandPtr );
	if ( !cmdToken[0] )
		goto cleanUp;
	sscanf( cmdToken, "%x", &retAddr );

	locallist = new xrCommand_t[numCommands];
	memset( locallist, 0, numCommands*sizeof( xrCommand_t ) );

	// get the caller's command list 
	DmGetMemory( ( void* )cmdList, numCommands*sizeof( xrCommand_t ), locallist, NULL );

	int numAdded = 0;
	for ( int i=0; i<numCommands; i++ )
	{
		if ( Remote_AddCommand( locallist[i].nameString, locallist[i].helpString ) )
			numAdded++;
	}

	// sort the list
	qsort( g_remoteCommands, g_numRemoteCommands, sizeof( remoteCommand_t* ), Remote_CompareCommands );

	ConsoleWindowPrintf( RGB( 0, 0, 0 ), "Completed.\n" );

	// return the result
	retVal = numAdded;
	int xboxRetVal = BigDWord( retVal );
	DmSetMemory( ( void* )retAddr, sizeof( int ), &xboxRetVal, NULL );

	DebugCommand( "0x%8.8x = AddCommands( 0x%8.8x, 0x%8.8x )\n", retVal, numCommands, cmdList );

	delete [] locallist;

	// success
	errCode = 0;

	if ( g_bPlayTestMode )
	{
		if ( g_connectedToApp )
		{
			// send the developer command
			ProcessCommand( "developer 1" );
		}
	}

cleanUp:	
	return ( errCode );
}

//-----------------------------------------------------------------------------
// Remote_NotifyCommandFunc
//
//-----------------------------------------------------------------------------
DWORD __stdcall Remote_NotifyCommandFunc( const CHAR *strNotification )
{
	CHAR*	commandPtr;
	CHAR*	cmdToken;
	int		errCode;
	bool	async;

	// skip over the command prefix and the exclamation mark
    strNotification += strlen( VXCONSOLE_COMMAND_PREFIX ) + 1;
	commandPtr = ( CHAR* )strNotification;	

	// failure until otherwise
	errCode = -1;

	// default synchronous
	async = false;

	cmdToken = GetToken( &commandPtr );

	if ( cmdToken && !stricmp( cmdToken, "AddCommands()" ) )
	{
		errCode = rc_AddCommands( commandPtr );
		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "SetProfile()" ) )
	{
		// first arg dictates routing
		cmdToken = GetToken( &commandPtr );
		if ( cmdToken && !stricmp( cmdToken, "cpu" ) )
			errCode = rc_SetCpuProfile( commandPtr );
		else if ( cmdToken && !stricmp( cmdToken, "texture" ) )
			errCode = rc_SetTexProfile( commandPtr );

		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "SetProfileData()" ) )
	{
		// first arg dictates routing
		cmdToken = GetToken( &commandPtr );
		if ( cmdToken && !stricmp( cmdToken, "cpu" ) )
			errCode = rc_SetCpuProfileData( commandPtr );
		else if ( cmdToken && !stricmp( cmdToken, "texture" ) )
			errCode = rc_SetTexProfileData( commandPtr );

		async = true;
		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "TextureList()" ) )
	{
		errCode = rc_TextureList( commandPtr );
		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "MaterialList()" ) )
	{
		errCode = rc_MaterialList( commandPtr );
		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "SoundList()" ) )
	{
		errCode = rc_SoundList( commandPtr );
		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "TimeStampLog()" ) )
	{
		errCode = rc_TimeStampLog( commandPtr );
		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "MemDump()" ) )
	{
		errCode = rc_MemDump( commandPtr );
		async = true;
		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "MapInfo()" ) )
	{
		errCode = rc_MapInfo( commandPtr );
		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "Assert()" ) )
	{
		errCode = rc_Assert( commandPtr );
		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "FreeMemory()" ) )
	{
		errCode = rc_FreeMemory( commandPtr );
		async = true;
		goto cleanUp;
	}
	else if ( cmdToken && !stricmp( cmdToken, "Disconnect()" ) )
	{
		// disconnect requires specialized processing
		// send command status first while connection valid, then do actual disconnect
		// disconnect is always assumed to be valid, can't be denied
		CommandCompleted( 0 );
		DoDisconnect( TRUE );
		return S_OK;
	}
	else
	{
		// unknown command
	    PrintToQueue( RGB( 255,0,0 ), "Unknown Command: %s\n", strNotification );
		goto cleanUp;
	}

cleanUp:
	if ( !async )
		CommandCompleted( errCode );

	return ( S_OK );
}