// CommandLauncher.cpp : Defines the entry point for the application.
//

#include "stdafx.h"
#include "CommandLauncher.h"
#include "string.h"
#include <stdio.h>
#include <stdlib.h>
#include <conio.h>
#include <process.h>
#include <errno.h>

WCHAR **    G_rgArgs; // Command line arguments
int			G_cArg; // Count of command line arguments
FILE *		G_pf; 

// Forward declarations of functions included in this code module:
int processArguments(LPTSTR lpCmdLine);
void freeArgs();
int countArgs(LPTSTR lpCmdLine);
int addArg(int iArg, LPTSTR lpString, int cChar);
void log(LPTSTR szMessage);
_TCHAR * errorMessage(int err);
int executeCommand( int cArg, _TCHAR* rgArg[]);


#define ERROR_NO_ERROR							0
#define ERROR_MEMORY_ALLOCATION_FAILED			-1
#define ERROR_INVALID_NUMBER_OF_PARAMETERS		-2
#define ERROR_EXEC_E2BIG						-3
#define ERROR_EXEC_EACCES						-4 
#define ERROR_EXEC_EINVAL						-5
#define ERROR_EXEC_EMFILE						-6
#define ERROR_EXEC_ENOENT						-7
#define ERROR_EXEC_ENOEXEC						-8
#define ERROR_EXEC_ENOMEM						-9
#define ERROR_EXEC_UNKNOWN						-10
#define ERROR_STRCPY_FAILED						-11
#define ERROR_JAVA_EXE_ARG_MISSING				-12
#define ERROR_JAVA_CLASSPATH_OPTION_ARG_MISSING	-13
#define ERROR_JAVA_CLASSPATH_ARG_MISSING		-14
#define ERROR_BAD_COMMAND_LINE					-15



int APIENTRY _tWinMain(HINSTANCE hInstance,
                     HINSTANCE hPrevInstance,
                     LPTSTR    lpCmdLine,
                     int       nCmdShow)
{
	UNREFERENCED_PARAMETER(hInstance);
	UNREFERENCED_PARAMETER(hPrevInstance);
	UNREFERENCED_PARAMETER(lpCmdLine);
	UNREFERENCED_PARAMETER(nCmdShow);

	int rc;

	_wfopen_s(&G_pf, L"C:\\CommandLauncher.log", L"a+");

	// Process the command line
	if (ERROR_NO_ERROR != (rc = processArguments(lpCmdLine)))
	{
		return rc;
	}

	rc = executeCommand(G_cArg, G_rgArgs);

	log(errorMessage(rc));

	fwprintf(G_pf, L"CommandLauncher: return = %d\n", rc);

	fclose(G_pf);

	return rc;
}


