/*
 * Museum.c
 * 2.00 - rewrite to handle Browse and ArcWeb history as well as Fresco's (!)
 * 2.01 - now has repl_descs and skip_descs as separate files loaded at runtime
 * (c) Musus Umbra 1998
 */

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

#include "kernel.h"



static char usage_text[] = "museum <output>  [ -f|-b|-a <name> <file> ] ...";
static char name[] = "Museum";
static char version[] = "2.01 (25 Jun 1998)";
static char author[] = "(c) Musus Umbra 1998.";



/*--------------------------------------------------------------------------*/
/* This is an array of page description strings, the URLs for which won't	*/
/* be included in the output.												*/
/* (case sensitive, BTW).													*/

static char **skip_descs = NULL;
/*	"'Museum' generated History list", */
/*	"Museum generated history list", */
/*	"404 Not Found", */
/*	"Error accessing local file", */



/*--------------------------------------------------------------------------*/
/* These descriptions will be replaced by the URLs they are associated with */
/* This is Way Cool(tm) for "An error occurred" pages from bloody WebServe. */

static char **replace_descs = NULL;
/*	"An error occurred", */



/*--------------------------------------------------------------------------*/
/* Types																	*/

typedef enum { BROWSER_FRESCO, BROWSER_BROWSE, BROWSER_ARCWEB } BrowserType;

typedef struct lnode_
{
	struct lnode_	*next;		/* -> next node in the list */
	BrowserType		type;		/* type of hotlist file */
	char			*name;		/* name (for index) */
	char			*file;		/* filename of hotlist file */
} ListNode;








/*--------------------------------------------------------------------------*/
/* Write an error report to the (HTML) file, tack a footer onto it and then */
/* close it and exit.														*/

static void fatal( FILE *o, char *fmt, ... )
{
	va_list	ap;
	va_start(ap,fmt);

	fprintf(o,"<br><br><h2>Fatal error</h2>");
	vfprintf(o,fmt,ap);
	va_end(ap);
	fprintf(o,"\n</body>\n</html>\n");
	fclose(o);

	exit( EXIT_FAILURE );
}


/*--------------------------------------------------------------------------*/
/* Read a file of strings (one per line) and return an array of strings		*/
/* (the \ns are replaced with nulls).										*/
/* Blank lines, and lines beginning with a space are skipped.				*/

char** read_string_array( char *s )
{
	static char *null_array[] = { 0 };
	char **d;
	int strings;
	char buffer[4096];

	FILE *i = fopen(s,"r"); if ( !i ) { return null_array; }

	/* Count the strings in the file */
	for ( strings=1 ; fgets(buffer,4096,i) ; strings++ )
		if ( !*buffer || isspace(*buffer) ) { strings--; }

	/* reseek to the start */
	fseek(i,0L,SEEK_SET);

	/* allocate storage */
	d = (char**) malloc(strings*sizeof(char*));
	if ( !d )
	{
		fclose(i);
		fprintf(stderr,"Out of memory (1)\n");
		exit( EXIT_FAILURE );
	}

	/* read the strings */
	for ( strings=0 ; fgets(buffer,4096,i) ; strings++ )
	{
		int l =strlen(buffer);		/* NB: incl. LF */
		if (l>0 && !isspace(*buffer) )
		{
			buffer[l-1] = 0;
			d[strings] = (char*) malloc(l*sizeof(char));
			if ( !d[strings] )
			{
				fclose(i);
				fprintf(stderr,"Out of memory (1)\n");
				exit( EXIT_FAILURE );
			}
			strcpy(d[strings],buffer);
		}
	}

	/* terminate the array */
	d[strings] = 0;

	/* close the file and return the array */
	fclose(i);
	return d;
}



/*--------------------------------------------------------------------------*/
/* Read the configuration:													*/

static void read_config( void )
{
	skip_descs = read_string_array("Museum:Skip");
	replace_descs = read_string_array("Museum:Replace");
}



/*--------------------------------------------------------------------------*/
/* make a copy of a string, substituting &gt, &quot, etc. as needed			*/
/* returned string is in malloc-d storage so should be free-d by the caller */

static char *text_to_html( char *s )
{
	int nl = 0;
	char *q;
	char *e;

	/* Calculate the resulting string length */
	for ( q=s ; *q ; q++ )
	{
		switch ( *q )
		{
			case '<' :
			case '>' : nl += 4; break;
			case '&' : nl += 5; break;
			case '\"': nl += 6; break;
			default  : nl++;
		}
	}

	e = malloc(nl+1);
	if ( !e )
	{
		fprintf(stderr,"Out of memory\n");
		exit( EXIT_FAILURE );
	}

	/* Copy the string, doing the substitutions */
	for ( q=e ; *s ; s++ )
	{
		switch ( *s )
		{
			case '<' : strcpy(q,"&lt;"); q+=4; break;
			case '>' : strcpy(q,"&gt;"); q+=4; break;
			case '&' : strcpy(q,"&amp;"); q+=5; break;
			case '\"': strcpy(q,"&quot;"); q+=6; break;
			default  : *q++ = *s;
		}
	}
	*q = 0;
	return e;
}









