//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//
//=============================================================================//
// vmpi_launch.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "iphelpers.h"
#include "bitbuf.h"
#include "vmpi.h"

bool g_bBroadcast = false;

int PrintUsage()
{
	printf( "vmpi_launch -machine <remote machine> -priority <priority> [-mpi_pw <password>] -command \"command line...\"\n" );
	printf( "-command must be the last switch..\n" );
	return 1;
}


int GetCurMicrosecondsAndSleep( int sleepLen )
{
	Sleep( sleepLen );

	int retVal;
	__asm
	{
		rdtsc
		mov		retVal,   eax
	}
	return retVal;
}


const char* FindArg( int argc, char **argv, const char *pName, const char *pDefault )
{
	for ( int i=0; i < argc; i++ )
	{
		if ( stricmp( argv[i], pName ) == 0 )
		{
			if ( (i+1) < argc )
				return argv[i+1];
			else
				return pDefault;
		}
	}
	return NULL;
}


int ParseArgs( int argc, char **argv, CIPAddr &remoteIP, int &iPriority, int &iFirstArg )
{
	if ( FindArg( argc, argv, "-broadcast", "1" ) )
		 g_bBroadcast = true;

	if ( g_bBroadcast == false )
	{
		const char *pRemoteIPStr = FindArg( argc, argv, "-machine", NULL );
		if ( !pRemoteIPStr || !ConvertStringToIPAddr( pRemoteIPStr, &remoteIP ) )
		{
			printf( "%s is not a valid machine name or IP address.\n", pRemoteIPStr );
			return PrintUsage();
		}
	}
	
	iPriority = 0;
	const char *pPriorityStr = FindArg( argc, argv, "-priority", NULL );
	if ( pPriorityStr )
		iPriority = atoi( pPriorityStr );
	
	if ( iPriority < 0 || iPriority > 1000 )
	{
		printf( "%s is not a valid priority.\n", pPriorityStr );
		return PrintUsage();
	}

	const char *pCommand = FindArg( argc, argv, "-command", NULL );
	if ( !pCommand )
	{
		return PrintUsage();
	}
	for ( iFirstArg=1; iFirstArg < argc; iFirstArg++ )
	{
		if ( argv[iFirstArg] == pCommand )
			break;
	}

	return 0;
}


void SendJobRequest( 
	ISocket *pSocket,
	int argc, 
	char **argv, 
	CIPAddr &remoteIP, 
	int &iPriority, 
	int &iFirstArg,
	int jobID[4] )
{
	// Build the packet to send out the job.
	char packetData[4096];
	bf_write packetBuf;
	
	// Come up with a unique job ID.
	jobID[0] = GetCurMicrosecondsAndSleep( 1 );
	jobID[1] = GetCurMicrosecondsAndSleep( 1 );
	jobID[2] = GetCurMicrosecondsAndSleep( 1 );
	jobID[3] = GetCurMicrosecondsAndSleep( 1 );

	
	// Broadcast out to tell all the machines we want workers.
	packetBuf.StartWriting( packetData, sizeof( packetData ) );
	packetBuf.WriteByte( VMPI_PROTOCOL_VERSION );

	const char *pPassword = FindArg( argc, argv, "-mpi_pw", "" );
	packetBuf.WriteString( pPassword );
	
	packetBuf.WriteByte( VMPI_LOOKING_FOR_WORKERS );
	
	packetBuf.WriteShort( 0 );	// Tell the port that we're listening on.
								// In this case, there is no VMPI master waiting for the app to connect, so
								// this parameter doesn't matter.
	packetBuf.WriteShort( iPriority );

	packetBuf.WriteLong( jobID[0] );
	packetBuf.WriteLong( jobID[1] );
	packetBuf.WriteLong( jobID[2] );
	packetBuf.WriteLong( jobID[3] );
	packetBuf.WriteWord( argc-iFirstArg ); // 1 command line argument..

	// Write the alternate exe name.
	for ( int iArg=iFirstArg; iArg < argc; iArg++ )
		packetBuf.WriteString( argv[iArg] );

	for ( int iBroadcastPort=VMPI_SERVICE_PORT; iBroadcastPort <= VMPI_LAST_SERVICE_PORT; iBroadcastPort++ )
	{
		remoteIP.port = iBroadcastPort;

		if ( g_bBroadcast == false )
			 pSocket->SendTo( &remoteIP, packetBuf.GetBasePointer(), packetBuf.GetNumBytesWritten() );
		else
			 pSocket->Broadcast( packetBuf.GetBasePointer(), packetBuf.GetNumBytesWritten(), iBroadcastPort );
	}

	if ( g_bBroadcast == false )
		 printf( "Sent command, waiting for reply...\n" );
	else
		 printf( "Sent command\n" );
}


bool WaitForJobStart( ISocket *pSocket, const CIPAddr &remoteIP, const int jobID[4] )
{
	while ( 1 )
	{
		CIPAddr senderAddr;
		char data[4096];
		int len = -1;
		
		if ( g_bBroadcast == false )
			 pSocket->RecvFrom( data, sizeof( data ), &senderAddr );
		else 
			 pSocket->RecvFrom( data, sizeof( data ), NULL );

		if ( len == 19 && 
			memcmp( senderAddr.ip, remoteIP.ip, sizeof( senderAddr.ip ) ) == 0 &&
			data[1] == VMPI_NOTIFY_START_STATUS &&
			memcmp( &data[2], jobID, 16 ) == 0 )
		{
			if ( data[18] == 0 )
			{
				// Wasn't able to run.
				printf( "Wasn't able to run on target machine.\n" );
				return false;
			}
			else
			{
				// Ok, the process is running now.
				printf( "Process running, waiting for completion...\n" );
				return true;
			}
		}

		Sleep( 100 );
	}		
}


void WaitForJobEnd( ISocket *pSocket, const CIPAddr &remoteIP, const int jobID[4] )
{
	while ( 1 )
	{
		CIPAddr senderAddr;
		char data[4096];
		int len = pSocket->RecvFrom( data, sizeof( data ), &senderAddr );
		if ( len == 18 && 
			memcmp( senderAddr.ip, remoteIP.ip, sizeof( senderAddr.ip ) ) == 0 &&
			data[1] == VMPI_NOTIFY_END_STATUS &&
			memcmp( &data[2], jobID, 16 ) == 0 )
		{
			int ret = *((int*)&data[2]);
			printf( "Finished [%d].\n", ret );
			break;
		}

		Sleep( 100 );
	}		
}


int main(int argc, char* argv[])
{
	if ( argc < 4 )
	{
		return PrintUsage();
	}

	
	// Parse the command line.
	CIPAddr remoteIP;
	int iFirstArg, iPriority;
	int jobID[4];

	int ret = ParseArgs( argc, argv, remoteIP, iPriority, iFirstArg );
	if ( ret != 0 )
		return ret;

	// Now send the command to the vmpi service on that machine.
	ISocket *pSocket = CreateIPSocket();
	if ( !pSocket->BindToAny( 0 ) )
	{
		printf( "Error binding a socket.\n" );
		return 1;
	}
	
	SendJobRequest( pSocket, argc, argv, remoteIP, iPriority, iFirstArg, jobID );

	// Wait for a reply, positive or negative.
	if ( g_bBroadcast == false )
	{
		if ( !WaitForJobStart( pSocket, remoteIP, jobID ) )
			return 2;

		WaitForJobEnd( pSocket, remoteIP, jobID );
	}
	
	pSocket->Release();
	return 0;
}