/* -*- Mode: C; tab-width: 4 -*- */
/* worm --- draw wiggly worms */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)worm.c	4.07 97/11/24 xlockmore";

#endif

/*-
 * Copyright (c) 1991 by Patrick J. Naughton.
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted,
 * provided that the above copyright notice appear in all copies and that
 * both that copyright notice and this permission notice appear in
 * supporting documentation.
 *
 * This file is provided AS IS with no warranties of any kind.  The author
 * shall have no liability with respect to the infringement of copyrights,
 * trade secrets or any patents by this file or any part thereof.  In no
 * event will the author be liable for any lost revenue or profits or
 * other special, indirect and consequential damages.
 *
 * Revision History:
 * 10-May-97: Compatible with xscreensaver
 * 03-Sep-96: fixed bug in allocation of space for worms, added 3d support
 *            Henrik Theiling <theiling@coli.uni-sb.de>
 * 27-Sep-95: put back malloc
 * 23-Sep-93: got rid of "rint". (David Bagley)
 * 27-Sep-91: got rid of all malloc calls since there were no calls to free().
 * 25-Sep-91: Integrated into X11R5 contrib xlock.
 *
 * Adapted from a concept in the Dec 87 issue of Scientific American p. 142.
 *
 * SunView version: Brad Taylor <brad@sun.com>
 * X11 version: Dave Lemke <lemke@ncd.com>
 * xlock version: Boris Putanec <bp@cs.brown.edu>
 */

#ifdef STANDALONE
#define PROGCLASS "Worm"
#define HACK_INIT init_worm
#define HACK_DRAW draw_worm
#define worm_opts xlockmore_opts
#define DEFAULTS "*delay: 17000 \n" \
 "*count: -20 \n" \
 "*cycles: 10 \n" \
 "*size: -3 \n" \
 "*ncolors: 200 \n" \
 "*use3d: False \n" \
 "*delta3d: 1.5 \n" \
 "*right3d: red \n" \
 "*left3d: blue \n" \
 "*both3d: magenta \n" \
 "*none3d: black \n"
#define SMOOTH_COLORS
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"		/* in xlockmore distribution */
#endif /* STANDALONE */

#ifdef MODE_worm

ModeSpecOpt worm_opts =
{0, NULL, 0, NULL, NULL};

#ifdef USE_MODULES
ModStruct   worm_description =
{"worm", "init_worm", "draw_worm", "release_worm",
 "refresh_worm", "init_worm", NULL, &worm_opts,
 17000, -20, 10, -3, 64, 1.0, "",
 "Shows wiggly worms", 0, NULL};

#endif

#define MINSIZE 1

#define SEGMENTS  36
#define MINWORMS 1

#define MAXZ      750
#define MINZ      100
#define SCREENZ   200
#define GETZDIFF(z) (MI_DELTA3D(mi)*20.0*(1.0-(SCREENZ)/((float)(z)+MINZ)))
#define IRINT(x) ((int)(((x)>0.0)?(x)+0.5:(x)-0.5))

/* How many segments to draw per cycle when redrawing */
#define REDRAWSTEP 3

typedef struct {
	XPoint     *circ;
	int        *diffcirc;
	int         dir, dir2;
	int         tail;
	int         x, y, z;
	int         redrawing, redrawpos;
} wormstuff;

typedef struct {
	int         xsize, ysize, zsize;
	int         wormlength;
	int         nc;
	int         nw;
	int         circsize;
	wormstuff  *worm;
	XRectangle *rects;
	int         maxsize;
	int        *size;
	unsigned int chromo;
} wormstruct;

static float sintab[SEGMENTS];
static float costab[SEGMENTS];
static int  init_table = 0;

static wormstruct *worms = NULL;

