/*
 * BDotD.c
 *  Musus Umbra 1996/7
 *
 * Choose a 'backdrop of the day' and say whether it should be centred,
 * tiled or scaled.
 * Any backdrop will not be rechosen until all other backdrops have been
 * chosen.  New backdrops added to the dir will be taken into account
 * automatically.  The centre/tile choice is based on the current screen
 * mode (should be same as wimpmode at boot-up under RO3).
 * NB: Creates a file in the backdrop dir to hold state info between
 * runs.
 * Output is in the form of two system variables, <bdotd$flag> is -c, -s or
 * -t for centred/scaled/tiled, and <bdotd$backdrop> is the pathname of the
 * chosen spritefile.
 * Syntax: bdotd <backdrop dir>
 *
 * Author  : Musus Umbra
 * Revision History:
 * 		v1.00 : 960502/0635 : Initial fully working version; problems with
 *								paths fixed (all paths now canonicalised)
 * 		v2.00 : 970218/0530 : s/c/t option decided on the fly; now caches sprite
 *								dimensions, rather than the s/c/t flag, in the
 *								BDotDState file.
 *								Even better handling of pathnames.
 *								Significantly improved s/c/t choosing.
 *								Command line options implemented.
 *								Hourglass used if not in a TaskWindow.
 *		v2.10 : 970218/1645 : Vastly improved speed of directory scanning.
 *		v2.11 : 970414/0255 : Better -t handling (slight buggette fix?)
 *		v2.12 : 970426/0300 : Canonicalising paths is now an option (mainly
 *								because it is sooo sloooow. Even in code).
 								conicalise_code function added [in s.bdotd_code]
 *		v2.13 : 970919/1500 : new fn: mode_is_valid rather than >=0 && <=127
 *								also now accept -modes stated a la X640Y480
 *								and builds a mode descriptor (OS3.5+)
 *		v2.14 : 970919/1500 : now allows -mode ? to use current
 */

/* no. of entries to read per call with OSGBPB 12 */
#define GBPB_GRAN 16

/* default leafname of state file */
#define STATEFILE "bdotdState"	

/* max border for '-c' (OS units) */
#define DEFAULT_BORDER_TOLERANCE 32

/* min acceptable no. of 'tiles'  */
#define DEFAULT_TILE_TOLERANCE   1.75

/* max relative error in aspect ratio of sprite for '-scale' */
#define DEFAULT_SCALE_TOLERANCE  0.15



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

#include "kernel.h"
#define OS_SpriteOp 0x2e
#define OS_ReadModeVariable 0x35
#define OS_SetVarVal 0x24
#define OS_GBPB 0x0c
#define OS_Word 0x07
#define OS_Byte 0x06
#define OS_FSControl 0x29
#define Hourglass_On 0x406c0
#define Hourglass_Off 0x406c1
#define TaskWindow_TaskInfo 0x43380


/* Some flags are global for ease of, well, um. Sheesh! Gimme a break! */
/* what is this? The Spanish Inquisition?! (don't mention the cushion) */
int verbose = 0;
int canonicalise = 1;






extern char* canonicalise_code(char *path);		/* in bdotd_code.s */

char *canonicalise_path(char *path)
{
	char *s;
	if ( !canonicalise )
	{
		s = malloc( strlen(path)+1 );
		if ( !s ) { fprintf(stderr,"Ooops! Malloc failed!\n"); exit(1); }
		strcpy(s,path);
	}
	else
	{
		s = canonicalise_code(path);
	}
	return s;
}


char *syntax_message[] = {
"Syntax: bdotd [-nostate | -statefile <filename>] [-mode <mode>] ",
"              [-raw_paths | -canonicalise_paths ] ",
"              [-border_tolerance <int>] [-tile_tolerance <real>]",
"              [-aspect_tolerance <real>] {-dir <directory>} ",
"              {[-path] <directory>}",
"where [x] means that 'x' is optional, <x> means \"insert an 'x' here\",",
"and {x} means \"one (or more) 'x's\".",
"Only the first letter of a -flag counts, so \"-n\" <=> \"-nostate\" for",
"instance.", 0 };