// <java exe path> -cp <classpath> class k1=v1 k2=k2
int processArguments(LPTSTR lpCmdLine)
{
	int		iArg;
	int		iChar;
	int		iAssignment;
	int		iClassStart;
	int		iClassEnd;
	int		iClassPathStart;
	int		iClassPathEnd;
	int		rc;
	bool	fClasspathOptionFound = false;
	int		iKeyStart;
	int		iValueEnd;

	fwprintf( G_pf, L"current command line = %s\n", lpCmdLine);


	// Validate the command line
	if (NULL == lpCmdLine || (WCHAR)0 == *lpCmdLine)
	{
		return ERROR_BAD_COMMAND_LINE;
	}

	// Count the arguments on the command line.  TThe name of this executable
	// is not included in the count.
	G_cArg = countArgs(lpCmdLine);

	// Make sure we got enough to exec something.  There must be at least the
	// path to jave.exe, the classpath option, the classpath and a class.
	if (G_cArg < 4) 
	{
		return ERROR_INVALID_NUMBER_OF_PARAMETERS;
	} 

	// Allocate an array of wide string for the arguments, add 1 for a NULL at
	// the end of the array
	G_rgArgs = (WCHAR**)malloc((G_cArg + 1) * sizeof(WCHAR *));
	if (NULL == G_rgArgs)
	{
		return ERROR_MEMORY_ALLOCATION_FAILED;
	}

	// Null out the array
	memset(G_rgArgs, 0, (G_cArg + 1) * sizeof(WCHAR *)); 

	// Find the java.exe argument
	iChar = 0;
	for (iChar = 0; 0 != lpCmdLine[iChar + 4]; iChar++)
	{
		if (0 == _wcsnicmp(lpCmdLine + iChar, L".exe", 4))
		{
			break;
		}
	}
	if (0 == lpCmdLine[iChar + 4])
	{
		rc = ERROR_JAVA_EXE_ARG_MISSING;
		goto ErrorOut;
	}

	// Add the java.exe argument
	if (ERROR_NO_ERROR != (rc = addArg(0, lpCmdLine, iChar + 4)))
	{
		goto ErrorOut;
	}

	// Move past the java.exe argument
	iChar += 4;

	// Move past any spaces
	for (;L' ' == lpCmdLine[iChar]; iChar++)
	{
		// Intentionally left blank
	}

	// Find the classpath argument
	for (; 0 != lpCmdLine[iChar + 3]; iChar++)
	{
		if (0 == _wcsnicmp(lpCmdLine + iChar, L"-cp", 3))
		{
			fClasspathOptionFound = true;
			break;
		}
	}
	if (!fClasspathOptionFound)
	{
		rc = ERROR_JAVA_CLASSPATH_OPTION_ARG_MISSING;
		goto ErrorOut;
	}
	if (0 == lpCmdLine[iChar + 3])
	{
		rc = ERROR_JAVA_CLASSPATH_ARG_MISSING;
		goto ErrorOut;
	}

	// Add the classpath option argument
	if (ERROR_NO_ERROR != (rc = addArg(1, L"-cp", iChar + 3)))
	{
		goto ErrorOut;
	}

	// Move past the classpath option argument
	iChar += 3;

	// Move past any spaces
	for (;L' ' == lpCmdLine[iChar]; iChar++)
	{
		// Intentionally left blank
	}

	// The classpath is next.  It can have spaces in it so we need to work 
	// backards from the first key/value pair, or the end of the line if
	// there are no key/value pairs.
	iClassPathStart = iChar;

	// Find the location of the next '='
	for (; 0 != lpCmdLine[iChar] && L'=' != lpCmdLine[iChar]; iChar++)
	{
		// Intentially left blank
	}

	// If there was a key/value pair - move to the start of the key
	if (L'=' == lpCmdLine[iChar])
	{
		iAssignment = iChar;

		// Move back to the previous space.  This should put us at the 
		// beginning of the first key/value pair.  Assume that all args 
		// are property key/value pairs and property keys have no spaces.
		for (; L' ' != lpCmdLine[iChar] && iChar >= 0; iChar--)
		{
			// Intentially left blank
		}
	}

	else
	{
		iAssignment = 0;
		iChar--;
	}

	// Move past any spaces (moving toward the start of the line)
	for (;L' ' == lpCmdLine[iChar]; iChar--)
	{
		// Intentionally left blank
	}

	// This should put us at the end of the class to be executed
	iClassEnd = iChar;

	// Move to the previous space (moving toward the start of the line)
	for (;L' ' != lpCmdLine[iChar]; iChar--)
	{
		// Intentionally left blank
	}

	iClassStart = iChar + 1;

	// Add the class argument
	if (ERROR_NO_ERROR != (rc = addArg(3, lpCmdLine + iClassStart, iClassEnd - iClassStart + 1)))
	{
		goto ErrorOut;
	}

	// Move past any spaces (moving toward the start of the line)
	for (;L' ' == lpCmdLine[iChar]; iChar--)
	{
		// Intentionally left blank
	}

	// This should put us at the end of the classpath
	iClassPathEnd = iChar;

	// Add the class path argument
	if (ERROR_NO_ERROR != (rc = addArg(2, lpCmdLine + iClassPathStart, iClassPathEnd - iClassPathStart + 1)))
	{
		goto ErrorOut;
	}

	// Are the any key/value pairs?
	if (0 != iAssignment)
	{
		iArg = 4;
		while (0 != lpCmdLine[iAssignment])
		{
			iKeyStart = iAssignment;

			// Move back to the previous space.  This should put us at the 
			// beginning of the current next key/value pair.  Assume that all args 
			// are property key/value pairs and property keys have no spaces.
			for (; L' ' != lpCmdLine[iKeyStart] && iKeyStart > 0; iKeyStart--)
			{
				// Intentially left blank
			}
			if (L' ' == lpCmdLine[iKeyStart])
			{
				iKeyStart++;
			}

			// Find the location of the next '='
			iValueEnd = iAssignment + 1;
			for (; 0 != lpCmdLine[iValueEnd] && L'=' != lpCmdLine[iValueEnd]; iValueEnd++)
			{
				// Intentially left blank
			}

			// If there was a property...
			if (L'=' == lpCmdLine[iValueEnd])
			{
				iAssignment = iValueEnd;

				// Move back to the previous space.  This should put us at the 
				// beginning of the next key/value pair.  Assume that all args 
				// are property key/value pairs and property keys have no spaces.
				for (; L' ' != lpCmdLine[iValueEnd] && iValueEnd >= 0; iValueEnd--)
				{
					// Intentially left blank
				}
			}

			else
			{
				// We have reached the end of the command line - back off from the 
				// null terminator.
				iAssignment = iValueEnd;
				iValueEnd--;
			}

			// Move thorugh any spaces
			for (; L' ' == lpCmdLine[iValueEnd] && iValueEnd >= 0; iValueEnd--)
			{
				// Intentially left blank
			}

			// Add the key/value pair
			if (ERROR_NO_ERROR != (rc = addArg(iArg, lpCmdLine + iKeyStart, iValueEnd - iKeyStart + 1)))
			{
				goto ErrorOut;
			}

			// Go on to the next arg
			iArg++;
		}
	}

	return ERROR_NO_ERROR;

ErrorOut:

	freeArgs();
	log(errorMessage(rc));
	return rc;
}

