/* -*- Mode: C; tab-width: 4 -*- */
/* xjack -- Jack having one of those days */

#if 0
static const char sccsid[] = "@(#)xjack.c	5.00 2000/11/01 xlockmore";

#endif

/* xscreensaver, Copyright (c) 1997 Jamie Zawinski <jwz AT jwz.org>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  No representations are made about the suitability of this
 * software for any purpose.  It is provided "as is" without express or
 * implied warranty.
 *
 * Wendy, let me explain something to you.  Whenever you come in here and
 * interrupt me, you're BREAKING my CONCENTRATION.  You're DISTRACTING me!
 * And it will then take me time to get back to where I was. You understand?
 * Now, we're going to make a new rule.	When you come in here and you hear
 * me typing, or whether you DON'T hear me typing, or whatever the FUCK you
 * hear me doing; when I'm in here, it means that I am working, THAT means
 * don't come in!  Now, do you think you can handle that?
 *
 * Revision History:
 * 01-Nov-2000: Allocation checks
 * 10-Oct-1998: revision history edited.
 */

#ifdef STANDALONE
#define MODE_xjack
#define DEFAULTS "*delay: 50000 \n" \
	"*font: fixed\n" \
	"*text: \n" \
	"*fullrandom: True \n" \

# define free_xjack 0
# define reshape_xjack 0
# define xjack_handle_event 0
#define DEF_FONT "-*-courier-medium-r-*-*-*-240-*-*-m-*-*-*",
#define DEF_TEXT "All work and no play makes Jack a dull boy.  ";
#include "xlockmore.h"  /* in xscreensaver distribution */

#else /* STANDALONE */
#include "xlock.h"     /* in xlockmore distribution */
#include "iostuff.h"
#endif
#include <ctype.h>

#ifdef MODE_xjack

ENTRYPOINT ModeSpecOpt xjack_opts =
{0, (XrmOptionDescRec *) NULL, 0, (argtype *) NULL, (OptionStruct *) NULL};

#ifdef USE_MODULES
ModStruct xjack_description =
{"xjack", "init_xjack", "draw_xjack", "release_xjack",
 "init_xjack", "init_xjack", (char *) NULL, &xjack_opts,
 50000, 1, 1, 1, 64, 1.0, "",
 "Shows Jack having one of those days", 0, NULL};

#endif

#ifdef USE_MB
static int font_height(XFontStruct *f)
{
        XRectangle mbRect;

        XmbTextExtents(fontset, "Aj", 1, NULL, &mbRect);
        return mbRect.height;
}
#define font_ascent(f) (font_height(f)*4/5)
static int char_width(XFontStruct *f)
{
        XRectangle mbRect;

        XmbTextExtents(fontset, "A", 1, NULL, &mbRect);
        return mbRect.width;
}

#else
#define font_height(f) ((f==None)?15:f->ascent + f->descent)
#define font_ascent(f) ((f==None)?12:f->ascent)
#define char_width(f) ((mode_font->per_char?mode_font->per_char['n' - mode_font->min_char_or_byte2].rbearing:mode_font->min_bounds.rbearing))
#endif

extern XFontStruct *getFont(Display * display);

typedef struct {
	int   word_length;
	int   sentences;
	int   paras;
	Bool  caps;
	Bool  break_para;
	int   mode, stage, busyloop;
	int   left, right;      /* characters */
	int   x, y;             /* characters */
	int   hspace, vspace;   /* pixels */
	int   ascent;
	int   height;
	int   win_width, win_height;
	int   columns, rows;    /* characters */
	int   char_width, line_height;  /* pixels */
	const char *s;
	GC    gc;
} jackstruct;

static jackstruct *jacks = (jackstruct *) NULL;

static XFontStruct *mode_font = None;
static const char *source = "All work and no play makes Jack a dull boy.  ";
static const char *source_message;

extern char *message;

static void
free_xjack_screen(Display *display, jackstruct *jp)
{
	if (jp == NULL) {
		return;
	}
	if (jp->gc != None)
		XFreeGC(display, jp->gc);
	jp = NULL;
}