char *help_message[] = {
"'bdotd' is (c) Musus Umbra, 1996/7.",
"This executable is version 2.14 (19 Sep 1997)",
"'bdotd' should have been supplied with a !Help file, explaining its use ",
"(the license terms require this).  Please refer to that",
"document for full information.", 0 };



typedef struct lnode
{
	struct lnode *next;
	char *name;	/* pathname of spritefile (as specified) */
	char *realname;	/* and the canonicalised version */
	int  size;	/* size in bytes (quick check for 'changed' files) */
	int  w,h;	/* size of sprite in OS units */
	char shown;	/* shown yet flag */
} node;


int under_taskwindow(void)
{
	_kernel_swi_regs r;
	_kernel_oserror *e;

	r.r[0] = 0;
	e = _kernel_swi( TaskWindow_TaskInfo, &r, &r );
	if (e)
	{
		fprintf(stderr,"Fatal internal error: %s / %d\n",e->errmess,__LINE__);
		exit(1);
	}
	return r.r[0] != 0;
}

/* Remove leading and trailing whitespace from a string */
char *mungstring(char *s)
{
	char *e;
	while (isspace(*s)) { s++; }
	for ( e = s; *e; e++ )
		;
	while (isspace(*e) && e>s) { e--; }
	*e = 0;
	return s;
}

/* Find the first non-space character in a string */
char firstchar(char *s)
{
	while (isspace(*s++))
		;
	return *(s-1);
}


/* Set an OS (environment) variable 'var' to the value 'to' (a string) */
void set_os_var(char *var, char *to)
{
	_kernel_swi_regs r;
	_kernel_oserror *e;

	if ( verbose ) { printf("Setting Variable '%s' ... ", var); }

	r.r[0] = (int) var;			/* -> variable name */
	r.r[1] = (int) to;			/* -> value */
	r.r[2] = strlen(to);		/* size of value */
	r.r[3] = 0;					/* context pointer (0 for 1st call) */
	r.r[4] = 0;					/* var type = literal string */
	e = _kernel_swi( OS_SetVarVal, &r, &r );
	if (e)
	{
		fprintf(stderr,"\nFatal internal error (setvarval): %s\n",e->errmess);
		exit(2);
	}

	if ( verbose ) { printf("done.\n"); }
}


/* Determine the current screen mode */
int screenmode(void)
{
	_kernel_swi_regs r;
	r.r[0] = 135;
	_kernel_swi( OS_Byte, &r, &r );
	return r.r[2];
}


/* take a copy of a string and force it to lowercase */
char *lowcpy(char *d, char *s)
{
	char *dp = d;

	while ( (*d++=tolower(*s++)) != NULL )
		;

	return dp;
}

/* Read a mode variable */
int modevar(int mode, int var)
{
	_kernel_swi_regs r;
	_kernel_oserror *e;

	r.r[0] = mode;
	r.r[1] = var;
	e = _kernel_swi( OS_ReadModeVariable, &r, &r );
	if (e)
	{
		fprintf(stderr,"Bad call: modevar(%d,%d)\n",mode,var);
		exit(2);
	}
	return r.r[2];
}

