/*
 * fspell.c
 *
 * Make an Angband .prf file that maps spells onto function keys.
 *
 * Version History:
 * < 2.00 - mainly prototyping & mucking around; not documented.
 * v2.00 (18-Mar-96) : * Initial fully working version
 * v2.01 (23-Jan-97) : * Bug fix - forgot to initialise a variable
 				         (still worked OK - good ol' APCS)
   					   * Added code to ask for missing command line options
   v2.10 (24-Jan-97) : * Now possible to append the new prefs to a .prf file
 *
 */

static char identity[] = "fspell 2.10 (c) 1997 Musus Umbra";

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

/* Constants and definitions */

#define F1TO9     0x180		/* Base key code for f1..f9 */
#define F10TO12   0x1C0		/* and f10..f12 */
#define SHIFTMOD  0x010		/* Modifier for shift-ed keys */
#define CTRLMOD   0x020		/* and control-ed keys */
#define SHCTRLMOD 0x030		/* and shift-control-ed keys */

#define PREFIX    "^_"		/* 31 is sent to Angabnd before the key code */
#define POSTFIX   "\\r"		/* and CR is sent afterwards */
#define SPELLKEY  'm'		/* the character that invokes a spell */
#define PRAYERKEY 'p'		/* the character that invokes a prayer */
#define DEFPREF   "A:\\e\\e"	/* prefix to a definition line */
#define COMPREF   "P:"		/* prefix to the mapping line */

/* Types & Data structures */

typedef enum
{
	ALONE=0, SHIFTED=1, CONTROLLED=2, SHIFTCONTROLLED=3
} modifier_type;

typedef struct l_node
{
	struct l_node *next;		/* ->next node in list */
	modifier_type modifier;		/* Shift/Ctrl/Both/Alone? */
	int           key;			/* which key (0..11) */
	char          book;			/* which book (eg. 'a' or '1') */
	char          spell;		/* which spell (eg. 'b') */
	char          *spellname;	/* ->name of spell */
} listnode;


/* Functions & Procedures */

/*
 * return a pointer to a suitable description of the 'modifier' applied
 * to a key.
 */

static char *modtext( modifier_type m )
{
	static char *texts[] = { "", "shift-", "ctrl-" , "shift-ctrl-" , 0 };
	char **t;

	for ( t=texts; m && *t; m-- )
		t++;

	return *t ? *t : "<badmodifier>-";
}


/*
 * strip tailing spaces from a string and return a pointer to the
 * first non-whitespace character in it.
 */

static char *unpad( char *l )
{
	char *start;

	for ( start=l; isspace(*start); start++)
		;										/* find start of string */
	for ( l = start; *l ; l++)
		;										/* find terminator */
	if ( l != start )
	{
		for ( --l; isspace(*l); l--)
			;									/* find end of string */
	}
	*++l = 0;									/* terminate */

	return start;
}


/*
 * test whether a particular key is already defined
 */

static int defined ( modifier_type m, int key, listnode *h )
{
	while (h)
	{
		if ( (h->modifier == m) && (h->key == key) )
		{
			return 1;
		}
		h = h->next;
	}

	return 0;
}






/*
 * Add a node to the head of the list pointed to by 'h' with
 * data modifier 'm', key 'k', book 'b', spell 's' & name 'n'.
 * returns a pointer to the new head of the list.
 */

static listnode *addnode( modifier_type m, int k, char b, char s, char *n, listnode *h)
{
	listnode *thisnode = malloc(sizeof(listnode));

	if ( thisnode == NULL )			/* Out of memory? */
	{
		fprintf(stderr,"fspell: insufficient memory (addnode/1)\n");
		exit(1);
	}

	thisnode->modifier = m;
	thisnode->key = k;
	thisnode->book = b;
	thisnode->spell = s;
	if ( !n )
	{
		n = "";
	}
	thisnode->spellname = malloc(strlen(n)+1);
	if ( thisnode->spellname == NULL )
	{
		fprintf(stderr,"fspell: insufficient memory (addnode/2)\n");
		exit(1);
	}
	strcpy(thisnode->spellname,n);
	thisnode->next = h;

	return thisnode;
}


