/*
 * coobld - The incredible fortune cookie system database builder
 */

/* Version T1.00 - 20-Nov-86 - tmk - Initial portable version		*/
/* Version T1.01 - 05-Jun-87 - tmk - Modify for new header structure,
				     add password, fix -d option which
				     broke on swappped pointer machines
				     like MSDOS, add -o option to specify
				     output file name			*/
/* Version T1.02 - 24-Jun-87 - tmk - Modify for new file structure, get
				     rid of password scheme		*/
/* Version T1.03 - 30-Jun-87 - tmk - Add help info			*/
/* Version T1.04 - 02-Jul-87 - tmk - Add support for Turbo C		*/
/* Version T1.05 - 27-Nov-87 - tmk - Add function prototypes, fix arg type
				     problem with get() and put()	*/
/* Version T1.06 - 18-Jul-88 - tmk - Make file I/O errors fatal, set error
				     level on MS-DOS			*/
/* Version V1.20 - 24-Jul-88 - tmk - Official release version (finally)	*/
/* Version V1.21 - 21-Dec-89 - tmk - Add support for DEC PDP-11 C V1.0	*/
/* Version V1.23 - 19-Nov-90 - tmk - Add support for 2.10BSD Unix	*/
/* Version V1.24 - 01-Sep-91 - tmk - 2BSD Unix doesn't need byte swaps	*/

#define VERSION		"V1.24 01-Sep-91 - tmk"

/*
 * Define module ID for VMS (must go for Turbo C and Unix, else bogus error)
 */
/*ifdef Pseudo-ifdef for Turbo C and Unix */
#ifdef	vms
#ifdef	__DECC
#pragma	module	coobld	VERSION
#else
#module	coobld	VERSION
#endif	/* __DECC */
#endif	/* vms */
/*ifdef Pseudo-ifdef for Turbo C and Unix */
#ifdef	__pdp11c
#pragma list title VERSION
#endif	/* __pdp11c */

/*
 * Convince Turbo C it's running under MS-DOS (it isn't sure)
 */
#ifdef	__TURBOC__
#define	MSDOS		1
#endif	/* __TURBOC__ */

/*
 * Grab some header files
 */
#include <stdio.h>
#include <ctype.h>
#include <time.h>
#ifndef	decus
#ifndef	unix
#include <stdlib.h>
#endif	/* unix */
#include <string.h>
#endif	/* decus */
#ifdef	MSDOS
#ifndef	__TURBOC__
#include <malloc.h>
#else
#include <alloc.h>
#endif	/* __TURBOC__ */
#endif	/* MSDOS */

/*
 * Have a fight about function declarations, names, and file modes
 */
#ifdef	decus
#define	w_mode	"wn"
#define	r_mode	"rn"
#define	unlink	delete
#define	void
#define gotfget
#else
#define	w_mode	"wb"
#define r_mode	"rb"
#endif	/* decus */
#ifdef	__pdp11c
#define	unlink	remove
#endif	/* __pdp11c */
#ifdef	vms
#define	unlink	remove
#define	gotfget
#endif

/*
 * Define some constants
 */
#define	TRUE	1
#define	FALSE	0
#define	EOS	0
#define	SIGNAL	'%'
#ifdef	vms
#define	IO_SUC	65535
#define	IO_ERR	65535
#else
#define	IO_SUC	0
#define	IO_ERR	1
#endif	/* vms */

/*
 * Global variables
 */
FILE	*indexfp;			/* Indices stored here		*/
FILE	*dummyfp;			/* Dummy output file		*/
FILE	*outfp;				/* Output file (cookie.fil)	*/

char	*dummy_file	= "cdummy.tmp";
char	*index_file	= "cindex.tmp";
char	*cookie_file	= "cookie.fil";
char	*input_file	= "cookie.txt";