/* Determine the 'c/s/t' flag for a backdrop *n in mode 'mode' */
char *chooseflag(node *n, int mode, int btol, double ttol, double stol)
{
	int mw = modevar(mode,11) << modevar(mode,4);	/* mode's width (OS) */
	int mh = modevar(mode,12) << modevar(mode,5);	/* mode's height (OS) */
	int ibh;		/* icon bar's height in pixels */
	double xr, yr;
	int xb,yb;		/* screen area unused/overfilled (ie. border) */

	if ( verbose ) { printf("Choosing -scale/-centre/-tile flag ... "); }

	switch( modevar(mode,5) )		/* switch (yeig) */
	{
		case 2 : /* 45dpi mode (ie. 24, TV, Low-res, vomit-and-choke) */
			ibh = 34; break;
		case 0 : /* 180dpi mode (unsupported but catered for here) */
		case 1 : /* 90dpi mode (ie. 22, VGA, Hi-res, call-it-what-you-will) */
		default:
			ibh = 67; break;
	}

	mh -= (ibh<<modevar(mode,5));	/* subtract the height of the iconbar */

	/* First, try centred */
	xb = abs(mw-n->w); yb = abs(mh-n->h);
	if ( xb<=btol && yb<=btol )
	{
		if ( verbose ) { printf("done [chose -centre].\n"); }
		return "-centre";
	}

	/* now try tiled */
	xr = ((double) mw) / ((double) n->w); yr = ((double) mh) / ((double) n->h);
	if ( xr>=ttol || yr>=ttol )
	{
		if ( verbose ) { printf("done [chose -tile].\n"); }
		return "-tile";
	}

	/* that just leaves scaled, but check that the aspect ration won't get */
	/* _too_ badly buggered... */
	xr = ((double) n->w) / ((double) n->h);
	yr = ((double) mw) / ((double) mh);
	xr = fabs(yr-xr)/xr;
	if ( xr<=stol )
	{
		if ( verbose ) { printf("done [chose -scale].\n"); }
		return "-scale";
	}

	/* it's not perfect, but -centre would appear to fit the tolerances best */
	if ( verbose ) { printf("awkward (%.2lf/%.2lf) [using -centre].\n",xr,yr); }
	return "-centre";
}

/* Produce a seed for the random number generator using the system clock */
unsigned int seed(void)
{
	_kernel_swi_regs r;
	char block[8];
	block[0] = 3;
	r.r[0] = 14;
	r.r[1] = (int) block;
	_kernel_swi( OS_Word, &r, &r );
	return (block[2]<<24) | (block[1]<<16) | (block[3]<<8) | (block[0]);
}

/* Generate a random integer between 0 and 'r' (+ve) */
int random(int r)
{
	static int seeded = 0;

	if ( !seeded ) { srand( seed() ); seeded = 1; }
	return rand() % r;
}

/* compare two strings case-insensitively (a la strcmp) */
int lowcmp(char *a, char *b)
{
	for ( ; tolower(*a)==tolower(*b); a++, b++ )
	{
		if ( *a == '\0' ) { return 0; }
	}
	return (tolower(*a) - tolower(*b));
}

/* canonicalise a path (returned buffer claimed with malloc) */
/* Now implemented in assembler
 *	char* canonicalise_path(char *path)
 *	{
 *		char *buffer;
 *		int  bsize;
 *		_kernel_swi_regs r;
 *		_kernel_oserror *e;
 *	
 *		r.r[0] = 37;
 *		r.r[1] = (int) path;
 *		r.r[2] = r.r[3] = r.r[4] = r.r[5] = 0;
 *		if ( (e = _kernel_swi( OS_FSControl, &r, &r )) !=NULL )
 *		{
 *			fprintf(stderr,"Error canonicalising path: %s\n",e->errmess);
 *			exit(1);
 *		}
 *		bsize = 1 - r.r[5];
 *		buffer = malloc( bsize );
 *		if ( !buffer )
 *	    {
 *	    	fprintf(stderr,"Out of memory / %d\n",__LINE__);
 *	    	exit(1);
 *	    }
 *		r.r[5] = bsize;
 *		r.r[2] = (int) buffer;
 *		if ( (e = _kernel_swi( OS_FSControl, &r, &r )) !=NULL )
 *		{
 *			fprintf(stderr,"Error canonicalising path: %s\n",e->errmess);
 *			exit(1);
 *		}
 *		return buffer;
 *	}
 */