ENTRYPOINT void
release_xjack(ModeInfo * mi)
{
	if (jacks != NULL) {
		int				 screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++) {
			free_xjack_screen(MI_DISPLAY(mi), &jacks[screen]);
		}
		free(jacks);
		jacks = (jackstruct *) NULL;
	}
	if (mode_font != None) {
		XFreeFont(MI_DISPLAY(mi), mode_font);
		mode_font = None;
	}
}

ENTRYPOINT void
init_xjack(ModeInfo * mi)
{
	Display		*display = MI_DISPLAY(mi);
	jackstruct *jp;
	XGCValues gcv;

	MI_INIT(mi, jacks);
	jp = &jacks[MI_SCREEN(mi)];
	if (mode_font == None)
		if ((mode_font = getFont(display)) == None) {
			release_xjack(mi);
		}

	if (jp->gc == None) {
		gcv.font = mode_font->fid;
		gcv.graphics_exposures = False;
		gcv.foreground = MI_WHITE_PIXEL(mi);
		gcv.background = MI_BLACK_PIXEL(mi);
		if ((jp->gc = XCreateGC(display, MI_WINDOW(mi),
				GCForeground | GCBackground | GCGraphicsExposures | GCFont,
				&gcv)) == None) {
			return;
		}
		jp->ascent = font_ascent(mode_font);
		jp->height = font_height(mode_font);
	}

	jp->win_width = MI_WIDTH(mi);
	jp->win_height = MI_HEIGHT(mi);

	jp->hspace = jp->vspace = 15;  /* pixels */
	jp->char_width = char_width(mode_font);
	if (!jp->char_width)
		jp->char_width = 10;
	if (!jp->height)
		jp->height = 15;
	jp->line_height = jp->height + 1;

	jp->columns = MAX((jp->win_width - jp->hspace - jp->hspace) / jp->char_width, 2);
	jp->rows = (MI_HEIGHT(mi) - jp->vspace - jp->vspace) / jp->line_height;

	jp->left = 0xFF & (NRAND((jp->columns / 2) + 1));
	if ( jp->columns - jp->left != 10 )
		jp->right = jp->left + (0xFF & (NRAND(jp->columns - jp->left - 10) + 10));
	else
		jp->right = jp->left + 10;
	jp->x = 0;
	jp->y = 0;
	jp->sentences = 0;
	jp->paras = 0;
	jp->caps = False;
	jp->break_para = True;
	jp->mode = 0;
	jp->stage = 0;
	jp->busyloop = 0;
#ifdef STANDALONE
	source_message = source;
#else
	if (!message || !*message)
		source_message = source;
	else
		source_message = message;
#endif
	jp->s = source_message;
	MI_CLEARWINDOW(mi);
}

