/* Sizer.c - Sizes files from INF file. */

#include <windows.h>
#include <stdio.h>
#include <crtdbg.h>
#include "support.h"
#include "disklist.h"

static BOOL FileSize(const char * const pszPath, UINT * const pcbFile)
{
	BOOL fSuccess = FALSE;

	FILE * pFile = fopen(pszPath, "r");
	_ASSERT(pFile != NULL);
	if (pFile == NULL)
	{
		perror(NULL);
		goto Error;
	}

	fseek(pFile, 0, SEEK_END);
	*pcbFile = ftell(pFile);

	fclose(pFile);

	fSuccess = TRUE;

Error:

	return fSuccess;
}

static BOOL FileVersion(const char * const pszPath, WORD * const pa, WORD * const pb, WORD * const pc, WORD * const pd)
{
	BOOL fSuccess = FALSE;

	_ASSERT(pszPath != NULL);
	_ASSERT(pa != NULL);
	_ASSERT(pb != NULL);
	_ASSERT(pc != NULL);
	_ASSERT(pd != NULL);

	if ((pszPath != NULL) &&
	    (pa != NULL) &&
	    (pb != NULL) &&
	    (pc != NULL) &&
	    (pd != NULL))
	{
		DWORD dwNotUsed = 0;
		DWORD cbVerInfo = GetFileVersionInfoSize((LPTSTR)pszPath, &dwNotUsed);

		if (cbVerInfo != 0)
		{
			void * pvi = malloc(cbVerInfo);

			_ASSERT(pvi != NULL);
			if (pvi != NULL)
			{
				if (GetFileVersionInfo((LPTSTR)pszPath, 0, cbVerInfo, pvi))
				{
					VS_FIXEDFILEINFO * pffi = NULL;
					UINT cbffi = 0;

					if (VerQueryValue(pvi, "\\", &pffi, &cbffi))
					{
						_ASSERT(pffi != NULL);
						_ASSERT(cbffi == sizeof(*pffi));

						*pa = HIWORD(pffi->dwFileVersionMS);
						*pb = LOWORD(pffi->dwFileVersionMS);
						*pc = HIWORD(pffi->dwFileVersionLS);
						*pd = LOWORD(pffi->dwFileVersionLS);

						fSuccess = TRUE;
					}
				}

				free(pvi);
			}
		}
	}

	return fSuccess;
}

static BOOL GetFilePath(int const nDisk, char const * const pszFileToFind, char * const pszPath, size_t const cchPath)
{
	BOOL fSuccess = FALSE;
	FILE * pOpenTest = NULL;

	char szPath[MAX_PATH];

	_ASSERT(pszFileToFind != NULL);
	_ASSERT(strlen(pszFileToFind) < ElementCount(szPath));
	_ASSERT(pszPath != NULL);
	_ASSERT(cchPath > 0);
	_ASSERT(cchPath <= ElementCount(szPath));

	/* First try to find the file using the path specified. */
	{
		const char * pszFile = pszFileToFind;
		strcpy(szPath, pszFileToFind);

		if (*pszFile == '\\')	/* User meant root? Seems unlikely but check anyway. */
		{
			if ((pOpenTest = fopen(szPath, "r")) != NULL)
				goto FoundIt;

			/* Not in root. Skip over first slash. All directories will have trailing slash. */
			pszFile++;
		}

		{
			char const * pszDir = NULL;
			while ((pszDir = GetDirectory(nDisk, pszDir)) != NULL)
			{
				_ASSERT((strlen(pszDir) + strlen(pszFile)) < ElementCount(szPath));

				sprintf(szPath, "%s%s", pszDir, pszFile);
				if ((pOpenTest = fopen(szPath, "r")) != NULL)
					goto FoundIt;
			}
		}
	}

	/* At this point we've failed unless we have a directory. */
	/* If the file has a directory, try finding it without the directory. */
	{
		const char * pszFile = strrchr(pszFileToFind, '\\');

		if (pszFile != NULL)
		{
			pszFile++;
			fSuccess = GetFilePath(nDisk, pszFile, pszPath, cchPath);
		}
	}

	goto Exit;

FoundIt:

	_ASSERT(strlen(szPath) < cchPath);
	strcpy(pszPath, szPath);

	fSuccess = TRUE;

Exit:

	if (pOpenTest != NULL) fclose(pOpenTest);

	return fSuccess;
}