static void
worm_doit(ModeInfo * mi, int which, unsigned long color)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	GC          gc = MI_GC(mi);
	wormstruct *wp = &worms[MI_SCREEN(mi)];
	wormstuff  *ws = &wp->worm[which];
	int         x, y, z;
	int         diff;

	ws->tail++;
	if (ws->tail == wp->wormlength)
		ws->tail = 0;

	x = ws->circ[ws->tail].x;
	y = ws->circ[ws->tail].y;

	if (MI_IS_USE3D(mi)) {
		diff = ws->diffcirc[ws->tail];
		if (MI_IS_INSTALL(mi)) {
			XSetForeground(display, gc, MI_NONE_COLOR(mi));
		} else {
			XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
		}
		XFillRectangle(display, window, gc, x - diff, y,
			       wp->circsize, wp->circsize);
		XFillRectangle(display, window, gc, x + diff, y,
			       wp->circsize, wp->circsize);
	} else {
		XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
		XFillRectangle(display, window, gc, x, y, wp->circsize, wp->circsize);
	}

	if (LRAND() & 1)
		ws->dir = (ws->dir + 1) % SEGMENTS;
	else
		ws->dir = (ws->dir + SEGMENTS - 1) % SEGMENTS;

	x = (ws->x + IRINT((float) wp->circsize * costab[ws->dir]) +
	     wp->xsize) % wp->xsize;
	y = (ws->y + IRINT((float) wp->circsize * sintab[ws->dir]) +
	     wp->ysize) % wp->ysize;

	ws->circ[ws->tail].x = x;
	ws->circ[ws->tail].y = y;
	ws->x = x;
	ws->y = y;

	if (MI_IS_USE3D(mi)) {
		if (LRAND() & 1)
			ws->dir2 = (ws->dir2 + 1) % SEGMENTS;
		else
			ws->dir2 = (ws->dir2 + SEGMENTS - 1) % SEGMENTS;
		/* for the z-axis the wrap-around looks bad,
		   * so worms should just turn around.
		 */
		z = (int) (ws->z + wp->circsize * sintab[ws->dir2]);
		if (z < 0 || z >= wp->zsize)
			z = (int) (ws->z - wp->circsize * sintab[ws->dir2]);

		diff = (int) (GETZDIFF(z) + 0.5);	/* ROUND */
		ws->diffcirc[ws->tail] = diff;

		ws->z = z;

		/* right eye */
		color = 0;
		wp->rects[color * wp->maxsize + wp->size[color]].x = x + diff;
		wp->rects[color * wp->maxsize + wp->size[color]].y = y;
		wp->size[color]++;

		/* left eye */
		color = 1;
		wp->rects[color * wp->maxsize + wp->size[color]].x = x - diff;
		wp->rects[color * wp->maxsize + wp->size[color]].y = y;
		wp->size[color]++;

#if 0
		if (ws->redrawing) {	/* Too hard for now */
			int         j;

			for (j = 0; j < REDRAWSTEP; j++) {
				int         k = (ws->tail - ws->redrawpos + wp->wormlength)
				% wp->wormlength;

				color = 0;
				wp->rects[color * wp->maxsize + wp->size[color]].x =
					ws->circ[k].x + ws->diffcirc[k];
				wp->rects[color * wp->maxsize + wp->size[color]].y =
					ws->circ[k].y;
				wp->size[color]++;

				color = 1;
				wp->rects[color * wp->maxsize + wp->size[color]].x =
					ws->circ[k].x - ws->diffcirc[k];
				wp->rects[color * wp->maxsize + wp->size[color]].y =
					ws->circ[k].y;
				wp->size[color]++;

				if (++(ws->redrawpos) >= wp->wormlength) {
					ws->redrawing = 0;
					break;
				}
			}
		}
#endif

	} else {

		wp->rects[color * wp->maxsize + wp->size[color]].x = x;
		wp->rects[color * wp->maxsize + wp->size[color]].y = y;
		wp->size[color]++;
		if (ws->redrawing) {
			int         j;

			ws->redrawpos++;
			/* Compensates for the changed ws->tail
			   since the last callback. */

			for (j = 0; j < REDRAWSTEP; j++) {
				int         k = (ws->tail - ws->redrawpos + wp->wormlength)
				% wp->wormlength;

				wp->rects[color * wp->maxsize + wp->size[color]].x = ws->circ[k].x;
				wp->rects[color * wp->maxsize + wp->size[color]].y = ws->circ[k].y;
				wp->size[color]++;

				if (++(ws->redrawpos) >= wp->wormlength) {
					ws->redrawing = 0;
					break;
				}
			}
		}
	}
}