/* Build a list of nodes from a directory tree */
node *scandir(char *dir, node *head, int recurse)
{
	char block[40*GBPB_GRAN];	/* block for GBPB */
	_kernel_swi_regs r;
	_kernel_oserror *e;
	char pathname[256];
	node* temp;
	char *p;

	char *leaf = (pathname+strlen(dir));
	strcpy(pathname,dir);
	if ( *leaf!=':' && *leaf!='.') { *leaf++='.'; }

	r.r[0]=12;       	  	/* OS_GBPB 12 */
	r.r[1]=(int) dir;	  	/* ->directory name */
	r.r[2]=(int) block;		/* ->buffer */
	r.r[4]=0;        	  	/* offset to start from */
	r.r[5]=40*GBPB_GRAN;	/* size of buffer */
	r.r[6]=0;       		/* filename to match (ie. '*') */
	while ( r.r[4]>=0 )
	{
		r.r[3]=GBPB_GRAN;		/* entries to read */
		e=_kernel_swi(OS_GBPB,&r,&r);
		if (e)
		{
		    fprintf(stderr,"*** FS error: %s ***\n",e->errmess);
		    exit(1);
		}
		p = block;
		while ( r.r[3] )
		{
			strcpy(leaf,p+24);			/* 0-term. object name */
			if ( *((int*)(p+16)) != 1 )	/* if object type isn't a file */
			{
				if ( recurse && *leaf != '!' )	/* don't look inside apps */
				{
					head = scandir(pathname,head,recurse);
				}
			}
			else /* ie. is a file */
			{
				if ( *((int*)(p+20)) == 0xff9 )		/* if a spritefile */
				{
					temp = malloc( sizeof(node) );
					if ( !temp )
					{
						fprintf(stderr,"Out of memory / %d\n",__LINE__);
						exit(1);
					}
					temp->next = head;
					temp->name=malloc( strlen(pathname)+1 );
					if ( !temp->name )
					{
						fprintf(stderr,"Out of memory / %d\n",__LINE__);
						exit(1);
					}
					strcpy(temp->name,pathname);
					temp->realname = canonicalise_path(pathname);
					lowcpy(temp->realname,temp->realname);
					temp->size = *((int*)(p+8));
					temp->w = temp->h = -1;
					temp->shown = '?';
					head = temp;
				}
			}
			p += (24+strlen(leaf));
			p += ( 4 - (((int) p) & 3) );
			r.r[3]--;
		}
	}
	return head;
}


/* Write a statefile contating the info in the list from 'head' */
void write_statefile( char *pathname, node *head )
{
	FILE *ostream = fopen( pathname, "w" );

	if ( verbose ) { printf("Writing statefile '%s' ... ",pathname); }
	if ( !ostream ) { fprintf(stderr,"can't write statefile!\n"); return; }
	while (head)
	{
		fprintf(ostream,"%4dx%-4d:%c:%-8d\"%s\"\n", head->w, head->h,
			head->shown, head->size, head->name );
		head = head->next;
	}
	fclose(ostream);
	if ( verbose ) { printf("done\n"); }
}



int rstrcmp(char *a, char *b, int al)
{
	char *c;
	if ( al!=strlen(b) ) { return 1; }
	c=a+al; b+=al;
	while ( *b-- == *c-- )
		if ( c<a ) { return 0; }
	return 1;
}



/* search the list for a file 'n' (size 's') and fill its details in */
void update_list(node *head, char *n, int s, char f, int w, int h)
{
	char *ss;
	char *temp = canonicalise_path(n);
	int sl;
	lowcpy(temp,temp);
	ss=/*mungstring*/(temp);
	for ( sl=strlen(ss) ; head ; head = head->next )
	{
		/*if ( head->w < 0 )*/						/* available? */
		if ( head->size==s )					/* possibly a match? */
		if ( !rstrcmp(ss,head->realname,sl) )	/* really a match? */
		{
			head->shown = f;
			head->w = w;
			head->h = h;
			free(temp);
			return;			/* we're done here, ma'am */
		}
	}
	free(temp);
}



int dpi_to_eig( int dpi )
{
	switch (dpi)
	{
		case 180 : return 0;
		case  90 : return 1;
		case  45 : return 2;
	}
	return -1;
}


void get_mode_info( int mode, int *bpp, int *xeig, int *yeig )
{
	int type = (mode>>27) & 31;

	if ( type )		/* ie. new format sprite with type info */
	{
		*xeig = dpi_to_eig( (mode>>1) & 4095 );
		*yeig = dpi_to_eig( (mode>>14) & 4095 );
		switch (type)
		{
			case 1 : *bpp = 1; break;
			case 2 : *bpp = 2; break;
			case 3 : *bpp = 4; break;
			case 4 : *bpp = 8; break;
			case 5 : *bpp = 16; break;
			case 6 : *bpp = 24; break;
			default: *bpp = -1;
		}
	}
	else		/* ie. old format mode word */
	{
		*bpp = 1 << modevar( mode, 9 );
		*yeig = modevar( mode, 5 );
		*xeig = modevar( mode, 4 );
	}
}