/*--------------------------------------------------------------------------*/
/* Read a string from a file, terminating at a \0 or EOF.					*/
/* cf. fgets																*/

static char *n_fgets( char *buffer, int bs, FILE *i )
{
	int c = 0;
	char *o = buffer;

	while (1)
	{
		c = fgetc(i);
		if ( c==0 || c==EOF ) { break; }
		*o++ = c;
	}
	*o = 0;
	if ( c==EOF && !*buffer ) { return NULL; }
	return buffer;
}




/*--------------------------------------------------------------------------*/
/* Write the <li> line for a given <desc>/<url> pair to file 'o'			*/
/* Mainly used to implement the replace_desc and skip_desk thingies.		*/

static void write_entry( FILE *o, char *url, char *desc )
{
	char **s;

	/* Check for 'skip' */
	for ( s=skip_descs ; *s ; s++ )
		if ( !strcmp(*s,desc) )
			return;

	/* Check for 'replace' */
	for ( s=replace_descs ; *s ; s++ )
	{
		if ( !strcmp(*s,desc) )
		{
			desc = url;
			break;
		}
	}

	fprintf(o,"  <li> <a href=\"%s\">%s</a>\n",url,desc);
}

















/*--------------------------------------------------------------------------*/
/* Create the html fragment for a Fresco history file						*/

static void process_fresco( FILE *o, char *f )
{
	char url[4096], desc[4096];			/* buffers */
	FILE *i = fopen(f,"rb");
	if ( !i ) { return; }

	while ( n_fgets(url,4096,i) )
	{
		if ( !n_fgets(desc,4096,i) ) { break; }
		if ( !*url ) { break; }
		if ( !*desc ) { strcpy(desc,url); }
		write_entry(o,url,desc);
	}

	fclose(i);
}




/*--------------------------------------------------------------------------*/
/* Create the html fragment for a Browse history file						*/

static void process_browse( FILE *o, char *f )
{
	char url[4096], desc[4096];			/* buffers */
	FILE *i = fopen(f,"r");
	if ( !i ) { return; }

	/* Skip first 'garbage' line */
	if ( !fgets(url,4096,i) ) { fclose(i); return; }

	while ( fgets(url,4096,i) )
	{
		int l;
		/* Now read the description */
		if ( !fgets(desc,4096,i) ) { break; }
		l = strlen(desc);
		if ( l>0 && desc[l-1]=='\n' ) { desc[l-1] = 0; }

		/* and the URL */
		if ( !fgets(url,4096,i) ) { break; }
		l = strlen(url);
		if ( l>0 && url[l-1]=='\n' ) { url[l-1] = 0; }

		/* handle dodads... */
		if ( !*url ) { break; }
		if ( !*desc ) { strcpy(desc,url); }

		/* Write the entry */
		write_entry(o,url,desc);
	}

	fclose(i);
}





/*--------------------------------------------------------------------------*/
/* Create the html fragment for an ArcWeb history file						*/

static void process_arcweb( FILE *o, char *f )
{
	char url[4096];				/* buffer */
	char *desc;
	FILE *i = fopen(f,"r");
	if ( !i ) { return; }

	while ( fgets(url,4096,i) )
	{
		int l;
		desc = url;

		l = strlen(url);
		if ( l>0 && url[l-1]=='\n' ) { url[l-1] = 0; }
		if ( !*url ) { break; }

		while ( *desc && *desc!=' ' ) { desc++; }

		if ( *desc==' ' ) { *desc++=0; }
		if ( !*desc ) { desc = url; }

		write_entry(o,url,desc);
	}
	fclose(i);
}



/*--------------------------------------------------------------------------*/
/* Set a file's type.														*/

static _kernel_oserror *set_type( char *f, int type )
{
	_kernel_swi_regs r;
	r.r[0] = 18;
	r.r[1] = (int) f;
	r.r[2] = type;
	return _kernel_swi( 0x08, &r, &r );		/* OS_File */ 
}




/*--------------------------------------------------------------------------*/
/* Parse the command line, building the list of histories to use.			*/
/* Generate the header of the HTML output, then use subroutines to			*/
/* actually create the HTML for each node in the list.						*/
/* An attempt is made to open each history file when creating the			*/
/* list/header so that non-existent files can me marked as "unavailable".	*/