static void
free_worms(wormstruct * wp)
{
	int         wn;

	if (wp->worm) {
		for (wn = 0; wn < wp->nw; wn++) {
			if (wp->worm[wn].circ)
				(void) free((void *) wp->worm[wn].circ);
			if (wp->worm[wn].diffcirc)
				(void) free((void *) wp->worm[wn].diffcirc);
		}
		(void) free((void *) wp->worm);
		wp->worm = NULL;
	}
	if (wp->rects) {
		(void) free((void *) wp->rects);
		wp->rects = NULL;
	}
	if (wp->size) {
		(void) free((void *) wp->size);
		wp->size = NULL;
	}
}

void
init_worm(ModeInfo * mi)
{
	wormstruct *wp;
	int         size = MI_SIZE(mi);
	int         i, j;

	if (worms == NULL) {
		if ((worms = (wormstruct *) calloc(MI_NUM_SCREENS(mi),
					       sizeof (wormstruct))) == NULL)
			return;
	}
	wp = &worms[MI_SCREEN(mi)];
	if (MI_NPIXELS(mi) <= 2 || MI_IS_USE3D(mi))
		wp->nc = 2;
	else
		wp->nc = MI_NPIXELS(mi);

	free_worms(wp);
	wp->nw = MI_COUNT(mi);
	if (wp->nw < -MINWORMS)
		wp->nw = NRAND(-wp->nw - MINWORMS + 1) + MINWORMS;
	else if (wp->nw < MINWORMS)
		wp->nw = MINWORMS;
	if (!wp->worm)
		wp->worm = (wormstuff *) malloc(wp->nw * sizeof (wormstuff));

	if (!wp->size)
		wp->size = (int *) malloc(MI_NPIXELS(mi) * sizeof (int));

	wp->maxsize = (REDRAWSTEP + 1) * wp->nw;	/*  / wp->nc + 1; */
	if (!wp->rects)
		wp->rects =
			(XRectangle *) malloc(wp->maxsize * MI_NPIXELS(mi) * sizeof (XRectangle));


	if (!init_table) {
		init_table = 1;
		for (i = 0; i < SEGMENTS; i++) {
			sintab[i] = SINF(i * 2.0 * M_PI / SEGMENTS);
			costab[i] = COSF(i * 2.0 * M_PI / SEGMENTS);
		}
	}
	wp->xsize = MI_WIDTH(mi);
	wp->ysize = MI_HEIGHT(mi);
	wp->zsize = MAXZ - MINZ + 1;
	if (MI_NPIXELS(mi) > 2)
		wp->chromo = NRAND(MI_NPIXELS(mi));

	if (size < -MINSIZE)
		wp->circsize = NRAND(-size - MINSIZE + 1) + MINSIZE;
	else if (size < MINSIZE)
		wp->circsize = MINSIZE;
	else
		wp->circsize = size;

	for (i = 0; i < wp->nc; i++) {
		for (j = 0; j < wp->maxsize; j++) {
			wp->rects[i * wp->maxsize + j].width = wp->circsize;
			wp->rects[i * wp->maxsize + j].height = wp->circsize;

		}
	}
	(void) memset((char *) wp->size, 0, wp->nc * sizeof (int));

	wp->wormlength = (int) sqrt(wp->xsize + wp->ysize) *
		MI_CYCLES(mi) / 8;	/* Fudge this to something reasonable */
	for (i = 0; i < wp->nw; i++) {
		wp->worm[i].circ = (XPoint *) malloc(wp->wormlength * sizeof (XPoint));
		wp->worm[i].diffcirc = (int *) malloc(wp->wormlength * sizeof (int));

		for (j = 0; j < wp->wormlength; j++) {
			wp->worm[i].circ[j].x = wp->xsize / 2;
			wp->worm[i].circ[j].y = wp->ysize / 2;
			if (MI_IS_USE3D(mi))
				wp->worm[i].diffcirc[j] = 0;
		}
		wp->worm[i].dir = NRAND(SEGMENTS);
		wp->worm[i].dir2 = NRAND(SEGMENTS);
		wp->worm[i].tail = 0;
		wp->worm[i].x = wp->xsize / 2;
		wp->worm[i].y = wp->ysize / 2;
		wp->worm[i].z = SCREENZ - MINZ;
		wp->worm[i].redrawing = 0;
	}

	if (MI_IS_INSTALL(mi) && MI_IS_USE3D(mi)) {
		MI_CLEARWINDOWCOLOR(mi, MI_NONE_COLOR(mi));
	} else {
		MI_CLEARWINDOW(mi);
	}
}