void read_sprite_info( void *area, node *n )
{
	int *iarea = (int*) area;
	int ww, bw, bpp, xeig, yeig;

	iarea = (int*) (((int) iarea)+iarea[2]);
	ww = iarea[4];		/* get word-width-1 */
	get_mode_info( iarea[10], &bpp, &xeig, &yeig );
	if ( bpp<=0 || xeig<0 || yeig<0 )
	{
		fprintf(stderr,"Trouble finding dimensions of '%s':\n",n->name);
		fprintf(stderr,"xeig==%d yeig==%d bpp==%d ww=%d\n",xeig,yeig,bpp,ww);
		exit(1);
	}
	bw = ww * 32;		/* width in bits */
	bw -= iarea[6];		/* subtract lefthand wastage */
	bw += iarea[7];		/* and add the used portion of the rightmost word */
	n->w = (bw/bpp)<<xeig;
	n->h = (iarea[5]+1)<<yeig;
}

void get_dimensions( node *n )
{
	char buffer[ 8*1024 + 16 ];		/* 8k input buffer :-) */
	FILE *istream = fopen( n->name, "rb" );
	int get_bytes = ( n->size > (8*1024) ) ? 8*1024 : n->size;
	if ( !istream )		/* huh? */
	{
		fprintf(stderr,"'%s' wouldn't open for reading\n",n->name);
		exit(1);
	}
	if ( !fread( buffer+4, get_bytes, 1, istream ) )		/* read 8Kb */
	{
		fclose(istream);
		fprintf(stderr,"error reading '%s'\n",n->name);
		exit(1);
	}
	fclose(istream);
	*((int*)buffer) = n->size+4;	/* fudge it to look like a sprite area */
	read_sprite_info( buffer, n );
}



/* Read a statefile, ammending the list (at head) to reflect changes, etc */
void read_statefile( char *pathname, node *head )
{
	FILE *istream = fopen( pathname, "r" );
	if ( verbose ) { printf("Reading statefile '%s' ... ",pathname); }
	if ( istream )
	{
		char buffer[256];
		while ( fgets(buffer,256,istream) )
		{
			char *line = /*mungstring*/(buffer);	/* tidy start/end */
			if ( *line )			/* don't process blank lines! */
			{
				int w,h,s;			/* width, height, size */
				char f, *n;			/* shown flag, name */
				char *token;
				w = (token=strtok(line,"x"))!=NULL ? atoi(token) : -1;
				h = (token=strtok(NULL,":"))!=NULL ? atoi(token) : -1;
				f = (token=strtok(NULL,":"))!=NULL ? firstchar(token) : '-';
				s = (token=strtok(NULL,"\""))!=NULL ? atoi(token) : -1;
				n = (token=strtok(NULL,"\""))!=NULL ? /*mungstring*/(token) :"";
				if ( *n ) { update_list(head,n,s,f,w,h); }
			}
		}
		fclose(istream);
		if ( verbose ) { printf("done.\n"); }
	}
	else if ( verbose ) { printf("failed.\n"); }

	/* Now hunt through the list for uninitialised nodes and 'fix' them :-) */
	if ( verbose ) { printf("Ammending list of sprites ... "); }
	for ( ; head ; head = head->next )
	{
		if ( head->w < 0 )		/* ie. wasn't in the state file */
		{
			get_dimensions(head);	/* init. it */
		}
	}
	if ( verbose ) { printf("done.\n"); }
}