static BOOL Evaluate(char const * const pszKeyword, char * const pszBody, FILE * pOutput)
{
	BOOL fSuccess = FALSE;
	enum { Keyword_Comment, Keyword_Disk, Keyword_File, KeywordCount };
	static char const * const apszKeyword[] = { "COMMENT", "DISK", "FILE" };
	int const cKeywords = ElementCount(apszKeyword);
	int iKeyword;

	_ASSERT(cKeywords == KeywordCount);

	dbgprintf("Evaluating &%s(%s)\n", pszKeyword, pszBody);

	if (pszKeyword == NULL || *pszKeyword == '\0')
	{
		fputs("No keyword found", stderr);
		goto Error;
	}

	for (iKeyword = 0; iKeyword < cKeywords; iKeyword++)
		if (stricmp(pszKeyword, apszKeyword[iKeyword]) == 0)
			break;

	switch (iKeyword)
	{
		default:
		{
			fprintf(stderr, "%s: Invalid keyword", pszKeyword);
			goto Error;

			break;
		}

		case Keyword_Comment:
		{
			/* Just prevent the writing of the body by doing nothing. */
			break;
		}

		case Keyword_Disk:
		{
			char * pszDirectories;
			int nDisk = strtol(pszBody, &pszDirectories, 10);

			if (nDisk == 0)
			{
				fputs("Invalid disk number", stderr);
				goto Error;
			}

			while (*pszDirectories != '\0')
				if (*pszDirectories++ == ',')
					break;

			if (!AddDiskEntry(nDisk, pszDirectories))
				goto Error;

			break;
		}

		case Keyword_File:
		{
			char * pszDisk = strtok(pszBody, ",");
			char * pszFile = strtok(NULL, ",");

			BOOL fDoSize = TRUE;
			BOOL fDoVersion = TRUE;

			int nDisk = 0;

			_ASSERT(pszDisk != NULL);
			if (pszDisk == NULL)
			{
				fprintf(stderr, "Syntax error (%s): no disk number\n", pszBody);
				goto Error;
			}

			_ASSERT(pszFile != NULL);
			if (pszFile == NULL)
			{
				fprintf(stderr, "Syntax error (%s): no file specified\n", pszBody);
				goto Error;
			}

			{
				char * pszOption = NULL;
				BOOL fFoundOptions = FALSE;

				while ((pszOption = strtok(NULL, ",")) != NULL)
				{
					dbgprintf("Found &FILE option %s\n", pszOption);

						  if (stricmp(StripBoth(pszOption, " \t\""), "SIZE") == 0)			fDoSize = TRUE;
					else if (stricmp(StripBoth(pszOption, " \t\""), "NOSIZE") == 0)		fDoSize = FALSE;
					else if (stricmp(StripBoth(pszOption, " \t\""), "VERSION") == 0)		fDoVersion = TRUE;
					else if (stricmp(StripBoth(pszOption, " \t\""), "NOVERSION") == 0)	fDoVersion = FALSE;
				}
			}

			nDisk = atoi(pszDisk);
			_ASSERT(nDisk != 0);
			if (nDisk == 0)
			{
				fprintf(stderr, "Invalid disk number (%s)\n", pszBody);
				goto Error;
			}

			pszFile = StripBoth(pszFile, " \t\"");

			{
				char szPath[MAX_PATH];
				if (!GetFilePath(nDisk, pszFile, szPath, ElementCount(szPath)))
				{
#if defined(_DEBUG)
					fprintf(stderr, "%s: Unable to find file (_DEBUG not writing SIZE or VERSION)\n", pszFile);
					fDoSize = FALSE;
					fDoVersion = FALSE;
#else
					fprintf(stderr, "%s: Unable to find file", pszFile);
					goto Error;
#endif
				}

				fprintf(pOutput, "%d, %s", nDisk, pszFile);

				if (fDoSize)
				{
					UINT cbFile;

					dbgprintf("Calculating size for %s\n", szPath);

					if (FileSize(szPath, &cbFile))
						fprintf(pOutput, ", SIZE=%u", cbFile);
				}

				if (fDoVersion)
				{
					WORD a, b, c, d;

					dbgprintf("Calculating version for %s\n", szPath);

					if (FileVersion(szPath, &a, &b, &c, &d))
						fprintf(pOutput, ", OVERWRITE=OLDER, VERSION=\"%u,%u,%u,%u\"", a, b, c, d);
				}
			}

			break;
		}
	}

	fSuccess = TRUE;

Error:

	return fSuccess;
}

