/* 
 * Have a look at helpmsg() to find out more
 */

#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "kernel.h"
#include "swis.h"

#ifndef BOOL
#define BOOL int
#define FALSE (1 == 10)
#define TRUE (FALSE == FALSE)									// tee hee.
#endif

static BOOL verbose = FALSE;
static BOOL force = FALSE;
static BOOL killall = FALSE;
static BOOL help = FALSE;

static void helpmsg(void)
{
	fprintf(stderr, "%s\n",
			"Make sure that only the latest version of a module which may have a\n"
			"number of different names and current versions is loaded. Why? Because\n"
			"I agreed to let R-Comp distribute a badge engineered version of X-Files\n"
			"under the name WebFS and I don't expect it's a good idea to have two\n"
			"image filing systems handling the same filetype in memory. At very least\n"
			"it will be hard to ensure that the newer of the two is the one which\n"
			"actually handles the files.\n"
			"\n"
			"   Syntax: RMOneOf [-fvk] <name> <ver> ... <modulename>\n"
			"\n"
			"A commonly used example will be\n"
			"\n"
			"   RMOneOf X-Files 0.57 WebFS 1.57 <Obey$Dir>.WebFS\n"
			"\n"
			"It is assumed that the named file will contain a module which is intended\n"
			"to replace any of the modules named in the list. First the module name and\n"
			"version are read from the file. An error will be given if the module in\n"
			"the file is not one of the named modules; this behaviour is supressed by\n"
			"the -f (force) option. If any of the listed modules is present and has a\n"
			"version number which is less than the module in the file that module will\n"
			"be killed (actually if it has the same name as the new module it will only\n"
			"be killed if the 'k' flag is present). If, after that, there is no module\n"
			"in memory which is as new or newer than the named module it will be loaded.\n");
}

/* Given a file handle and an offset within that file where a 32 bit offset * 
 * may be found return the NULL terminated string which lies at the end of *
 * this short chain of indirection. The string will be in a malloc()ed buffer
 * * which can be free()d when you're finished with it. */

static char *getmodulefilestring(FILE * mf, unsigned long pos)
{
	int c, l;
	char *s;

	if (fseek(mf, pos, SEEK_SET) != 0 || fread(&pos, sizeof (pos), 1, mf) < 1 || fseek(mf, pos, SEEK_SET)
			!= 0)
		return NULL;

	for (c = getc(mf), l = 0; c != 0 && c != EOF; l++, c = getc(mf))
	{
		if (!isprint(c) && c != '\t')
			return NULL;
	}

	if (s = malloc(l + 1), !s)
		return NULL;

	if (fseek(mf, pos, SEEK_SET) != 0 || fread(s, 1, l + 1, mf) < l + 1)
	{
		free(s);
		return NULL;
	}

	return s;
}

static char *getmodver(const char *modhelp, char buf[5])
{
	memset(buf, 0, 5);

	modhelp = strchr(modhelp + 1, '.');
	while (modhelp && (!isdigit(modhelp[-1]) || !isdigit(modhelp[1]) || !isdigit(modhelp[2])))
		modhelp = strchr(modhelp + 1, '.');

	if (modhelp)
	{
		memcpy(buf, modhelp - 1, 4);
		return buf;
	}

	return NULL;
}

static const char *getmodulestring(const void *modbase, unsigned pos)
{
	const char *s = (const char *) modbase + ((unsigned *) modbase)[pos / 4];
	const char *s2;

	for (s2 = s; *s2; s2++)
	{
		if (!isprint(*s2) && *s2 != '\t')
			return NULL;
	}

	return s;
}

static int strucmp(const char *a, const char *b)
{
	if (0 == strcmp(a, b))
		return 0;

	while (*a && tolower(*a) == tolower(*b))
		a++, b++;

	return tolower(*a) - tolower(*b);
}