/*
 * Parse the line 'l' and add a new node to the list pointed to by 'h'
 * that holds the data 'l' represents.
 * NB: 'l' may be modified by this function !!!
 * returns a pointer to the new head of the list.
 */

static listnode *parseline( char *l, listnode *h, int ln )
{
	modifier_type modifier;
	char          book;
	char          spell;
	char          *token;
	char          *line = l;
	char          c;
	int           key;

	line = unpad(line);
	if ( *line == 0 || *line == '#' || *line == ':' ) { return h; }

	token = strtok(line,":");	/* find key element */
	token = unpad(token);		/* strip spaces */
	modifier = ALONE;
	key = -1;

	while ( (c = *token++)!=NULL && key==-1 )
	{
		if ( !isspace(c) )
		{
			switch (tolower(c))
			{
				case 's' : modifier |= SHIFTED; break;
				case 'c' : modifier |= CONTROLLED; break;
				case 'f' : key = atoi(token); break;
				case '!' : return h;
				default  :
					fprintf(stderr,"fspell: bad key code '%s' at line %d\n",
					        token, ln);
					exit(1);
			}
		}
	}
	if ( key == -1 )
	{
		fprintf(stderr,"fspell: key code missing at line %d\n",ln);
		exit(1);
	}
	if ( key == 12 )
	{
		fprintf(stderr,"fspell: bad assign (F12) at line %d\n",ln);
		exit(1);
	}
	if ( key<0 || key>11 )
	{
		fprintf(stderr,"fspell: key out of range at line %d\n",ln);
		exit(1);
	}

	token = strtok(NULL,":");			/* get book/spell token */
	book = 0;
	spell = 0;
	while ( (c = *token++) !=NULL )
	{
		if ( !isspace(c) )
		{
			if ( book && spell )
			{
				fprintf(stderr,"fspell: extraneous book/spell at line %d\n",ln);
				exit(1);
			}
			if ( book )
			{
				spell = c;
			}
			else
			{
				book = c;
			}
		}
	}

	token = strtok(NULL,":");
	token = unpad(token);

	if ( defined(modifier,key,h) )
	{
		fprintf(stderr,"fspell: duplicate mapping for key %sF%d at line %d\n",
		        modtext(modifier),key,ln);
		exit(1);
	}

	return addnode(modifier,key,book,spell,token,h);
}



/*
 * Build a list of key_mapping's from the input stream 'istream'.
 * returns a pointer to the created list
 */

static listnode *parsefile(FILE *istream)
{
	listnode *head = NULL;				/* ->head of list we'll create */
	char     buffer[256];				/* buffer for line of text */
	int      line = 0;

	while (!feof(istream))				/* while not at end of file */
	{
		line++;
		if ( fgets(buffer,256,istream) )		/* get line */
		{
			head = parseline(buffer,head,line);		/* Parse line */
		}
	}

	return head;						/* return ->list */
}


/*
 * decode a list node into a string.
 */

static char *decode( listnode *l )
{
	static char buffer[256];

	sprintf(buffer,"%sF%d = %c%c%c [%s]", modtext(l->modifier), l->key,
	        SPELLKEY, l->book, l->spell, l->spellname);

	return buffer;
}


/*
 * produce the 'macro' text for a list node
 */

static char *macro( listnode *l, char invoke )
{
	static char buffer[8];

	sprintf(buffer,"%c%c%c", invoke, l->book, l->spell);

	return buffer;
}


/*
 * encode a key & modifier into a hex value
 */
