/*		C O O K I E
 *
 * Print a random cookie.
 *
 */

/*)BUILD
*/

#ifdef	DOCUMENTATION

title	cookie		Print a Random Message
index			Print a Random Message

synopsis

	cookie [options]

description

	Cookie prints a fortune cookie.  The options are:
	.lm +8
	.s.i- 8;-N	Output N cookies.
	.s.i- 8;N	Output the Nth cookie.
	.s.i- 8;-a	Output all cookies in entry order.
	cookie -a -a appends %% to each cookie (so that the
	output file may be fed back to coobld).
	.s.i -8;-f file	Read cookies from this file.  (Ignore
	search list.)
	.s.i- 8;-h	Eliminate Heinlien and Lazarus Long cookies
	.s.i- 8;-v	Video mode -- Format for a VT52 or VT100
	(VT100 assumed).
	.s.i- 8;-vt52	Explicitly format for a VT52 or VT100 in VT52 mode
	.s.i- 8;-vt100	Explicitly format for a VT100 in ANSI mode
	.s.i- 8;-c	Continuous -- give lots of random cookies
	.s.i- 8;-s N	Continuous with specified sleep interval.  This may
	also be specified "-sN".  If no number is given, "-s 60" will be
	assumed.
	.s.i -8;-w file	Write cookies to the file as well as
	to the screen.
	.s.i -8;-d	Write cookies to the file as well as to the screen.
	(The file is assumed to be a DECtalk terminal.)
	.s.i- 8;/HEINLIEN=NO##Same as -h, but for VMS systems
	.s.lm -8
	To enable elimination of cookies by Robert A. Heinlien and his alter
	ego Lazarus Long, define "NOHEINLIEN".  If eliminated, a suitable
	anticookie will be presented.
	.s
	Cookie uses the following search list to locate the cookie file:
	.s.nf
		[]cookie.fil
		sys_cookie:cookie.fil
		public:cookie.fil
		pub:cookie.fil
		sys$public:cookie.fil
		games:cookie.fil
		sys$games:cookie.fil
	.s.f
author

	Martin Minow

bugs

#endif

#define	NOHEINLIEN	1

#include	<stdio.h>
#include	<ctype.h>

#ifdef	vms
extern int		errno;
#define	IO_ERROR	errno
#endif
#ifdef	rsx
#define	R_MODE		"run"
#else
#ifdef	rt11
#define	R_MODE		"rn"
#else
#define	R_MODE		"r"
#endif
#endif
#ifdef	decus
int	$$narg	= 1;		/* No argv> prompt			*/

extern int	$$rsts;
#else
int		$$rsts = 0;
#define	streq(a,b)	((strcmp(a,b) == 0))
#endif
extern long	rand();
extern long	seed;
extern long	time();

#define	EOS	0

int	doall		= 0;
int	video		= 0;
int	isvt52		= 0;
int	notLong		= 0;		/*  nor Heinlien		*/
int	continuous	= 0;
int	long_sleep	= 0;
FILE	*extrafd	= NULL;
FILE	*talkfd		= NULL;
int	cur_line	= 0;
int	linecount	= 0;
char	*old_buff	= NULL;
char	sc_buff[121];
char	temp_text[81];

FILE		*cookfd = NULL;
char		*cookfile[] = {		/* Cookie search list		*/
	"cookie.fil",			/* Current directory first	*/
	"games:cookie.fil",		/* Some				*/
	"game:cookie.fil",		/*   games			*/
	"sys$games:cookie.fil",		/*     directories		*/
	"public:cookie.fil",		/* Various			*/
	"pub:cookie.fil",		/*   public			*/
	"sys$public:cookie.fil",	/*     directories		*/
	NULL,
};

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

#define	NCOOKIES	(header.ncookie)
#define	TEXTSIZE	2048
#define	INDEXMAX	(TEXTSIZE / sizeof (long))

union {
	char text[TEXTSIZE];		/* Cookies stored here		*/
	long index[INDEXMAX];		/* Cookie sub index entries	*/
} t;

