/*
 * coobld
 *
 */

/*)BUILD
*/

#ifdef	DOCUMENTATION

title	coobld	Build Cookie Data File
index		Build cookie data file

synopsis

	coobld	[options] file_list

description


	Build the internal data file for fortune cookies.
	If no file argument is given, the file "cookie.txt"
	will be looked for on the current account.  The file_list
	may contain wild-cards.
	.s
	The following options are defined:
	.lm +8
	.s.i -8;-d	Debug
	.s.i -8;-v	Verify (print each cookie line as it is read)

Cookie File Format

	Cookie text files have the following format:
	.nf
		Each cookie is terminated by a line containing
		"%%" in column 1 and 2.
		%%
		  By default, a cookie that starts with one
		  or more spaces is considered to be a "poem"
		  and will not be reformatted.
		%%
		To attribute a cookie. terminate it by
		terminating the sentence with a '.', '?',
		or '!' then append the author's name as shown.
					-- Author's name.
		It is recommended that author's names start in
		column 48 where possible.  Note that the cookie
		may continue, which allows adding footnotes or
		rebuttals.
		%%
	.s.f
	Cookies are stored on a disk file in a format compatible
	with the limited amount of random access capabilities available
	on "standard" C libraries.  The format is, of course, called
	"Cookie access method."  The output file is written by fwrite()
	and must be read by fread().
	.s
	The cookie file has the following format:
	.s.nf
	Record 1:
	  long	ncookie;	/* number of cookies	*/
	  int	nindex;		/* Index table size	*/
	  int	subindex;	/* How many subindexes	*/
	  int	sindex;		/* sizeof index table	*/
	  char	date[26];	/* When file created	*/
	.s
	Record 2:
	  long	main_index[tabdim];
				/* Seek location for	*/
				/* each subindex entry	*/
	.s
	Record 3 .. tabdim+2
	  long	sub_index[tabdim];
 				/* Cookie seek address	*/
	.s
	Cookie records have just plain text, one space between words.
	Each cookie is terminated by a line containing "%%".
	.s
	Thus, to find cookie N, proceed as follows:
	.s.nf
		Read record 1.
		Allocate space for index[].
		Read record 2 into index.
	.s
		Read index[N / ncookies] into index.
		Read from index[N % ncookies].
	.s.f
	Note: if the maximum cookie contains 2047 bytes, the index table
	may be dimensioned (2047 / sizeof (long)) (== 256) and the maximum
	number of cookies equals 256 * 256 == 65536.  If this is done, and
	only one cookie is to be read, only one buffer is needed, defined as:
	.s.nf
		union {
		  char	text[TEXTSIZE];
		  long	index[TEXTSIZE / sizeof (long)];
		} cookie;

author

	Martin Minow

bugs

	probably.

#endif

#include <stdio.h>
#define	TRUE	1
#define	FALSE	0
#define	EOS	0
#ifdef	rsx
#define	R_MODE	"run"
#define	W_MODE	"wun"
#else
#ifdef	rt11
#define	R_MODE	"rn"
#define	W_MODE	"wn"
#else
#define	R_MODE	"r"
#define	W_MODE	"w"
#endif
#endif

#define	SIGNAL	'%'

extern long	ftell();

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

#define	INPUT_FILE	"cookie.txt"

/*
 * The following may need work on RT11 to insert file sizes
 */

char	*text_file	= "ctext.tmp";
char	*dummy_file	= "cdummy.tmp";
char	*index_file	= "cindex.tmp";
char	*cookie_file	= "cookie.fil";

struct header {
	long	ncookie;	/* Number of cookies		*/
	short	nindex;		/* Dimension of top_index[]	*/
	short	subindex;	/* Number of subindex entries	*/
	short	sindex;		/* Sizeof index for alloc	*/
	char	date[28];	/* Date cookie file built	*/
} header;

char	text[513];		/* Working text			*/
long	*sub_index; 		/* Indexes stored here		*/
long	*top_index;		/* Top level indexes go here	*/
long	firstindex;		/* -> top index in indexfp	*/
long	firstcookie;		/* -> first cookie in dummyfp	*/
int	verbose		= 0;
int	debug		= 0;

main(argc, argv)
int 	argc;
char	*argv[];
{
	register char	*ap;
	register int	i;
	int		nfiles;
	FILE		*file_open();

	nfiles = 0;
	for (i = 1; i < argc; i++) {
	    ap = argv[i];
	    if (*ap++ == '-') {
		argv[i] = NULL;
		for (; *ap != EOS; ap++) {
		    switch (tolower(*ap)) {

		    case 'd':
			debug++;
			break;

		    case 'v':
			verbose++;
			break;

		    default:
			fprintf(stderr, "?Unknown option '%c'\n", *ap);
		    }
		}
	    }
	    else {
		nfiles++;
	    }
	}
	
	/*
	 * Copy raw cookies to a temp file.  Collect how many and
 	 * dimension of indexes.
	 */
	if (nfiles == 0) {
	    argc = 2;
	    argv[1] = INPUT_FILE;
	}
	maketext(argc, argv);
	/*
	 * Build dummy cookie file
	 */
	dummyfp = file_open(dummy_file, W_MODE);
	indexfp = file_open(index_file, W_MODE);
	makedummy(argc, argv);
	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);
	cdelete(indexfp);
}

cdelete(fd)
FILE		*fd;
/*
 * Close or delete the file
 */
{
	char	work[40];

	if (debug)
	    fclose(fd);
	else {
	    fgetname(fd, work);
	    fclose(fd);
	    delete(work);
	}
}

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);
	    error("?COOBLD-F-Can't %s \"%s\"\n",
		(*mode == 'w') ? "create" : "open", filename);
	}
	return(fd);
}

