/* -*- Mode: C; tab-width: 4 -*- */
/* lissie --- the Lissajous worm */

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

/*-
 * lissie.c - The Lissajous worm for xlock, the X Window System
 *               lockscreen.
 *
 * Copyright (c) 1996 by Alexander Jolk <ub9x@rz.uni-karlsruhe.de>
 *
 * 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:
 * 01-Nov-2000: Allocation checks
 * 10-May-1997: Compatible with xscreensaver
 * 18-Aug-1996: added refresh-hook.
 * 01-May-1996: written.
 */

#ifdef STANDALONE
# define MODE_lissie
# define DEFAULTS	"*delay: 10000 \n" \
					"*count: 1 \n" \
					"*cycles: 20000 \n" \
					"*size: -200 \n" \
					"*ncolors: 200 \n" \
					"*fpsSolid: true \n" \

# define SMOOTH_COLORS
# define release_lissie 0
# define reshape_lissie 0
# define lissie_handle_event 0
# include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
# include "xlock.h"		/* in xlockmore distribution */
#endif /* STANDALONE */

#ifdef MODE_lissie

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

#ifdef USE_MODULES
ModStruct   lissie_description =
{"lissie", "init_lissie", "draw_lissie", (char *) NULL,
 "refresh_lissie", "init_lissie", "free_lissie", &lissie_opts,
 10000, 1, 2000, -200, 64, 0.6, "",
 "Shows lissajous worms", 0, NULL};

#endif

#define MINSIZE 1

#define Lissie(n)\
	if (lissie->loc[(n)].x > 0 && lissie->loc[(n)].y > 0 &&\
		lissie->loc[(n)].x <= lp->width && lissie->loc[(n)].y <= lp->height) {\
		if (lissie->ri < 2)\
			XDrawPoint(display, MI_WINDOW(mi),\
				gc, lissie->loc[(n)].x, lissie->loc[(n)].y);\
		else\
			XDrawArc(display, MI_WINDOW(mi), gc,\
				lissie->loc[(n)].x - lissie->ri / 2,\
				lissie->loc[(n)].y - lissie->ri / 2,\
				lissie->ri, lissie->ri, 0, 23040);\
	}

#define FLOATRAND(min,max)	((min)+(LRAND()/MAXRAND)*((max)-(min)))
#define INTRAND(min,max)     ((min)+NRAND((max)-(min)+1))

#define MINDT  0.01
#define MAXDT  0.15

#define MAXLISSIELEN  100
#define MINLISSIELEN  10
#define MINLISSIES 1

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

typedef struct {
	double      tx, ty, dtx, dty;
	int         xi, yi, ri, rx, ry, len, pos;
	int         redrawing, redrawpos;
	XPoint      loc[MAXLISSIELEN];
	unsigned long color;
} lissiestruct;

typedef struct {
	Bool        painted;
	int         width, height;
	int         nlissies;
	lissiestruct *lissie;
	int         loopcount;
} lissstruct;

static lissstruct *lisses = (lissstruct *) NULL;


static void
drawlissie(ModeInfo * mi, lissiestruct * lissie)
{
	Display    *display = MI_DISPLAY(mi);
	GC          gc = MI_GC(mi);
	lissstruct *lp = &lisses[MI_SCREEN(mi)];
	int         p = (++lissie->pos) % MAXLISSIELEN;
	int         oldp = (lissie->pos - lissie->len + MAXLISSIELEN) % MAXLISSIELEN;

	/* Let time go by ... */
	lissie->tx += lissie->dtx;
	lissie->ty += lissie->dty;
	if (lissie->tx > 2 * M_PI)
		lissie->tx -= 2 * M_PI;
	if (lissie->ty > 2 * M_PI)
		lissie->ty -= 2 * M_PI;

	/* vary both (x/y) speeds by max. 1% */
	lissie->dtx *= FLOATRAND(0.99, 1.01);
	lissie->dty *= FLOATRAND(0.99, 1.01);
	if (lissie->dtx < MINDT)
		lissie->dtx = MINDT;
	else if (lissie->dtx > MAXDT)
		lissie->dtx = MAXDT;
	if (lissie->dty < MINDT)
		lissie->dty = MINDT;
	else if (lissie->dty > MAXDT)
		lissie->dty = MAXDT;

	lissie->loc[p].x = lissie->xi + (int) (sin(lissie->tx) * lissie->rx);
	lissie->loc[p].y = lissie->yi + (int) (sin(lissie->ty) * lissie->ry);

	/* Mask */
	XSetForeground(display, gc, MI_BLACK_PIXEL(mi));
	Lissie(oldp);

	/* Redraw */
	if (MI_NPIXELS(mi) > 2) {
		XSetForeground(display, gc, MI_PIXEL(mi, lissie->color));
		if (++lissie->color >= (unsigned) MI_NPIXELS(mi))
			lissie->color = 0;
	} else
		XSetForeground(display, gc, MI_WHITE_PIXEL(mi));
	Lissie(p);
	if (lissie->redrawing) {
		int         i;

		lissie->redrawpos++;
		/* This compensates for the changed p
		   since the last callback. */

		for (i = 0; i < REDRAWSTEP; i++) {
			Lissie((p - lissie->redrawpos + MAXLISSIELEN) % MAXLISSIELEN);
			if (++(lissie->redrawpos) >= lissie->len) {
				lissie->redrawing = 0;
				break;
			}
		}
	}
}