void choose_backdrop( node *head, int mode, int btol, double ttol, double stol )
{
	int unshown = 0;
	int choice;
	node *scan;

	if ( verbose ) { printf("Choosing a backdrop ..."); }

	for ( scan=head ; scan ; scan=scan->next )
		if ( scan->shown !='*' ) { unshown++; }

	if ( !unshown )			/* ie. no more to choose from */
	{
		for ( scan=head ; scan ; scan=scan->next )
		{
			scan->shown = '-';
			unshown++;
		}
	}

	choice = random(unshown)+1;
	scan=head;
	while ( choice && scan )
	{
		if ( scan->shown !='*' ) { choice--; }
		if ( choice ) { scan=scan->next; }
	}

	if ( !scan )
	{
		fprintf(stderr,"Adny's a moron! 'scan' has left the list!\n");
		exit(1);
	}

	if ( verbose ) { printf("done.\n"); }

	set_os_var("BDotD$Flag",chooseflag(scan,mode,btol,ttol,stol));
	set_os_var("BDotD$Backdrop",scan->name);
	scan->shown = '*';
}



void message(FILE *s, char **text )
{
	for ( ; *text ; text++ )
		fprintf( s,"%s\n", *text );
}



void kill_hourglass(void)
{
	_kernel_swi_regs r;
	_kernel_swi( Hourglass_Off, &r, &r );
}


static int mode_is_valid(int m)
{
	_kernel_swi_regs r;
	_kernel_oserror *e;
	r.r[0] = m;
	e = _kernel_swi( 0x3f /*OS_CheckModeValid*/, &r, &r );
	if (e) { return 0; }
	if ( r.r[0] == -2 ) { return 1; }
	return (r.r[0]>=0) && (r.r[0]==m);
} 



static int parse_mode(char *m)
{
	static int tmp[10];
	int a,v,p;
	if ( isdigit(*m) ) { return atoi(m); }
	if ( *m=='?' ) { return screenmode(); }
	tmp[0] = 1;
	tmp[3] = 2;		/* assume 4bpp */
	tmp[4] = -1;	/* max refresh rate */
	tmp[5] = -1;	/* terminator */
	p = 5;

	while (*m)
	{
		switch ( tolower(*m) )
		{
			case 'c' : case 'g' : case 'f' : /* ignored */
				m++;
				a = 0;
				break;
			case 'x' : case 'y' :
				a = 1 + (tolower(*m++)=='y');
				break;
			case 'e' : m++;
				switch (tolower(*m))
				{
					case 'x' :
						a = 4;		/* xeig */
						m++;
						break;
					case 'y' :
						a = 5;		/* yeig */
						m++;
						break;
					default :
						fprintf(stderr,"Bad mode element 'E%s'\n",m);
						exit(1);
				}
				break;
			default :
				fprintf(stderr,"Bad mode element '%s'\n",m);
				exit(1);
		}

		v = 0;
		while ( isdigit(*m) ) { v=v*10+(*m-'0'); m++; }
		if ( !a && *m && strchr("tkm",tolower(*m)) ) { m++; }

		switch (a)
		{
			case 1 : case 2 :
				tmp[a] = v;
				break;
			case 4 : case 5 :
				if ( p>7 )
				{
					fprintf(stderr,"Too many -mode E qualifiers\n");
					exit(1);
				}
				tmp[p++] = a;
				tmp[p++] = v;
				tmp[p] = -1;
			break;
		}
	}
	return (int) tmp;
}



/* And now, Ladies and Gentlemen... The function you've all been waiting for! */
/* Give it all up for... main() !!! <applause> <cheer> [Hoorah!] etc. */