void freeArgs()
{
	int iArg;
	if (NULL != G_rgArgs)
	{
		for (iArg = 0; iArg < G_cArg; iArg++)
		{
			if (NULL != G_rgArgs[iArg])
			{
				free(G_rgArgs[iArg]);
				G_rgArgs[iArg] = NULL;
			}
		}
		free(G_rgArgs);
		G_rgArgs = NULL;
	}
}

int countArgs(LPTSTR lpCmdLine)
{
	int cArgument;

	// Check if the exe to execute is the only argument.  Assume that all additional
	// arguments have an '=' in them.
	for (cArgument = 4; *lpCmdLine != (WCHAR)0; lpCmdLine++)
	{
		if (*lpCmdLine == L'=')
		{
			cArgument++;
		}
	}
	return cArgument;
}

int addArg(int iArg, LPTSTR lpString, int cChar)
{
	int cb = (cChar + 3) * sizeof(WCHAR); // count of bytes 

	// Allocate space for the new arg
	G_rgArgs[iArg] = (WCHAR *)malloc(cb);
	if (NULL == G_rgArgs[iArg])
	{
		return ERROR_MEMORY_ALLOCATION_FAILED;
	}

	// Null out the argument
	memset(G_rgArgs[iArg], 0, cb); 

	// Add a starting quote
//	G_rgArgs[iArg][0] = L'\"';

	// Copy the arg
	if (0 != wcsncpy_s(G_rgArgs[iArg], cChar + 1, lpString, cChar))
	{
		return ERROR_STRCPY_FAILED;
	}

	// Add a terminating quote
//	G_rgArgs[iArg][cb-1] = L'\"';

	return ERROR_NO_ERROR;
}