static BOOL Parse(FILE * pInput, FILE * pOutput)
{
	BOOL fSuccess = FALSE;
	int iLine = 0;
	char szLine[1024];
	int const cchMaxLine = sizeof(szLine);

	OpenDiskList();

	while (fgets(szLine, cchMaxLine, pInput))
	{
		char const * pszComment;
		char const * pszCurrent;
		char * pszToken;

		iLine++;

		dbgprintf("Parsing Line %d: %s", iLine, szLine);

		if (strrchr(szLine, '\n') == NULL)
		{
			fprintf(stderr, "Maximum line length of %d exceeded", cchMaxLine);
			goto Error;
		}

		/* Find the first comment character in the line. If none found, point to end. */
		{
			char const * const pszComment1 = strchr(szLine, ';');
			char const * const pszComment2 = strstr(szLine, "//");

			if (pszComment1 == NULL)
				pszComment = (pszComment2 != NULL) ? pszComment2 : szLine + strlen(szLine);
			else if (pszComment2 == NULL)
				pszComment = pszComment1;
			else if (pszComment1 < pszComment2)
				pszComment = pszComment1;
			else
				pszComment = pszComment2;
		}

		pszCurrent = szLine;
		while ((pszToken = strchr(pszCurrent, '&')) != NULL  && pszToken < pszComment)
		{
			char const * pszKeyword;
			char * pszBody;

			/* Write everything before the token. */
			if (pszToken != pszCurrent)
			{
				*pszToken = '\0';
				fputs(pszCurrent, pOutput);
			}

			pszKeyword = strtok(pszToken + 1, "(");
			pszBody = strtok(NULL, ")");
			pszCurrent = strtok(NULL, "");

			if (!Evaluate(pszKeyword, pszBody, pOutput))
				goto Error;
		}

		if (pszCurrent != NULL && *pszCurrent != '\0')
			fputs(pszCurrent, pOutput);
	}

	if (ferror(pInput))
	{
		perror(NULL);
		goto Error;
	}

	fSuccess = TRUE;

	goto Exit;

Error:

	fprintf(stderr, " in line %d", iLine);

Exit:

	CloseDiskList();

	return fSuccess;
}

int main(int argc, char ** argv)
{
	int Result = EXIT_FAILURE;
	FILE * pInput = NULL;
	FILE * pOutput = NULL;

	if (argc != 3)
	{
		printf(
			"Usage:\n"
			"\n"
			"SIZER input output\n"
			);
		goto Exit;
	}

	pInput = fopen(argv[1], "r");
	if (pInput == NULL)
	{
		perror(argv[1]);
		goto Error;
	}

	pOutput = fopen(argv[2], "w");
	if (pOutput == NULL)
	{
		perror(argv[2]);
		goto Error;
	}

	if (!Parse(pInput, pOutput))
		goto Error;

	Result = EXIT_SUCCESS;

Error:

	if (pInput)
		fclose(pInput);

	if (pOutput)
		fclose(pOutput);

Exit:

	return  Result;
}