struct header {
#ifdef	vms
	short	coover;			/* Version of CAM structure	*/
	short	spare;			/* Spare value (was password)	*/
	long	ncookie;		/* Number of cookies		*/
	short	bcookie;		/* Size of largest cookie	*/
	short	nindex;			/* Dimension of index[]		*/
	short	subindex;		/* Number of subindex entries	*/
	short	sindex;			/* Sizeof index for alloc	*/
#else
	int	coover;			/* Version of CAM structure	*/
	int	spare;			/* Spare value (was password)	*/
	long	ncookie;		/* Number of cookies		*/
	int	bcookie;		/* Size of largest cookie	*/
	int	nindex;			/* Dimension of index[]		*/
	int	subindex;		/* Number of subindex entries	*/
	int	sindex;			/* Sizeof index for alloc	*/
#endif	/* vms */
	char	date[28];		/* Date cookie file built	*/
} header;

char	text[513];			/* Working text			*/
long	*sub_index; 			/* Indices stored here		*/
long	*top_index;			/* Top level indices go here	*/
long	firstindex;			/* -> top index in indexfp	*/
long	firstcookie;			/* -> first cookie in dummyfp	*/
int	debug	= 0;			/* Debug initally off		*/
int	width	= 6;			/* Debug width initially 80	*/

/*
 * For 'modern' compilers (all but decus), supply function prototypes
 */
#ifndef	decus
#ifdef	unix
extern	int  main();
extern	void cdelete();
extern	FILE * file_open();
extern	void maketext();
extern	void makedummy();
extern	void makecookie();
extern	void get();
extern	void put();
extern	void hswap();
extern	void swaplb();
extern	long swapl();
extern	void dump();
#else
extern	int  main(int, char **);
extern	void cdelete(FILE *, char *);
extern	FILE * file_open(char *, char *);
extern	void maketext(char *);
extern	void makedummy(char *);
extern	void makecookie(void);
extern	void get(long *, int, FILE *, char *);
extern	void put(long *, int, FILE *, char *);
extern	void hswap(void);
extern	void swaplb(long *);
extern	long swapl(long);
extern	void dump(long *, char *, int);
#endif	/* unix */
extern	long ftell();			/* decus doesn't define???	*/
#endif	/* decus */

/*
 * Actual code starts here
 */