/*
 * If only one cookie is needed, topindex[] can be overlayed over t.index[]
 */

long	topindex[INDEXMAX];		/* Top index entries		*/

main(argc, argv)
int		argc;
char		*argv[];
{
	register int	howmany;		/* How many to do	*/
	register char	*ap;
	register int	outtime;
#ifdef	rsx
	int		noargs;
	extern int	$$pos;			/* TRUE on P/OS		*/
#endif
	long		which;
	long		when;
	long		atol();

	which = 0;
#ifdef	rsx
	noargs = (argc <= 1);			/* For P/OS		*/
#endif
	while (argc > 1) {
	    ap = argv[1];
	    if (isdigit(*ap) || (*ap == '-' && isdigit(ap[1])))
		which = atol(ap);
	    else if (*ap != '-') {
#ifdef	NOHEINLIEN
		if (streq(ap, "/HEINLIEN=NO")
		 || streq(ap, "/heinlien=no"))
		    notLong++;
		else
#endif
		    fprintf(stderr, "?Unknown command \"%s\"\n", ap);
	    }
	    else for (ap++; *ap; ap++) {
		if (isupper(*ap))
		    *ap = tolower(*ap);
		switch (*ap) {
		case 'a':
		    doall++;
		    break;

		case 'c':
		    continuous++;
		    break;

		case 'f':
		    if ((cookfd = fopen(argv[2], R_MODE)) == NULL) {
			perror(argv[2]);
			exit(IO_ERROR);
		    }
		    argv++;
		    argc--;
		    goto next_arg;

#ifdef	NOHEINLIEN
		case 'h':
		    notLong++;			/* No (Lazarus) Longs	*/
		    break;
#endif
		case 's':			/* Long sleep		*/
		    continuous++;
		    if (isdigit(ap[1])) {
			long_sleep = atoi(&ap[1]);
		    }
		    else if (isdigit(argv[2][0])) {
			long_sleep = atoi(argv[2]);
			argv++;			/* Skip next argument	*/
			argc--;			/* too.			*/
		    }
		    else {
			long_sleep = 60;
			break;
		    }
		    goto next_arg;

		case 'v':
		    video = 1;
		    if (ap[1] == 't' || ap[1] == 'T') {
			video = atoi(&ap[2]);
			if (video == 52)
			    isvt52 = 1;
			else if (video == 100)
			    isvt52 = 0;
			else
			    fprintf(stderr, "?Unknown video \"%s\"\n", ap);
			goto next_arg;
		    }
		    break;

		case 'd':
		    if ((talkfd = fopen(argv[2], "w")) == NULL) {
			perror(argv[2]);
			exit(IO_ERROR);
		    }
		    argv++;
		    argc--;
		    goto next_arg;

		case 'w':
		    if ((extrafd = fopen(argv[2], "w")) == NULL) {
			perror(argv[2]);
			exit(IO_ERROR);
		    }
		    argv++;
		    argc--;
		    goto next_arg;

		default:
		    fprintf(stderr, "?Unknown option '%c'\n", *ap);
		    fprintf(stderr, "Option string is \"%s\"\n", ap);
		}
	    }
next_arg:
	    argc--;
	    argv++;
	}
	setup_cookie();
	if (video)
	    erpage(1, 1);
	if (doall) {
	    for (which = 1; which <= NCOOKIES; which++) {
		docookie(which, time(NULL));
	    }
	}
	if (continuous)
	    which = -1;
	if (which > 0) {
	    if (which > NCOOKIES) {
		sprintf(t.text, "Misfortune: there are only %ld cookies.\n",
		    NCOOKIES);
		output(which, t.text, time(NULL));
	    }
	    else
		docookie(which, time(NULL));
	}
	else {
	    if ((howmany = -which) == 0)
		howmany = 1;
	    while (howmany-- > 0) {
		which = rand();
		when = time(NULL);
		which = ((which ^ when) & 0x3FFFFFFFL) % NCOOKIES;
		docookie(which + 1, when);
		if (continuous)
		    howmany = 1;
		if (howmany > 0) {
		    if (long_sleep > 0)
			sleep(long_sleep);
		    else {
			outtime = (linecount + 1) * 2;
			if (outtime > 0)
			    sleep(outtime);
		    }
		}
	    }
	}
finish:	if (extrafd != NULL)
	    fclose(extrafd);
	if (talkfd != NULL)
	    fclose(talkfd);
}