void
draw_worm(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	GC          gc = MI_GC(mi);
	wormstruct *wp = &worms[MI_SCREEN(mi)];
	unsigned long wcolor;
	int         i;

	(void) memset((char *) wp->size, 0, wp->nc * sizeof (int));

	MI_IS_DRAWN(mi) = True;

	for (i = 0; i < wp->nw; i++) {
		if (MI_NPIXELS(mi) > 2) {
			wcolor = (i + wp->chromo) % wp->nc;

			worm_doit(mi, i, wcolor);
		} else
			worm_doit(mi, i, (unsigned long) 0);
	}

	if (MI_IS_USE3D(mi)) {
		if (MI_IS_INSTALL(mi))
			XSetFunction(display, gc, GXor);
		XSetForeground(display, gc, MI_RIGHT_COLOR(mi));
		XFillRectangles(display, window, gc, &(wp->rects[0]), wp->size[0]);

		XSetForeground(display, gc, MI_LEFT_COLOR(mi));
		XFillRectangles(display, window, gc, &(wp->rects[wp->maxsize]), wp->size[1]);
		if (MI_IS_INSTALL(mi))
			XSetFunction(display, gc, GXcopy);
	} else if (MI_NPIXELS(mi) > 2) {
		for (i = 0; i < wp->nc; i++) {
			XSetForeground(display, gc, MI_PIXEL(mi, i));
			XFillRectangles(display, window, gc, &(wp->rects[i * wp->maxsize]), wp->size[i]);
		}
	} else {
		XSetForeground(display, gc, MI_WHITE_PIXEL(mi));
		XFillRectangles(display, window, gc,
				&(wp->rects[0]), wp->size[0]);
	}

	if (++wp->chromo == (unsigned long) wp->nc)
		wp->chromo = 0;
}

void
release_worm(ModeInfo * mi)
{
	if (worms != NULL) {
		int         screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
			free_worms(&worms[screen]);
		(void) free((void *) worms);
		worms = NULL;
	}
}

void
refresh_worm(ModeInfo * mi)
{
	if (MI_IS_INSTALL(mi) && MI_IS_USE3D(mi)) {
		MI_CLEARWINDOWCOLOR(mi, MI_NONE_COLOR(mi));
	} else {
		MI_CLEARWINDOW(mi);
	}
	/* The 3D code does drawing&clearing by XORing.  We do not
	   want to go to too much trouble here to make it redraw
	   correctly. */
	if (!MI_IS_USE3D(mi) && worms != NULL) {
		wormstruct *wp = &worms[MI_SCREEN(mi)];
		int         i;

		for (i = 0; i < wp->nw; i++) {
			wp->worm[i].redrawing = 1;
			wp->worm[i].redrawpos = 0;
		}
	}
}

#endif /* MODE_worm */