int main(argc, argv)
int 	argc;
char	*argv[];
{
	register char	*ap;
	FILE		*file_open();

	while (argc > 1) {
	    ap = argv[1];
	    if (*ap != '-') {
		fprintf(stderr, "?Unknown command \"%s\"\n", ap);
#ifdef	unix
		fprintf(stderr, "  do COOBLD -z for help.\n\n\n");
#else
		fprintf(stderr, "  do COOBLD -? for help.\n\n\n");
#endif	/* unix */
	    }
	    else for (ap++; *ap; ap++) {
#ifdef	unix
		switch (isupper(*ap) ? tolower(*ap) : *ap) {
#else
		switch (tolower(*ap)) {
#endif	/* unix */
		case 'd':		/* Set debug option		*/
		    debug++;
		    break;

		case 'f':		/* Use different input file	*/
		    if (isgraph(ap[1]) != 0)
			input_file = &ap[1];
		    else if (argc > 2) {
			input_file = argv[2];
			argv++;
			argc--;
		    }
		    else {
			break;
		    }
		    goto next_arg;

		case 'o':		/* Use different output file	*/
		    if (isgraph(ap[1]) != 0)
			cookie_file = &ap[1];
		    else if (argc > 2) {
			cookie_file = argv[2];
			argv++;
			argc--;
		    }
		    else {
			break;
		    }
		    goto next_arg;

		case 'w':
		    width = 10;		/* Set debug width to 132	*/
		    break;


#ifdef	unix
		case 'z':		/* Display help message		*/
#else
		case '?':		/* Display help message		*/
#endif	/* unix */
		    printf("COOBLD %s",VERSION);
#ifdef	unix
		    printf("\n\nUsage is: coobld option option...\n");
#else
		    printf("\n\nUsage is: COOBLD option option...\n");
#endif	/* unix */
		    printf("Options: -d     Enable debugging output\n");
		    printf("         -f fn  Read input from file FN\n");
		    printf("         -o fn  Write output to file FN\n");
		    printf("         -w     132-column debug output\n");
#ifdef	unix
		    printf("         -z     Print this message\n");
#else
		    printf("         -?     Print this message\n");
#endif	/* unix */
		    exit(IO_SUC);
		    break;

		default:
		    fprintf(stderr, "?Unknown option '%c'\n", *ap);
#ifdef	unix
		    fprintf(stderr, "  do COOBLD -z for help.\n\n\n");
#else
		    fprintf(stderr, "  do COOBLD -? for help.\n\n\n");
#endif	/* unix */
		}
	    }
	    next_arg:
	    argc--;
	    argv++;
	}
	/*
	 * Copy raw cookies to a temp file.  Collect how many and
 	 * dimension of indexes.
	 */
	maketext(input_file);
	/*
	 * Build dummy cookie file
	 */
	dummyfp = file_open(dummy_file, w_mode);
	indexfp = file_open(index_file, w_mode);
	makedummy(input_file);
	fclose(dummyfp);
	fclose(indexfp);
	/*
	 * Build real cookie file
	 */
	outfp = file_open(cookie_file,  w_mode);
	dummyfp = file_open(dummy_file, r_mode);
	indexfp = file_open(index_file, r_mode);
	makecookie();
	fclose(outfp);
	cdelete(dummyfp, dummy_file);
	cdelete(indexfp, index_file);
	printf("Done, %ld cookies in output file %s\n", header.ncookie,
	    cookie_file);
	exit(IO_SUC);
}

void cdelete(fd, fn)
FILE		*fd;
char		*fn;
/*
 * Close or delete the file
 */
{
	if (debug)
	    fclose(fd);
	else {
	    fclose(fd);
	    unlink(fn);
	}
}

FILE * file_open(filename, mode)
char		*filename;
char		*mode;
/*
 * Open the file, die if failure
 */
{
	register FILE	*fd;

	if ((fd = fopen(filename, mode)) == NULL) {
	    perror(filename);
	    exit(IO_ERR);
	}
	return (fd);
}

void maketext(infn)
char		*infn;			/* Input file name		*/
/*
 * Read files obtaining the counts.
 *
 *	Header is set as follows:
 *
 *		header.coover		CAM revision level (1.02)
 *		header.spare		Spare value
 *		header.ncookie		Number of cookies (long)
 *		header.bcookie		Size of largest cookie
 *		header.nindex		Index dimension
 *		header.subindex		Number of subindex entries
 *		header.sindex		Sizeof index[]
 *		header.date		ctime()
 */
{
	register int		len;
	long			subsquare;
	char			*strcpy(char *dst, const char *src);
#ifdef	__DECC
	unsigned long		tvec;
#else
	long			tvec;
#endif	/* __DECC */
	FILE			*infd;
	long    		nrecords;

	header.bcookie = 0;
	time(&tvec);
	strcpy(header.date, ctime(&tvec));
	header.coover = 102;
	header.ncookie = 0;
	header.nindex = 0;
	subsquare = 0;
	len = 0;
	/*
	 * Read all cookies to count them and get the max. length.
	 */
	if ((infd = fopen(infn, "r")) == NULL) {
	    perror(infn);
	    exit(IO_ERR);
	}
	{
#ifndef gotfget
	printf("%s:", infn);		/* Print filename and info	*/
#else
	fgetname(infd, text);
	printf("%s:", text);		/* Print filename and info	*/
#endif	/* gotfget */
#ifdef	__pdp11c
	fflush(stdout);
#endif	/* __pdp11c */
	nrecords = 0;
	while (!feof(infd)) {
	    fgets(text, sizeof(text), infd);
	    nrecords++;
	    if (feof(infd) || (text[0] == SIGNAL && text[1] == SIGNAL)) {
		if (len == 0)
		    continue;
		if (len > header.bcookie)
		    header.bcookie = len;
		len = 0;
		header.ncookie++;
		if (subsquare < header.ncookie) {
		    header.nindex++;
		    subsquare = header.nindex * header.nindex;
		}
	    }
	    else {
		len += strlen(text) + 2;
	    }
	}
	printf(" %ld lines\n", nrecords);
	}
	header.subindex = (header.ncookie + header.nindex - 1) / header.nindex;
	header.sindex = header.nindex * sizeof(long);
	printf("%ld cookies read, the longest has %d bytes\n",
	    header.ncookie, header.bcookie);
	printf("top index = %d, sub index = %d, index area size = %d\n",
	    header.nindex, header.subindex, header.sindex);
}

void makedummy(infn)
char		*infn;
/*
 * Build a dummy cookie file in two separate files:
 *	dummyfp		Gets the cookie data
 *	indexfp		Gets the indices.
 *
 * This way, we don't have to reposition the file, nor do we
 * have to read and write the same file.
 */
{
	register int	subi;		/* Index into sub_index[]	*/
	register int	topi;		/* Index into top_index[]	*/
	register int	len;		/* Input record length		*/
	FILE		*infd;
	long		count;
	long		dummy_loc;

	count = 0;
	top_index = (long *)calloc(header.sindex, 1);
	sub_index = (long *)calloc(header.sindex, 1);
	if (top_index == NULL || sub_index == NULL)
	    fprintf(stderr, "Can't allocate index buffers -- %d bytes\n",
		header.sindex);
	put((long *)"555-2368", 9, dummyfp, "dummy magic number");
	put((long *)"555-2368", 9, indexfp, "index magic number");
	put((long *)&header, sizeof(header), dummyfp, "dummy header");
	put((long *)&header, sizeof(header), indexfp, "index header");
	put(sub_index, header.sindex, dummyfp, "dummy top index");
	put(sub_index, header.sindex, indexfp, "index top index");
	firstindex = ftell(indexfp);
	for (subi = header.subindex; --subi >= 0;) {
	    put(sub_index, header.sindex, dummyfp, "dummy sub index");
	}
	dummy_loc = firstcookie = ftell(dummyfp);
	subi = 0;
	topi = 0;
	len = 0;
	if ((infd = fopen(infn, "r")) == NULL) {
	    perror(infn);
	    exit(IO_ERR);
	}
	{
	while (!feof(infd)) {
	    if (fgets(text, sizeof(text), infd) == NULL
		|| (text[0] == '%' && text[1] == '%')) {
		if (len == 0)
		    continue;
		len = 0;
		count++;
		if (subi >= header.nindex) {
		    if (topi >= header.nindex) {
			fprintf(stderr, "Too many cookies, max is %d ** 2\n",
			    header.nindex);
		    }
		    top_index[topi] = ftell(indexfp);
		    topi++;
		    put(sub_index, header.sindex, indexfp, "sub index");
		    subi = 0;
		}
		sub_index[subi] = dummy_loc;
		subi++;
		fputs("%%\n", dummyfp);
		dummy_loc = ftell(dummyfp);
	    }
	    else {
		fputs(text, dummyfp);
		len += strlen(text);
	    }
	    if (ferror(dummyfp)) {
		perror("writing text to temp file");
		exit(IO_ERR);
	    }
	}
	}
	/*
	 * Put the last subindex record
	 */
	while (subi < header.nindex) {
	    sub_index[subi] = -1;
	    subi++;
	}
	top_index[topi] = ftell(indexfp);
	topi++;
	put(sub_index, header.sindex, indexfp, "last sub index");
	while (topi < header.nindex) {
	    top_index[topi] = -1;
	    topi++;
	}
	printf("Work files built, %ld cookies, %d index levels\n",
	    count, header.nindex);
	if (count != header.ncookie)
	    fprintf(stderr, "Expected %ld cookies, read %ld\n",
		header.ncookie, count);
}

void makecookie()
/*
 * Write outfp with cookie file, using
 *
 *	indexfp		Index file (has sub-indexes)
 *	dummyfp		Cookie work file
 *
 */
{
	register int	i;

	put((long *)"555-2368", 9, outfp, "cookie magic number");
#ifndef	decus
#ifndef	unix
	hswap();			/* Swap to PDP-11 format	*/
#endif	/* unix */
#endif	/* decus */
	put((long *)&header, sizeof(header), outfp, "cookie header");
#ifndef	decus
#ifndef	unix
	hswap();			/* And back to system format	*/
	swaplb(top_index);		/* Swap to PDP-11 format	*/
#endif	/* unix */
#endif	/* decus */
	put(top_index, header.sindex, outfp, "cookie top index");
#ifndef	decus
#ifndef	unix
	swaplb(top_index);		/* And back to system format	*/
#endif	/* unix */
#endif	/* decus */
	if (debug)
	    dump(top_index, "cookie top index", -1);
	if (fseek(indexfp, firstindex, 0) != 0)
	    fprintf(stderr, "Can't seek to %ld on index file\n", firstindex);
	if (fseek(dummyfp, firstcookie, 0) != 0)
	    fprintf(stderr, "Can't seek to %ld on cookie file\n", firstcookie);
	for (i = 0; i < header.subindex; i++) {
	    get(sub_index, header.sindex, indexfp, "sub index");
#ifndef	decus
#ifndef	unix
	    swaplb(sub_index);		/* Swap to PDP-11 format	*/
#endif	/* unix */
#endif	/* decus */
	    put(sub_index, header.sindex, outfp, "cookie sub index");
#ifndef	decus
#ifndef	unix
	    swaplb(sub_index);		/* And back to system format	*/
#endif	/* unix */
#endif	/* decus */
	    if (debug)
		dump(sub_index, "cookie sub index", i);
	}
	printf("%d index records written\n", header.nindex + 1);
	while (fgets(text, sizeof(text), dummyfp) != NULL) {
	    fputs(text, outfp);
	}
	if (ferror(outfp)) {
	    perror("writing output");
	    exit(IO_ERR);
	}
}
/*
 * Raw I/O routines
 */
void get(whereto, size, fd, why)
long		*whereto;		/* Where to read to		*/
int		size;			/* Buffer size			*/
FILE		*fd;			/* Input file descriptor	*/
char		*why;			/* Who is reading for error	*/
/*
 * Read into the buffer.  Return the number of bytes read.
 * All errors are fatal.
 */
{
	register int	i;

	if ((i = fread(whereto, size, 1, fd)) != 1 || ferror(fd)) {
	    perror("coobld fread error");
	    fprintf(stderr, "Reading %s, expected 1 item, %d bytes, read %d items\n",
		why, size, i);
	    exit(IO_ERR);
	}
}

void put(wherefrom, size, fd, why)
long		*wherefrom;		/* Where to write from		*/
int		size;			/* Number of bytes to write	*/
FILE		*fd;			/* Output file descriptor	*/
char		*why;			/* Who is writeing for error	*/
/*
 * Write from the buffer. All errors are fatal.
 */
{
	register int		i;

	if ((i = fwrite(wherefrom, size, 1, fd)) != 1 || ferror(fd)) {
	    perror("coobld fwrite error");
	    fprintf(stderr, "Error writing 1 item of %d bytes to %s, %d items\n",
		size, why, i);
	    exit(IO_ERR);
	}
}

#ifndef	decus
#ifndef	unix
void hswap()
/*
 * Swap the header.xxx components to/from PDP-11 format
 */
{
	header.ncookie  = swapl(header.ncookie);
}

void swaplb(indextable)
long	indextable[];
/*
 * Swap an index table to/from PDP-11 format
 */
{
	register int i;

	for (i=0; i < header.nindex; i++)
	    indextable[i] = swapl(indextable[i]);
}

long swapl(l)				/* With thanks to Dennis Bednar	*/
long	l;
{
	register char *sp, *dp;
		 long r;

	sp = (char *) &l;
	dp = (char *) &r;

	*dp++ = sp[2];			/* PDP-11 stores bytes 2,3,0,1	*/
	*dp++ = sp[3];
	*dp++ = sp[0];
	*dp   = sp[1];
	return (r);
}
#endif	/* unix */
#endif	/* decus */

/*
 * For debugging only
 */

void dump(indextable, why, which)
long	indextable[];
char	*why;
int	which;
{
	register int i, j;

	printf("\n%s", why);
	if (which != -1)
	    printf(" number %d", which);
	printf(":\n");
	for (i = 0; i < header.nindex; i = i+width) {
	    for (j = 0; j < width; j=j+1) {
		if (i+j >= header.nindex)
		    break;
		printf("%3d %7ld", i+j, indextable[i+j]);
		if (i+j == header.nindex-1)
		    break;
		if (j == width-1)
		    break;
		printf(", ");
	    }
	    printf("\n");
	}
}