int main( int argc, char *argv[] )
{
	int nai = 0;
	int i;
	int use_statefile = 1;
	int mode = screenmode();
	node *head = NULL;
	char *statefile = STATEFILE;	/* default pathname of statefile */
	char *statedir = "";			/* ie. use first dir on cmd line */
	char buffer[256];
	int border_tolerance = DEFAULT_BORDER_TOLERANCE;
	double tile_tolerance = DEFAULT_TILE_TOLERANCE;
	double scale_tolerance = DEFAULT_SCALE_TOLERANCE;
	char *temp_dir;

	if ( !under_taskwindow() )
	{
		_kernel_swi_regs r;
		_kernel_swi( Hourglass_On, &r, &r );
		atexit( kill_hourglass );
	}

	for ( i=1 ; i<argc; i++ )
	{
		switch( nai )
		{
			case 0 :	/* expecting a -flag or a path/dir */
				if ( argv[i][0] != '-' )		/* ie. not a flag */
				{
					temp_dir = canonicalise_code(argv[i]);
					if ( statedir && !*statedir ) { statedir = argv[i]; }
					if (verbose) { printf("Scanning tree '%s' ... ",temp_dir); }
					head = scandir( temp_dir, head, 1 );
					if (verbose) { printf("done.\n"); }
					free(temp_dir);
				}
				else /* ie. a -flag */
				{
					switch ( tolower(argv[i][1]) )
					{
						case 'a' : /* -aspect */
							nai = 'a';
							break;
						case 'b' : /* -border */
							nai = 'b';
							break;
						case 'r' : /* -raw_paths */
							canonicalise = 0;
							break;
						case 'c' : /* -canonicalise */
							canonicalise = 1;
							break;
						case 't' : /* -tile */
							nai = 't';
							break;
						case 'm' : /* -mode */
							nai = 'm';
							break;
						case 'd' : /* -dir */
							nai = 'd';
							break;
						case 'p' : /* -path */
							nai = 0;
							break;
						case 'n' : /* -nostate */
							use_statefile = 0;
							break;
						case 'v' : /* -verbose */
							verbose = 1;
							break;
						case 'q' : /* -quiet */
							verbose = 0;
							break;
						case 's' : /* -statefile */
							use_statefile = 1;
							nai = 's';
							break;
						case 'h' : /* -help */
						case '?' : /* ick */
							message(stdout,help_message);
							break;
						default : /* oops... */
							fprintf(stderr,"Bad argument '%s';\n",argv[i]);
							message(stderr,syntax_message);
							exit(1);
					}
				}
				break;

			case 'b' : /* expecting a border tolerance */
				border_tolerance = atoi(argv[i]);
				if ( border_tolerance<0 || border_tolerance>256 )
				{
					fprintf(stderr,"Bad -border_tolerance '%s'\n",argv[i]);
					exit(1);
				}
				nai = 0;
				break;

			case 't' : /* expecting a tiling tolerance */
				tile_tolerance = atof(argv[i]);
				if ( tile_tolerance<0 || tile_tolerance>256 )
				{
					fprintf(stderr,"Bad -tile_tolerance '%s'\n",argv[i]);
					exit(1);
				}
				nai = 0;
				break;

			case 'a' : /* expecting an aspect ratio tolerance */
				scale_tolerance = atof(argv[i]);
				if ( scale_tolerance<0 || tile_tolerance>16 )
				{
					fprintf(stderr,"Bad -aspect_tolerance '%s'\n",argv[i]);
					exit(1);
				}
				nai = 0;
				break;

			case 'm' : /* expecting a mode number */
				mode = parse_mode(argv[i]);
				if ( !mode_is_valid(mode) )
				{
					fprintf(stderr,"Bad -mode '%s'\n",argv[i]);
					exit(1);
				}
				nai = 0;
				break;

			case 'd' : /* expecting a directory name */
				temp_dir = canonicalise_code( argv[i] );
				if ( statedir && !*statedir ) { statedir = argv[i]; }
				if (verbose) {printf("Scanning directory '%s' ... ",temp_dir);}
				head = scandir( temp_dir, head, 0 );	/* ie. no recursion! */
				if (verbose) { printf("done.\n"); }
				nai = 0;
				free(temp_dir);
				break;

			case 's' : /* expecting a pathname for the statefile */
				statedir = NULL;
				statefile = argv[i];		/* ie. ignore statedir */
				nai = 0;
				break;

			default : /* oops! */
				fprintf(stderr,"*** FIE: nai=='%c'\n",nai);
				exit(2);
		}
	}

	if ( nai || !head )		/* ie. incomplete command line */
	{
		fprintf(stderr,"Incomplete command line;\n");
		message(stderr,syntax_message);
		exit(1);
	}

	if ( use_statefile )
	{
		if ( statedir ) { sprintf(buffer,"%s.%s",statedir,statefile); }
		else { strcpy(buffer,statefile); }
		read_statefile(buffer,head);		/* update list from statefile */
	}

	choose_backdrop(head,mode,border_tolerance,tile_tolerance,scale_tolerance);

	if ( use_statefile ) { write_statefile(buffer,head); }

	return 0;
}