setup_cookie()
/*
 * Initialize cookie file, finding it in the search list
 */
{
	register char	**namep;
	register int	temp;

	if (cookfd == NULL) {
	    for (namep = cookfile; *namep != NULL; namep++) {
		if ((cookfd = fopen(*namep, R_MODE)) != NULL) {
		    break;
		}
	    }
	    if (cookfd == NULL) {
		fprintf(stderr, "Can't open cookie file\n");
		exit(IO_ERROR);
	    }
	}
#ifdef	rsx
	if ((temp = fget(&header, sizeof header, cookfd)) != sizeof header)
		rerror(sizeof header, temp, "Reading header file");
	if ((temp = fget(&topindex, header.sindex, cookfd)) != header.sindex)
		rerror(header.sindex, temp, "Reading top-level index");
#else
	if ((temp = fread(&header, sizeof header, 1, cookfd)) != 1)
		rerror(sizeof header, temp, "Reading header file");
	if ((temp = fread(&topindex, header.sindex, 1, cookfd)) != 1)
		rerror(header.sindex, temp, "Reading top-level index");
#endif
}

rerror(expected, got, why)
int		expected;		/* Bytes in item		*/
int		got;			/* Unexpectedly isn't == 1	*/
char		*why;
/*
 * Fatal read error
 */
{
	perror(why);
#ifdef	rsx
	fprintf(stderr, "expected %d bytes, read %d bytes\n",
		expected, got);
#else
	fprintf(stderr, "expected 1 item of %d bytes, read %d items\n",
			expected, got);
#endif
	fprintf(stderr, "Can't continue\n");
	exit(IO_ERROR);
}