ENTRYPOINT void
draw_xjack(ModeInfo * mi)
{
	Display		*display = MI_DISPLAY(mi);
	Window			window = MI_WINDOW(mi);
	const char *s2;
	jackstruct *jp;

	if (jacks == NULL)
		return;
	jp = &jacks[MI_SCREEN(mi)];
	if (jp->gc == None)
		return;

	MI_IS_DRAWN(mi) = True;

	jp->word_length = 0;
	for (s2 = jp->s; *s2 && *s2 != ' '; s2++)
		jp->word_length++;

	if (jp->break_para || (*(jp->s) != ' ' &&
		 (jp->x + jp->word_length) >= jp->right)) {
		jp->x = jp->left;
		jp->y++;

		if (jp->break_para)
			jp->y++;

		if (jp->mode == 1 || jp->mode == 2) {
			/* 1 = left margin goes southwest; 2 = southeast */
			jp->left += (jp->mode == 1 ? 1 : -1);
			if (jp->left >= jp->right - 10) {
				if ((jp->right < (jp->columns - 10)) && (LRAND() & 1))
					jp->right += (0xFF & (NRAND(jp->columns - jp->right)));
				else
					jp->mode = 2;
			} else if (jp->left <= 0) {
				jp->left = 0;
				jp->mode = 1;
			}
		} else if (jp->mode == 3 || jp->mode == 4) {
			/* 3 = right margin goes southeast; 4 = southwest */
			jp->right += (jp->mode == 3 ? 1 : -1);
			if (jp->right >= jp->columns) {
				jp->right = jp->columns;
				jp->mode = 4;
			} else if (jp->right <= jp->left + 10)
				jp->mode = 3;
			}

			if (jp->y >= jp->rows) {  /* bottom of page */
				/* scroll by 1-5 lines */
				int lines = ((NRAND(5)) ? 0 : (0xFF & (NRAND(5)))) + 1;

				if (jp->break_para)
					lines++;

				/* but sometimes scroll by a whole page */
				if (!NRAND(100))
					lines += jp->rows;

				while (lines > 0) {
					XCopyArea(display, window, window, jp->gc,
								0, jp->hspace + jp->line_height,
								jp->win_width,
								jp->win_height - jp->vspace - jp->vspace - jp->line_height,
								0, jp->vspace);
					XClearArea(display, window,
								0,	jp->win_height - jp->vspace - jp->line_height,
								jp->win_width,
								jp->line_height + jp->vspace + jp->vspace,
								False);
					XClearArea(display, window, 0, 0,	jp->win_width, jp->vspace, False);
					/* See? It's OK. He saw it on the television. */
					XClearArea(display, window, 0, 0, jp->hspace, jp->win_height, False);
					XClearArea(display, window,	jp->win_width - jp->vspace, 0,
								jp->hspace, jp->win_height, False);
					jp->y--;
					lines--;
#if 0
					XFlush(display);
					if (delay)
						 (void) usleep (delay * 10);
#endif
				}
				if (jp->y < 0)
					jp->y = 0;
			}

			jp->break_para = False;
		}

		if (*(jp->s) != ' ') {
			char c = *(jp->s);
			int xshift = 0, yshift = 0;

			if (!NRAND(50)) {
				xshift = NRAND((jp->char_width / 3) + 1);  /* mis-strike */
				yshift = NRAND((jp->line_height / 6) + 1);
				if (!NRAND(3))
					yshift *= 2;
				if (LRAND() & 1)
					xshift = -xshift;
				if (LRAND() & 1)
					yshift = -yshift;
			}

			if (!NRAND(250)) {  /* introduce adjascent-key typo */
				static const char * const typo[] = {
					"asqwz", "bghnv", "cdfvx", "dcefsrx", "edrsw34",
					"fcdegrtv", "gbfhtvy", "hbgjnuy", "ijkou89", "jhikmnu",
					"kijolm,", "lkop;.,", "mjkn,", "nbhjm", "oiklp09",
					"plo;[-0", "qasw12", "redft45", "sadewxz", "tfgry56",
					"uhijy78", "vbcfg", "waeqs23", "xcdsz", "yuhgt67",
					"zasx", ".l,;/",
					"ASQWZ", "BGHNV", "CDFVX", "DCEFSRX", "EDRSW#$",
					"FCDEGRTV", "GBFHTVY", "HBGJNUY", "IJKOU*(", "JHIKMNU",
					"KIJOLM,", "LKOP:><", "MJKN<", "NBHJM", "OIKLP)(",
					"PLO:{_)", "QASW!@", "REDFT$%", "SADEWXZ", "TFGRY%^",
					"UHIJY&*", "VBCFG", "WAEQS@#", "XCDSZ", "YUHGT^&",
					"ZASX", 0 };
				int i = 0;

				while (typo[i] && typo[i][0] != c)
					i++;
				if (typo[i])
					c = typo[i][0xFF & (NRAND(strlen(typo[i]+1)))];
			}

			/* caps typo */
			if (c >= 'a' && c <= 'z' && (jp->caps || !NRAND(350))) {
				c -= ('a' - 'A');
				if (c == 'O' && (LRAND() & 1))
					c = '0';
			}

			{
				Bool overstrike;

				do {
					(void) XDrawString (display, window, jp->gc,
							(jp->x * jp->char_width) + jp->hspace + xshift,
							(jp->y * jp->line_height) + jp->vspace + jp->ascent + yshift,
							&c, 1);
					overstrike = (xshift == 0 && yshift == 0 &&
						(0 == (LRAND() & 3000)));
					if (overstrike) {
						if (LRAND() & 1)
							xshift--;
						else
							yshift--;
					}
				} while (overstrike);
			}
			if ((tolower(c) != tolower(*(jp->s)))
					? (!NRAND(10))      /* backup to correct */
					: (!NRAND(400))) {  /* fail to advance */
				jp->x--;
				jp->s--;
#if 0
				XFlush(display);
				if (delay)
					 (void) usleep (0xFFFF & (delay + (NRAND(delay * 10))));
#endif
			}
		}

		jp->x++;
		jp->s++;

		if (!NRAND(200)) {
			if ((LRAND() & 1) && jp->s != source_message)
				jp->s--;   /* duplicate character */
			else if (*(jp->s))
				jp->s++;   /* skip character */
		}

		if (*(jp->s) == 0) {
			jp->sentences++;
			jp->caps = (!NRAND(40));  /* capitalize sentence */

			if (!NRAND(10) ||         /* randomly break paragraph */
					(jp->mode == 0 && (NRAND(10) || jp->sentences > 20))) {
				jp->break_para = True;
				jp->sentences = 0;
				jp->paras++;

				if (LRAND() & 1)       /* mode=0 50% of the time */
					jp->mode = 0;
				else
			 		jp->mode = (0xFF & NRAND(5));

			if (LRAND() & 1) {         /* re-pick margins */
				if (jp->columns < 3)
					jp->columns = 3;
				jp->left = 0xFF & (NRAND(jp->columns / 3));
				jp->right = jp->columns - (0xFF & (NRAND(jp->columns / 3)));

				if (!NRAND(3))        /* sometimes be wide */
					jp->right = jp->left + ((jp->right - jp->left) / 2);
			}

			if (jp->right - jp->left <= 10) {  /* introduce sanity */
				jp->left = 0;
				jp->right = jp->columns;
			}

			if (jp->right - jp->left > 50) {   /* if wide, shrink and move */
				jp->left += (0xFF & (NRAND((jp->columns - 50) + 1)));
				jp->right = jp->left + (0xFF & (NRAND(40) + 10));
			}

			/* oh, gag. */
			if (jp->mode == 0 && jp->right - jp->left < 25 && jp->columns > 40) {
				jp->right += 20;
				if (jp->right > jp->columns)
					jp->left -= (jp->right - jp->columns);
			}
		}
		jp->s = source_message;
	}

#if 0
	XFlush(display);
	if (delay) {
		(void) usleep (delay);
		if (!NRAND(3))
			(void) usleep(0xFFFFFF & ((NRAND(delay * 5)) + 1));

		if (jp->break_para)
			(void) usleep(0xFFFFFF & ((NRAND(delay * 15)) + 1));
	}
#endif

#ifdef NFS_COMPLAINTS
	if (jp->paras > 5 && (!NRAND(1000)) && jp->y < jp->rows-5) {
		int i;
		int n = NRAND(3);

		jp->y++;
		for (i = 0; i < n; i++) {
			/* See also http://catalog.com/hopkins/unix-haters/login.html */
			const char *n1 =
					"NFS server overlook not responding, still trying...";
			const char *n2 = "NFS server overlook ok.";

			while (*n1) {
				(void) XDrawString (display, window, jp->gc,
									(jp->x * jp->char_width) + jp->hspace,
									(jp->y * jp->line_height) + jp->vspace + jp->ascent,
									n1, 1);
				jp->x++;
				if (jp->x >= jp->columns)
					jp->x = 0, jp->y++;
				n1++;
			}
#if 0
			XFlush(display);
			(void) usleep (5000000);
#endif
			while (*n2) {
				(void) XDrawString (display, window, jp->gc,
								(jp->x * jp->char_width) + jp->hspace,
								(jp->y * jp->line_height) + jp->vspace + jp->ascent,
								n2, 1);
				jp->x++;
				if (jp->x >= jp->columns)
					jp->x = 0, jp->y++;
				n2++;
			}
			jp->y++;
#if 0
			XFlush(display);
			(void) usleep (500000);
#endif
		}
	}
#endif
}

XSCREENSAVER_MODULE ("Xjack", xjack)

#endif /* MODE_xjack */