int main(int argc, char *argv[])
{
	int argn, name, tail;
	char *me;
	FILE *mf;
	const char *modtitle, *modhelp;
	char modver[5];
	_kernel_swi_regs regs;
	_kernel_oserror *err;
	const char *newertitle, *newerhelp;
	BOOL fileinlist;

	if (me = strrchr(argv[0], '.'), me)
		me++;
	else if (me = strrchr(argv[0], ':'), me)
		me++;
	else
		me = argv[0];

	for (name = 0, argn = 1; argn < argc; argn++)
	{
		if (*argv[argn] == '-')
		{
			force |= strchr(argv[argn], 'f') || strchr(argv[argn], 'F');
			verbose |= strchr(argv[argn], 'v') || strchr(argv[argn], 'V');
			killall |= strchr(argv[argn], 'k') || strchr(argv[argn], 'K');
			help |= strchr(argv[argn], 'h') || strchr(argv[argn], 'H') || strchr(argv[argn], '?');
		}
		else
		{
			if ((name & 1) && (strlen(argv[argn]) != 4 || !getmodver(argv[argn], modver)))
			{
				fprintf(stderr, "%s: '%s' is not a valid module version number\n", me, argv[argn]);
				return EXIT_FAILURE;
			}
			name++;
			tail = argn;										/* remember the position of the module name */
		}
	}

	if (help)
	{
		helpmsg();
		return EXIT_SUCCESS;
	}

	if (name < 3 || (name & 1) == 0)
	{
		fprintf(stderr, "%s: Need the name and version of at least one module and\n", me);
		fprintf(stderr, "%s: the name of a file containing the module to load.\n", me);
		return EXIT_FAILURE;
	}

	if (mf = fopen(argv[tail], "rb"), NULL == mf)
	{
		fprintf(stderr, "%s: Can't open %s\n", me, argv[tail]);
		return EXIT_FAILURE;
	}

	if ((modtitle = getmodulefilestring(mf, 16), !modtitle) || (modhelp = getmodulefilestring(mf, 20)
					,!modhelp))
	{
		fprintf(stderr, "%s: Failed to read module title and help string for\n", me);
		fprintf(stderr, "%s: file %s. Is it a module?\n", me, argv[tail]);
		fclose(mf);
		return EXIT_FAILURE;
	}

	fclose(mf);

	/* Now find the version number in the module help string */

	if (!getmodver(modhelp, modver))
	{
		fprintf(stderr, "%s: Failed to extract module version from module\n", me);
		fprintf(stderr, "%s: help string; can't continue.\n", me);
		return EXIT_FAILURE;
	}

	if (verbose)
		fprintf(stderr, "%s: Module title: %s, version: %s\n", me, modtitle, modver);

	/* Now chase down the module chain killing any modules which have names which
	 * match one of ours but have older version numbers.
	 */

	regs.r[0] = 12;												/* reason code -- chase module chain */
	regs.r[1] = 0;												/* first call */
	regs.r[2] = 0;

	fileinlist = force;
	newertitle = newerhelp = NULL;

  scanmodules:
	for (;;)
	{
		const char *curtitle, *curhelp;
		char curver[5];											/* they make those plastic storage boxes don't they? */
		const char *thistitle, *thisver;

		if (err = _kernel_swi(OS_Module, &regs, &regs), err)
			break;

		if (curtitle = getmodulestring((void *) regs.r[3], 16), !curtitle)
			continue;
		if (curhelp = getmodulestring((void *) regs.r[3], 20), !curhelp)
			continue;
		if (!getmodver(curhelp, curver))
			continue;

		for (thistitle = NULL, thisver = NULL, argn = 1; argn < tail; argn++)
		{
			if (*argv[argn] != '-')
			{
				if (!thistitle)
					thistitle = argv[argn];
				else if (!thisver)
					thisver = argv[argn];

				if (thistitle && thisver)
				{
					if (!fileinlist && strucmp(thistitle, modtitle) == 0 && strcmp(thisver, modver)
							== 0)
						fileinlist = TRUE;

					if (strucmp(curtitle, thistitle) == 0)
					{
						if (strcmp(curver, thisver) < 0)
						{
							/* found a module which matches but which is older than the one in the list */

							if (killall || strucmp(curtitle, modtitle) != 0)
							{
								if (verbose)
									fprintf(stderr, "%s: Killing %s version %s\n", me, curtitle, curver);

								regs.r[0] = 4;					/* kill module */
								regs.r[1] = (int) curtitle;

								if (err = _kernel_swi(OS_Module, &regs, &regs), err)
								{
									fprintf(stderr, "%s: %s\n", me, err->errmess);
									return EXIT_FAILURE;
								}

								/* having delete a module we need to start scanning again */

								goto scanmodules;
							}
						}
						else
						{
							newertitle = curtitle;
							newerhelp = curhelp;
						}
					}

					thistitle = thisver = NULL;
				}
			}
		}
	}

	/* Finally, decide whether we need to load the module */

	if (!fileinlist)
	{
		fprintf(stderr, "%s: Warning: The module file %s was not one of the listed modules\n", me, argv
				[tail]);
		fprintf(stderr, "%s: It has the title %s and the version %s\n", me, modtitle, modver);
	}

	if (!newertitle)
	{
		if (verbose)
			fprintf(stderr, "%s: Loading %s (%s %s)\n", me, argv[tail], modtitle, modver);

		regs.r[0] = 1;
		regs.r[1] = (int) argv[tail];
		if (err = _kernel_swi(OS_Module, &regs, &regs), err)
		{
			fprintf(stderr, "%s: %s\n", me, err->errmess);
			return EXIT_FAILURE;
		}
	}
	else
	{
		if (verbose)
		{
			char newerver[5];

			(void) getmodver(newerhelp, newerver);

			fprintf(stderr, "%s: A newer module (%s %s) was already loaded\n", me, newertitle, newerver);
		}
	}

	return EXIT_SUCCESS;
}