static int keycode( modifier_type m, int key )
{
	key += key<10 ? F1TO9 : F10TO12;
	switch (m)
	{
		case ALONE           : break;
		case SHIFTED         : key += SHIFTMOD; break;
		case CONTROLLED      : key += CTRLMOD; break;
		case SHIFTCONTROLLED : key += SHCTRLMOD; break;
		default              :
			fprintf(stderr,"fspell: bad modifier (keycode)\n");
			exit(1);
	}

	return key;
}

/*
 * produce the keystroke text for a list node
 */

static char *keystroke( listnode *l )
{
	static char buffer[64];

	sprintf(buffer,"%s%03X%s", PREFIX, keycode(l->modifier,l->key), POSTFIX);

	return buffer;
}



static char *date_and_time( void )
{
	static char buffer[128];
	time_t t;
	t = time( &t );
	strcpy( buffer, ctime(&t) );
	return unpad(buffer);
}


/*
 * write the .prf data for list 'h' to output stream 'ostream'.
 */

static void writepref( listnode *h, char invoke, FILE *ostream )
{
	fprintf(ostream,"# Preferences file mapping spells onto function keys\n");
	fprintf(ostream,"# for RISC OS.\n");
	fprintf(ostream,"#\n# Generated %s by %s\n\n",date_and_time(),identity);

	while (h)
	{
		fprintf(ostream,"# %s\n",decode(h));
		fprintf(ostream,"%s%s\n",DEFPREF,macro(h,invoke));
		fprintf(ostream,"%s%s\n\n",COMPREF,keystroke(h));
		h = h->next;
	}

	fprintf(ostream,"# End generated file\n\n");
}



static char *getstring(char *prompt)
{
	char buffer[256];
	char *dyn_alloc;
	char *got_string;
	printf("Please type %s >",prompt);
	fgets(buffer,256,stdin);
	got_string = unpad(buffer);
	dyn_alloc = (char*) malloc( (strlen(got_string)+2)*sizeof(char) );
	strcpy(dyn_alloc,got_string);
	return dyn_alloc;
}


int main( int argc, char *real_argv[] )
{
	FILE *istream, *ostream;
	listnode *head;
	char invoke;
	char *argv[4];

	printf("\n%s\n\n",identity);

	/* We will be modifying these maybe, so best use copies! */
	argv[1] = real_argv[1];
	argv[2] = real_argv[2];
	argv[3] = real_argv[3];

	switch ( argc )
	{
		case 1 : /* ie. no parameters */
			argv[1] = getstring("the invocation key (ie. m or p)");
		case 2 : /* haven't got the source file */
			argv[2] = getstring("the source (fspell) filename");
		case 3 : /* haven't got the dest. file  */
			argv[3] = getstring("the destination (.prf) filename");
		case 4 : /* got everything */
			break;
		default :
			fprintf(stderr,"syntax: fspell [<invocation-key> ");
			fprintf(stderr,"[<source> [<dest>]]]\n");
			exit(1);
	}

	invoke = argv[1][0];

	istream = fopen(argv[2],"r");
	if ( istream )
	{
		char *output_mode = "Creating";

		printf("Parsing source file '%s'...\n",argv[2]);
		head = parsefile(istream);
		fclose(istream);
		ostream = fopen(argv[3],"r");
		if ( !ostream )
		{
			ostream = fopen(argv[3],"w");
		}
		else
		{
			char *response = NULL;
			printf("File '%s' already exists; ",argv[3]);
			response = getstring("(A)ppend or (R)eplace");
			if ( tolower(*response) != 'r' )
			{
				output_mode = "Appending to";
				ostream = freopen( argv[3], "a", ostream );
			}
			else
			{
				ostream = freopen(argv[3], "w", ostream);
			}
		}
		if (ostream)
		{
			printf("%s .prf file '%s'...\n",output_mode,argv[3]);
			writepref(head,invoke,ostream);
			fclose(ostream);
			printf("Done.\n\n");
		}
		else
		{
			fprintf(stderr,"Can't open '%s' for writing\n",argv[3]);
		}
	}
	else
	{
		fprintf(stderr,"Can't open '%s' for reading\n",argv[2]);
	}
}