int main( int argc, char *argv[] )
{

	ListNode	*head = NULL;
	ListNode	*tail = NULL;
	ListNode	*newnode = NULL;
	FILE		*o;					/* Output stream */
	int			i;

	if ( argc<2 )					/* no arguments */
	{
		fprintf(stderr,"Usage: %s\n",usage_text);
		exit( EXIT_FAILURE );
	}

	read_config();

	/* try to open the output file */
	o = fopen(argv[1],"w");
	if ( !o )
	{
		fprintf(stderr,"%s can't create file '%s'\n",name,argv[1]);
		exit( EXIT_FAILURE );
	}

	/* Write the HTML header to the file */
	fprintf(o,"<html>\n<head>\n");
	fprintf(o,"   <title>'%s' generated History list</title>\n",name);
	fprintf(o,"</head>\n\n<body>\n");
	fprintf(o,"<h2>%s generated history list</h2>",name);
	fprintf(o,"<font size=2>generated by %s %s %s</font>",name,version,author);
	fprintf(o,"\n");


	/* Now, scan the command line building the list */
	for ( i=2 ; i<argc ; i++ )
	{
		if ( !newnode )			/* ie. we're expecting a -<flag> */
		{
			BrowserType bt = BROWSER_FRESCO;	/* to avoid cc Warnings */

			if ( argv[i][0] != '-')
			{
				fatal(o,"Expecting -f, -b or -a but found '%s'",
					text_to_html(argv[i]));
			}

			switch ( tolower(argv[i][1]) )
			{
				case 'f' :	/* Fresco list */
					bt = BROWSER_FRESCO;
					break;
				case 'b' :	/* Browse list */
					bt = BROWSER_BROWSE;
					break;
				case 'a' :	/* ArcWeb list */
					bt = BROWSER_ARCWEB;
					break;
				default :	/* Ooops. */
					fatal(o,"Expecting -f, -b or -a but found '%s'",
						text_to_html(argv[i]));
			}

			/* Create a new list node, but don't link it into the list */
			newnode = malloc(sizeof(ListNode));
			if ( !newnode ) { fatal(o,"Out of memory."); }
			newnode->type = bt;
			newnode->name = newnode->file = NULL;
			newnode->next = NULL;
		}

		else				/* ie. expecting further args */

		{
			if ( !newnode->name )		/* ie. expecting a list name */
			{
				newnode->name=argv[i];
			}
			else						/* ie. expecting a filename */
			{
				newnode->file=argv[i];
				/* Link the node to the list */
				if ( tail )
					tail->next = newnode;
				else
					head = newnode;
				tail=newnode;
				newnode = NULL;		/* so we know we're expecting a flag */
			}
		}
	}

	/* Do we actually have anything to do? */
	if ( !head ) { fatal(o,"Usage: %s",text_to_html(usage_text)); }

	/* Check that the command line was complete */
	if ( newnode ) { fatal(o,"command line incomplete"); }


	/* Now, build the header index (but only if head!=tail) */
	if ( head!=tail )
	{
		ListNode *s;

		fprintf(o,"<h4>History lists:</h4>\n<ul>\n");
		for ( s=head ; s ; s=s->next )
		{
			FILE *t = fopen(s->file,"rb");
			if ( t )
			{
				fprintf(o,"  <li> <a href=\"#%s\">%s</a>\n",s->name,s->name);
				fclose(t);
			}
			else
			{
				char *bf = text_to_html(s->file);
				fprintf(o,"  <li> <b>%s</b> - unavailable; can't read %s\n",
					s->name,bf);
				free(bf);
				s->file = NULL;		/* ie. unavailable */
			}
		}
		fprintf(o,"</ul>\n");
	}

	fprintf(o,"\n<hr>\n<br>\n");


	for ( ; head ; head=head->next )
	{
		if ( head->file )
		{
			fprintf(o,"<a name=\"%s\"></a>",head->name);
			fprintf(o,"<h4>%s</h4>\n<ul>\n",head->name);
			switch ( head->type )
			{
				case BROWSER_FRESCO :
					process_fresco(o,head->file);
					break;
				case BROWSER_BROWSE :
					process_browse(o,head->file);
					break;
				case BROWSER_ARCWEB :
					process_arcweb(o,head->file);
					break;
			}
			fprintf(o,"</ul>\n");
			if ( head->next ) { fprintf(o,"\n<br>\n<hr>\n<br>\n"); }
		}
	}


	/* Write the HTML 'footer' */
	fprintf(o,"\n</body>\n</html>\n");
	fclose(o);

	set_type(argv[1],0xfaf);
}