void log(LPTSTR szMessage)
{
	LPTSTR szT = L"";
	if (NULL == szMessage)
		szMessage = szT;
	fwprintf(G_pf, L"JavaLauncher: %s\n", szMessage);
}

_TCHAR * errorMessage(int err)
{
	switch (err)
	{
	case ERROR_NO_ERROR:
		return L"No error\n";
	case ERROR_MEMORY_ALLOCATION_FAILED:
		return L"Memory allocation failed\n";
	case ERROR_INVALID_NUMBER_OF_PARAMETERS:
		return L"Invalid number of parameters\n";
	case ERROR_EXEC_E2BIG:
		return L"_exec: The space required for the arguments and environment settings exceeds 32 KB.\n";
	case ERROR_EXEC_EACCES:
		return L"_exec: The specified file has a locking or sharing violation.\n";
	case ERROR_EXEC_EINVAL:
		return L"_exec: Invalid parameter.\n";
	case ERROR_EXEC_EMFILE:	
		return L"_exec: Too many files open (the specified file must be opened to determine whether it is executable).\n";
	case ERROR_EXEC_ENOENT:	
		return L"_exec: The file or path not found.\n";
	case ERROR_EXEC_ENOEXEC:	
		return L"_exec: The specified file is not executable or has an invalid executable-file format.\n";
	case ERROR_EXEC_ENOMEM:	
		return L"_exec: Not enough memory is available to execute the new process; the available memory has been corrupted; or an invalid block exists, indicating that the calling process was not allocated properly.\n";
	case ERROR_EXEC_UNKNOWN:
		return L"Unknown _exec error.\n";
	case ERROR_STRCPY_FAILED:
		return L"String copy failed.\n";
	case ERROR_JAVA_CLASSPATH_OPTION_ARG_MISSING:
		return L"Classpath option \"-cp\" missing\n";
	case ERROR_JAVA_CLASSPATH_ARG_MISSING:
		return L"Classpath argument missing\n";
	case ERROR_BAD_COMMAND_LINE:
		return L"Bad command line\n";
	default:
		return L"Unknown error.\n";
	}
}

int executeCommand( int cArg, _TCHAR* rgArg[] )
{
	int i;						// Looping variable
	int rc = ERROR_NO_ERROR;	// Return code

	fwprintf( G_pf, L"Arg count = %d\n", cArg);
	for (i = 0; i < cArg; i++)
	{
		fwprintf(G_pf, L"rgArg[%d] (%s)\n", i, rgArg[i]);
	}

	// exec the command
//	if (-1 == _wexecv( rgArg[0], rgArg))
	if (-1 == _wspawnv(_P_WAIT, rgArg[0], rgArg))
	{
		switch (errno)
		{
		case E2BIG:		// The space required for the arguments and environment settings exceeds 32 KB.
			rc = ERROR_EXEC_E2BIG;
			break;
	
		case EACCES:	// The specified file has a locking or sharing violation.
			rc = ERROR_EXEC_EACCES;
			break;
 
		case EINVAL:	// Invalid parameter.
			rc = ERROR_EXEC_EINVAL;
			break;
 
		case EMFILE:	// Too many files open (the specified file must be opened to determine whether it is executable).
			rc = ERROR_EXEC_EMFILE;
			break;
 
		case ENOENT:	// The file or path not found.
			rc = ERROR_EXEC_ENOENT;
			break;
 
		case ENOEXEC:	// The specified file is not executable or has an invalid executable-file format.
			rc = ERROR_EXEC_ENOEXEC;
			break;
 
		case ENOMEM:	// Not enough memory is available to execute the new process; the available memory has been 
						// corrupted; or an invalid block exists, indicating that the calling process was not allocated 
						// properly.
			rc = ERROR_EXEC_ENOMEM;
			break;
 
		default:
			rc = ERROR_EXEC_UNKNOWN;
			break;
		}
	}

	fwprintf(G_pf, L"ExecuteCommand returning %d\n", rc);
	return rc;
}