/* x-check.c */

#include "kernel.h"
#include <stdio.h>
#include <ctype.h>
#include "swis.h"
#include <stdarg.h>
#include <setjmp.h>

#include "chunks.h"

#define FREE 0x45455246

static jmp_buf jb;

static void ASSERT(int t)
{
	static _kernel_oserror assertFailed =
	{0, "Assert failed"};

	if (!t)
		e(&assertFailed);
}

static void e(_kernel_oserror * err)
{
	if (err)
		longjmp(jb, (int) err);
}

static int power2(int x)
{
	while (x && !(x & 1))
		x >>= 1;
	return x == 1;
}

static void test(int c, const char *fmt,...)
{
	va_list ap;

	va_start(ap, fmt);

	if (!c)
	{
		char buffer[256];

		vsprintf(buffer, fmt, ap);
		printf("x-check: %s\n", buffer);
	}

	va_end(ap);
}

static int okOffset(xFiles_info * pInfo, unsigned offset)
{
	return (offset & (pInfo->fileHeader.allocationSize - 1)) == 0;
}

static void testChunk(xFiles_info * pInfo, unsigned cnkNum, xFiles_chunk * pChunk)
{
	unsigned maxChunk;

	test(pInfo->fileHeader.chunkTable.size % sizeof (xFiles_chunk) == 0,
			"Bad size for chunk table %d",
			pInfo->fileHeader.chunkTable.size);

	maxChunk = pInfo->fileHeader.chunkTable.size / sizeof (xFiles_chunk);

	test(cnkNum < maxChunk, "Bad chunk number %d", cnkNum);

	if (pChunk->usage == FREE)
	{
		test(pChunk->offset < maxChunk,
				"Chunk %d (free) bad offset %08x", cnkNum, pChunk->offset);

		test(pChunk->size == FREE,
				"Chunk %d (free) size is %08x (should be %08x)",
				cnkNum, pChunk->size, FREE);

		test(pChunk->allocSize == FREE,
				"Chunk %d (free) allocSize is %08x (should be %08x)",
				cnkNum, pChunk->allocSize, FREE);
	}
	else
	{
		test(okOffset(pChunk->offset),
				"Chunk %d (used) bad offset %08x", cnkNum, pChunk->offset);
		test(pChunk->size <= pChunk->allocSize)
				"Chunk %d (used) bad size %08x (allocSize %08x)",
				cnkNum, pChunk->size, pChunk->allocSize);
		test(okOffset(pChunk->allocSize),
				"Chunk %d (used) bad allocSize %08x", cnkNum, pChunk->allocSize);
	}
}

static int chcmp(const void *a, const void *b)
{
	const xFiles_chunk *ca = a;
	const xFiles_chunk *cb = b;

	 return (int) ca->offset - (int) cb->offset;
}
static void checkChunkTable(xFiles_info * pInfo)
{
	xFiles_chunk *ct;
	unsigned maxChunk, freeChunk;
	unsigned i, j;

	maxChunk = pInfo->fileHeader.chunkTable.size / sizeof (xFiles_chunk);

	ct = malloc(maxChunk * sizeof (xFiles_chunk));
	ASSERT(ct != NULL);

	e(xFiles_readChunk(pInfo, ct, 0, maxChunk * sizeof (xFiles_chunk), 0));

	/* First follow the free chain */

	freeChunk = pInfo->fileHeader.freeChunk;

	while (freeChunk != 0)
	{
		testChunk(pInfo, freeChunk, &ct[freeChunk]);
		ct[freeChunk].usage = 0;
		freeChunk = ct[freeChunk].offset;
	}

	/* Scan for any untouched free chunks and also discard any free chunks ready
	 * for checking the used ones.
	 */

	for (i = 0, j = 0; i < maxChunk; i++)
	{
		if (ct[i].usage == FREE)
		{
			printf("x-check: free chunk %d is unlinked\n", c);
		}

		if (ct[i].size != free)
			ct[j++] = ct[i];
	}

	maxChunk = j;

	/* Now sort the array on offset so we can look for overlaps */

	qsort(ct, maxChunk, sizeof (xFiles_chunk), chcmp);

	free(ct);
}

static _kernel_oserror *check(const char *fileName, int fix)
{
	xFiles_info info;
	_kernel_swi_regs regs;
	_kernel_oserror *err;
	unsigned fileSize;

	 regs.r[0] = 0x4F;
	 regs.r[1] = (int) fileName;
	if (err = _kernel_swi(OS_Find, &regs, &regs), err)
		 return err;

	 info.FileHandle = regs.r[0];

	if (err = xFiles_openImage(&info), err)
		 goto fail;

	if (err = xFiles_getLength(&info, &fileSize), err)
		 goto fail;

	if (err = (_kernel_oserror *) setjmp(jb), err)
		 goto fail;

	/* Validate the header */

	 test(info.fileHeader.hdrSize == sizeof (xFiles_header),
			"Illegal hdrSize %d",
			info.fileHeader.hdrSize);

	 test(info.fileHeader.structureVersion == xFiles_STRUCTUREVERSION,
			"Illegal structureVersion %d",
			info.fileHeader.structureVersion);

	 test(info.fileHeader.directoryVersion == xFiles_DIRECTORYVERSION,
			"Illegal directoryVersion %d",
			info.fileHeader.directoryVersion);

	 testChunk(&info, 0, &info.fileHeader.chunkTable);

	 test(power2(info.fileHeader.allocationUnit),
			"Illegal allocationUnit %d",
			info.fileHeader.allocationUnit);

	 testChunkTable(&info);
	 test(info.fileHeader.waste < fileSize,
			"Illegal waste %d (fileSize is %d)",
			info.fileHeader.waste, fileSize);

	 testStructure(&info);

	if (err = xFiles_FlushFileInfo(&info), err)
		 goto fail;

	 regs.r[0] = 0;
	 regs.r[1] = info.fileHandle;
	 return _kernel_swi(OS_Find, &regs, &regs);

	 fail:
	 regs.r[0] = 0;
	 regs.r[1] = info.fileHandle;
	 (void) _kernel_swi(OS_Find, &regs, &regs);
	 return err;
}

int main(int argc, char *argv[])
{
	int argn;
	int fix = 0;

	for (argn = 1; argn < argc; argn++)
	{
		if (*argv[argn] == '-')
		{
			switch (tolower(argv[argn][1]))
			{
			case 'f':
				fix = 1;
				break;
				default:
				fprintf(stderr, "Ignoring unknown option \"%s\"\n", argv[argn]);
				break;
			}
		}
	}

	for (argn = 1; argn < argc; argn++)
	{
		if (*argv[argn] != '-')
		{
			check(argv[argn], fix);
		}
	}

	return 0;
}