static void
initlissie(ModeInfo * mi, lissiestruct * lissie)
{
	lissstruct *lp = &lisses[MI_SCREEN(mi)];
	int         size = MI_SIZE(mi);
	int         i;

	if (MI_NPIXELS(mi) > 2)
		lissie->color = NRAND(MI_NPIXELS(mi));
	else
		lissie->color = MI_WHITE_PIXEL(mi);
	/* Initialize parameters */
	if (size < -MINSIZE)
		lissie->ri = NRAND(MIN(-size, MAX(MINSIZE,
		   MIN(lp->width, lp->height) / 4)) - MINSIZE + 1) + MINSIZE;
	else if (size < MINSIZE) {
		if (!size)
			lissie->ri = MAX(MINSIZE, MIN(lp->width, lp->height) / 4);
		else
			lissie->ri = MINSIZE;
	} else
		lissie->ri = MIN(size, MAX(MINSIZE, MIN(lp->width, lp->height) / 4));
	lissie->xi = INTRAND(lp->width / 4 + lissie->ri,
			     lp->width * 3 / 4 - lissie->ri);
	lissie->yi = INTRAND(lp->height / 4 + lissie->ri,
			     lp->height * 3 / 4 - lissie->ri);
	lissie->rx = INTRAND(lp->width / 4,
		   MIN(lp->width - lissie->xi, lissie->xi)) - 2 * lissie->ri;
	lissie->ry = INTRAND(lp->height / 4,
		  MIN(lp->height - lissie->yi, lissie->yi)) - 2 * lissie->ri;
	lissie->len = INTRAND(MINLISSIELEN, MAXLISSIELEN - 1);
	lissie->pos = 0;

	lissie->redrawing = 0;

	lissie->tx = FLOATRAND(0, 2 * M_PI);
	lissie->ty = FLOATRAND(0, 2 * M_PI);
	lissie->dtx = FLOATRAND(MINDT, MAXDT);
	lissie->dty = FLOATRAND(MINDT, MAXDT);

	for (i = 0; i < MAXLISSIELEN; i++)
		lissie->loc[i].x = lissie->loc[i].y = 0;
}

ENTRYPOINT void
init_lissie (ModeInfo * mi)
{
	lissstruct *lp;
	unsigned char ball;

	MI_INIT (mi, lisses);
	lp = &lisses[MI_SCREEN(mi)];

	lp->width = MI_WIDTH(mi);

#ifdef HAVE_JWXYZ
    jwxyz_XSetAntiAliasing (MI_DISPLAY(mi), MI_GC(mi),  False);
#endif

	lp->height = MI_HEIGHT(mi);

	lp->nlissies = MI_COUNT(mi);
	if (lp->nlissies < -MINLISSIES) {
		if (lp->lissie) {
			(void) free((void *) lp->lissie);
			lp->lissie = (lissiestruct *) NULL;
		}
		lp->nlissies = NRAND(-lp->nlissies - MINLISSIES + 1) + MINLISSIES;
	} else if (lp->nlissies < MINLISSIES)
		lp->nlissies = MINLISSIES;

	lp->loopcount = 0;

	if (lp->lissie == NULL)
		if ((lp->lissie = (lissiestruct *) calloc(lp->nlissies,
				sizeof (lissiestruct))) == NULL)
			return;

	MI_CLEARWINDOW(mi);
	lp->painted = False;

	for (ball = 0; ball < (unsigned char) lp->nlissies; ball++)
		initlissie(mi, &lp->lissie[ball]);

}

ENTRYPOINT void
draw_lissie (ModeInfo * mi)
{
	register unsigned char ball;
	lissstruct *lp;

	if (lisses == NULL)
		return;
	lp = &lisses[MI_SCREEN(mi)];
	if (lp->lissie == NULL)
		return;

	MI_IS_DRAWN(mi) = True;

	if (++lp->loopcount > MI_CYCLES(mi)) {
		init_lissie(mi);
	} else {
		lp->painted = True;
		for (ball = 0; ball < (unsigned char) lp->nlissies; ball++)
			drawlissie(mi, &lp->lissie[ball]);
	}
}

ENTRYPOINT void
free_lissie (ModeInfo * mi)
{
	lissstruct *lp;

	if (lisses == NULL)
		return;
	lp = &lisses[MI_SCREEN(mi)];

	if (lp->lissie != NULL) {
		(void) free((void *) lp->lissie);
		/* lp->lissie = NULL; */
	}
}

#ifndef STANDALONE
ENTRYPOINT void
refresh_lissie(ModeInfo * mi)
{
	int         i;
	lissstruct *lp;

	if (lisses == NULL)
		return;
	lp = &lisses[MI_SCREEN(mi)];
	if (lp->lissie == NULL)
		return;

	if (lp->painted) {
		MI_CLEARWINDOW(mi);
		for (i = 0; i < lp->nlissies; i++) {
			lp->lissie[i].redrawing = 1;
			lp->lissie[i].redrawpos = 0;
		}
	}
}
#endif

XSCREENSAVER_MODULE ("Lissie", lissie)

#endif /* MODE_lissie */