docookie(which, when)
long		which;
long		when;
/*
 * Read and output the Nth cookie.  Note: 1-origin addressing
 */
{
	long		index;
	long		temp;
	register int	bytect;
	register int	i;
	register char	*tp;

	which--;
	temp = header.nindex;
	temp = which / temp;
	i = temp;
	if (i >= header.subindex) {
	    fprintf(stderr,
		"Bug: Gone too far, index = %d, max = %d\n",
		i, header.subindex);
	    exit(IO_ERROR);
	}
	index = topindex[i];
	if (fseek(cookfd, index, 0) != 0) {
	    perror("cookie file");
	    exit(IO_ERROR);
	}
#ifdef	rsx
	if ((bytect = fget(&t.index, header.sindex, cookfd)) != header.sindex) {
#else
	if ((bytect = fread(&t.index, header.sindex, 1, cookfd)) != 1) {
#endif
		fprintf(stderr, "seek to sub-index %d, %ld. %06o %06o\n",
				i, index, index);
		rerror(header.sindex, bytect, "Can't read sub_index");
	}
	temp = header.nindex;
	temp = which % temp;
	i = temp;
	index = t.index[i];
	if (fseek(cookfd, index, 0) != 0) {
		perror("minor index seek");
		fprintf(stderr, "?Requesting cookie %ld, max = %ld\n",
				which, header.ncookie);
		return;
	}
	tp = &t.text[0];
#ifdef	rsx
	while (fget(tp, (sizeof t.text) - (tp - t.text), cookfd) > 0
#else
	while (fgets(tp, (sizeof t.text) - (tp - t.text), cookfd) != NULL
#endif
	 && !feof(cookfd)
	 && tp[0] != '%'
	 && tp[1] != '%') {
		tp += strlen(tp);
	}
	*tp = EOS;			/* Remove trailing %%		*/
	if (feof(cookfd)) {
		rerror(tp - t.text, tp - t.text, "Can't read cookie");
	}
	if (notLong && isLong(t.text))
		antiheinlien(which + 1, when);
	else
		output(which + 1, &t.text, when);
}

static char *anti1[] = {
	"A Heinlien Cookie you do distain,\n\
I'll say no more to soothe your brain.",
	"I'd rather drink a gallon of overage Rhine wine,\n\
Than read a quote by Robert A. Heinlien.",
	"I'd rather bite boils from an elephant's behind,\n\
Than read more quotes by Robert A. Heinlien.",
	"*I'd rather a tax audit found me wrong,",
	"*I'd rather be dribbled by old King Kong,",
	"*I'd rather find paraquat in my bong,",
	"*I'd rather Chuck Barris used me as the gong,",
	"*I'd rather do updates to Atari Pong,",
	"*I'd rather write a \"User's Guide to Pong\",",
};

static char *anti2[] = {
	"Than read another quote by Lazarus Long.",
	"Than have to read more of Lazarus Long.",
	"Than be forced to consider more Lazarus Long.",
};

isLong(text)
register char	*text;
/*
 * Return TRUE if the text contains "Heinlien" or "Lazarus Long"
 */
{
	register char	c;

	while ((c = *text++) != EOS) {
		switch (c) {

		case 'H':
				if (streq(text, "einlien"))
					return (TRUE);
				break;

		case 'L':
				if (streq(text, "azarus Long"))
					return (TRUE);
				break;
		}
	}
	return (FALSE);
}

static	char	antibuffer[257];

antiheinlien(which, when)
long		which;
long		when;
/*
 * Output a non-Heinlien cookie
 */
{
	register char	*tp;


	tp = anti1[(rand() & 32767) % (sizeof anti1) / (sizeof (char *))];
	if (*tp == '*') {
	    sprintf(antibuffer, "%s\n%s", tp + 1,
		anti2[(rand() & 32767) % (sizeof anti2) / (sizeof (char *))]);
	}
	else {
	    strcpy(antibuffer, tp);
	}
	output(which, antibuffer, when);
}

static int	nvoice;
static char	*voices[] = {
    "[:np]", "[:nb]", "[:nh]", "[:nu]", "[:nk]", "[:nf]", "[:nr]",
    NULL
};

output(which, out_text, when)
long		which;
char		*out_text;
long		when;
/*
 * Output one cookie
 */
{
	register char	*tp;

	startoutput(which, when);
	if (extrafd != NULL)
	    fprintf(extrafd, "%s\n", out_text);
	if (talkfd != NULL)
	    speak(out_text);
	printf("%s%s", out_text, (doall > 1) ? "%%\n" : "");
	linecount = 1;
	tp = out_text; 
	while ((tp = strchr(tp, '\n')) != NULL) {
	    linecount++;
	    tp++;
	}
	endoutput();
}

speak(out_text)
char		*out_text;
/*
 * Drive DECtalk
 */
{
	register char	*tp;
	register int	c;

	if (voices[nvoice] == NULL)
	    nvoice = 0;
	fprintf(talkfd, "%s ", voices[nvoice]);
	nvoice++;
	for (tp = out_text; (c = *tp++) != EOS;) {
	    switch (c) {
	    case ',':
		putc(',', talkfd);
		if (!isspace(*tp))
		    putc(' ', talkfd);
		break;

	    case '[':				/* [ => (		*/
		putc('(', talkfd);
		break;

	    case ']':
		putc(')', talkfd);		/* ] => )		*/
		break;

	    case '(':				/* (c.a. => ( c.a.	*/
		if (isalpha(*tp) && tp[1] == '.') {
		    putc(c, talkfd);
		    c = ' ';
		}
		putc(c, talkfd);
		break;

	    case ')':				/* (44-22 BC)? =>	*/
		if (tp > &out_text[1]		/* (44-22 BC)		*/
		 && !isalpha(tp[-2])
		 && *tp == '?') {
		    putc(c, talkfd);
		    c = ' ';
		    tp++;
		}
		putc(c, talkfd);
		break;

	    case '.':
		if (tp > &out_text[3]		/* H. L. Menken =>	*/
		 && isupper(tp[-2])		/* H L Menken		*/
		 && isspace(tp[-3])		/* so DECtalk doesn't	*/
		 && isspace(*tp))		/* pause.		*/
		    continue;
		if (tp > &out_text[2]		/* ...foo => ... foo	*/
		 && tp[-2] == '.'
		 && isalpha(*tp)) {
		    putc(c, talkfd);
		    c = ' ';
		}
		putc(c, talkfd);
		break;

	    case '?':
		if (tp > &out_text[2]
		 && isalpha(tp[-2])
		 && isspace(*tp))
		    putc(c, talkfd);
		break;

	    default:
		putc(c, talkfd);
		if (isdigit(c) && !isdigit(*tp)) {
		    switch (*tp) {
		    case '?':			/* 1900?-1980	*/
			if (tp[1] != '-')
			    break;
			tp++;			/* Skip ?	*/

		    case '-':			/* 1900-1910	*/
			putc(',', talkfd);
			putc(' ', talkfd);
			tp++;
			if (*tp == '?' && tp[1] == ')')
			    tp += 2;		/* 1900-?)	*/
			break;
		    }
		}
	    }
	}
	putc('\f', talkfd);
}

startoutput(which, when)
long		which;
long		when;
/*
 * Initialize output
 */
{

	register char	*timebuf;
	extern char	*ctime();

	if (video) {
	    sprintf(temp_text, "%ld", which);
	    timebuf = ctime(&when);
	    timebuf[24] = EOS;
	    erpage(1, 1);
	    if (isvt52) {
		/*
		 * VT52
		 */
		 vtout(1, 27, timebuf);
		 vtout(3, 36, temp_text);
	    }
	    else {
		/*
		 * VT100
		 */
		if ($$rsts) {
		    vtout(1, 8, "\233#3");	/* Double high		*/
		    vtout(0, 0,  timebuf);	/* Top half		*/
		    vtout(2, 8, "\233#4");	/* Double high		*/
		    vtout(0, 0, timebuf);	/* Bottom half		*/
		    vtout(3, 18, "\233#6");	/* Double wide		*/
		    vtout(0, 0, temp_text);
		}
		else {
		    vtout(1, 8, "\033#3");	/* Double high		*/
		    vtout(0, 0,  timebuf);	/* Top half		*/
		    vtout(2, 8, "\033#4");	/* Double high		*/
		    vtout(0, 0, timebuf);	/* Bottom half		*/
		    vtout(3, 18, "\033#6");	/* Double wide		*/
		    vtout(0, 0, temp_text);
		}
	    }
	    vtout(5, 1, "");
	}
}

endoutput()
/*
 * Clear the rest of the screen
 */
{
	if (video) {
	    erpage(0, 0);
	    fflush(stdout);
	}
	else
	    putchar('\n');
}

vtcurse(row, col)
int		row, col;
{
	if (row == 0)
	    return;
	if (isvt52) {
	    printf(($$rsts) ? "\r\233Y%c%c" : "\r\033Y%c%c",
		row + 040 - 1, col + 040 - 1);
	}
	else {
	    printf(($$rsts) ? "\r\233[%d;%dH" : "\r\033[%d;%dH",
		row, col);
	}
}

erpage(row, col)
int		row, col;
{
	vtcurse(row, col);
	if (isvt52)
	    fputs(($$rsts) ? "\233J" : "\033J", stdout);
	else
	    fputs(($$rsts) ? "\233[J" : "\033[J", stdout);
}

erline(row, col)
int		row, col;
{
	vtcurse(row, col);
	if (isvt52)
	    fputs(($$rsts) ? "\233K" : "\033K", stdout);
	else
	    fputs(($$rsts) ? "\233[K" : "\033[K", stdout);
}

vtout(row, col, text)
int		row, col;
char		*text;
{
	vtcurse(row, col);
	fputs(text, stdout);
}