maketext(argc, argv)
int		argc;
char		*argv[];
/*
 * Read files obtaining the counts.
 *
 *	Header is set as follows:
 *
 *		header.ncookie		Number of cookies (long)
 *		header.nindex		Index dimension
 *		header.subindex		Number of subindex entries
 *		header.sindex		Sizeof index[]
 *		header.date		ctime()
 */
{
	register int		len;
	long			subsquare;
	char			*cpystr();
	register int		bigbytes;	/* Longest record bytes	*/
	int			tvec[2];
	FILE			*infd;
	register int		nfiles;
	int			i;
	int			nrecords;

	bigbytes = 0;
	time(&tvec);
	cpystr(&header.date, ctime(&tvec));
	header.ncookie = 0;
	header.nindex = 0;
	subsquare = 0;
	len = 0;
	/*
	 * Read all cookies to count them and get the max. length.
	 */
	for (i = 1; i < argc; i++) {
	    if (argv[i] == NULL)
		continue;
	    if ((infd = fwild(argv[i], "r")) == NULL) {
		perror(argv[i]);
		continue;
	    }
	    for (nfiles = 0; fnext(infd); nfiles++) {
		fgetname(infd, text);
		printf("%s:", text);
		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 > bigbytes)
			    bigbytes = len;
			len = 0;
			header.ncookie++;
			if (subsquare < header.ncookie) {
			    header.nindex++;
			    subsquare = header.nindex * header.nindex;
			}
		    }
		    else {
			len += strlen(text) + 2;
		    }
		}
		printf("\t%d\n", nrecords);
	    }
	    if (nfiles == 0) {
		printf("No files match \"%s\"\n", argv[i]);
	    }
	}
	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, bigbytes);
	printf("top index = %d, sub index = %d, index area size = %d\n",
	    header.nindex, header.subindex, header.sindex);
}

makedummy(argc, argv)
int		argc;
char		*argv[];
/*
 * 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		*/
	int		i;
	FILE		*infd;
	long		count;
	long		dummy_loc;

	count = 0;
	if ((top_index = calloc(header.sindex, 1)) == NULL
	 || (sub_index = calloc(header.sindex, 1)) == NULL)
	    error("Can't allocate index buffers -- %d bytes\n", header.sindex);
	put(&header, sizeof header, dummyfp, "dummy header");
	put(&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;
	for (i = 1; i < argc; i++) {
	    if (argv[i] == NULL)
		continue;
	    if ((infd = fwild(argv[i], "r")) == NULL)
		continue;
	    while (fnext(infd) != NULL) {
		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) {
				error("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++;
#ifdef	rsx
			fput("%%\n", 4, dummyfp);
#else
			fputs("%%\n", dummyfp);
#endif
			dummy_loc = ftell(dummyfp);
		    }
		    else {
#ifdef	rsx
			fput(text, strlen(text) + 1, dummyfp);
#else
			fputs(text, dummyfp);
#endif
			len += strlen(text);
		    }
		    if (ferror(dummyfp)) {
			perror("writing text to temp file");
			error("Output error");
		    }
		}
	    }
	}
	/*
	 * 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)
	    error("Expected %ld cookies, read %ld\n", header.ncookie, count);
}

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

	put(&header, sizeof header, outfp, "cookie header");
	put(top_index, header.sindex, outfp, "cookie top index");
	itemct = 0;
	if (fseek(indexfp, firstindex, 0) != 0)
	    error("Can't seek to %ld on index file\n", firstindex);
	if (fseek(dummyfp, firstcookie, 0) != 0)
	    error("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");
	    put(sub_index, header.sindex, outfp, "cookie sub index");
	}
	printf("%d index records written\n", header.nindex + 1);
#ifdef	rsx
	while (fget(text, sizeof text, dummyfp), !feof(dummyfp)) {
	    fput(text, strlen(text) + 1, outfp);
#else
	while (fgets(text, sizeof text, dummyfp) != NULL) {
	    fputs(text, outfp);
#endif
	}
	if (ferror(outfp)) {
	    perror("writing output");
	    error("Output error");
	}
}

/*
 * Raw I/O routines
 */

get(whereto, size, fd, why)
char		*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;

#ifdef	rsx
	if ((i = fget(whereto, size, fd)) != size || ferror(fd)) {
	    perror("coobld fget error");
	    error("Reading %s, expected %d bytes, read %d bytes\n",
		why, size, i);
	}
#else
	if ((i = fread(whereto, size, 1, fd)) != 1 || ferror(fd)) {
	    perror("coobld fread error");
	    error("Reading %s, expected 1 item, %d bytes, read %d items\n",
		why, size, i);
	}
#endif
}

put(wherefrom, size, fd, why)
char		*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;

#ifdef	rsx
	if ((i = fput(wherefrom, size, fd)) != size || ferror(fd)) {
	    perror("coobld fput error");
	    error("Error writing %d bytes to %s, %d bytes written\n",
		size, why, i);
	}
#else
	if ((i = fwrite(wherefrom, size, 1, fd)) != 1 || ferror(fd)) {
	    perror("coobld fwrite error");
	    error("Error writing 1 item of %d bytes to %s, %d items\n",
		size, why, i);
	}
#endif
}

/*
 * For debugging only
 */

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

	printf("\n%s\n", why);
	for (i = 0; i < header.nindex; i++)
	    printf("%3d %ld\n", i, indextable[i]);
}
