/* -*- Mode: C; tab-width: 4 -*- */
/* bubble --- simple exploding bubbles */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)bubble.c	5.00 2000/11/01 xlockmore";

#endif

/*-
 * Copyright (c) 1998 by Charles Vidal <cvidal@ivsweb.com>
 *         http://www.chez.com/vidalc
 *         and David Bagley
 *
 * 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-Jan-1998: Written.
 */

#ifdef STANDALONE
#define MODE_bubble
#define PROGCLASS "Bubble"
#define HACK_INIT init_bubble
#define HACK_DRAW draw_bubble
#define bubble_opts xlockmore_opts
#define DEFAULTS "*delay: 100000 \n" \
 "*count: 25 \n" \
 "*size: 100 \n" \
 "*ncolors: 200 \n" \
 "*fullrandom: True \n"
#define UNIFORM_COLORS
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"		/* in xlockmore distribution */
#endif /* STANDALONE */

#ifdef MODE_bubble

#define DEF_BOIL  "False"
static Bool boil;

static XrmOptionDescRec opts[] =
{
	{(char *) "-boil", (char *) ".bubble.boil", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+boil", (char *) ".bubble.boil", XrmoptionNoArg, (caddr_t) "off"}
};
static argtype vars[] =
{
	{(void *) & boil, (char *) "boil", (char *) "Boil", (char *) DEF_BOIL, t_Bool}
};
static OptionStruct desc[] =
{
	{(char *) "-/+boil", (char *) "turn on/off boil"}
};

ModeSpecOpt bubble_opts =
{sizeof opts / sizeof opts[0], opts, sizeof vars / sizeof vars[0], vars, desc};

#ifdef USE_MODULES
ModStruct   bubble_description =
{"bubble", "init_bubble", "draw_bubble", "release_bubble",
 "refresh_bubble", "init_bubble", (char *) NULL, &bubble_opts,
 100000, 25, 1, 100, 64, 0.3, "",
 "Shows popping bubbles", 0, NULL};

#endif

typedef struct {
	int         x, y, life;
} bubbletype;

#define MINSIZE 20
#define MINBUBBLES 1

typedef struct {
	int         direction;
	int         color;
	int         width, height;
	int         nbubbles;
	Bool        boil;
	bubbletype *bubble;
	int         d;
	Pixmap      dbuf;
	GC          dbuf_gc;
} bubblestruct;

static bubblestruct *bubbles = (bubblestruct *) NULL;

static void
updateBubble(ModeInfo * mi, int i)
{
	bubblestruct *bp = &bubbles[MI_SCREEN(mi)];
	int         x, diameter, x4, y4, diameter4;

	if (bp->bubble[i].life + 1 > bp->d - NRAND(16) ||
	    bp->bubble[i].y < 0) {
		bp->bubble[i].life = 0;
		return;
	}
	++bp->bubble[i].life;
	diameter = bp->bubble[i].life;
	x = bp->bubble[i].x;
	if (bp->boil) {
		bp->bubble[i].y -= diameter / 2;
		x += (int) (cos((double) (diameter +
		       bp->bubble[i].x) / (M_PI / 5.0)) * (double) diameter);
	}
	/* SunOS 4.1.X xnews server may crash without this */
	if (diameter < 4) {
		XFillRectangle(MI_DISPLAY(mi), bp->dbuf, bp->dbuf_gc,
			    x - diameter / 2, bp->bubble[i].y - diameter / 2,
			       diameter, diameter);
	} else {
		XDrawArc(MI_DISPLAY(mi), bp->dbuf, bp->dbuf_gc,
			 x - diameter / 2, bp->bubble[i].y - diameter / 2,
			 diameter, diameter, 0, 23040);
	}
	diameter4 = diameter / 4;
	if (diameter4 > 0) {
		x4 = x - diameter4 / 2 +
			((bp->direction / 2) ? (-1) : 1) * diameter / 6;
		y4 = bp->bubble[i].y - diameter4 / 2 +
			((bp->direction % 2) ? 1 : (-1)) * diameter / 6;
		/* SunOS 4.1.X xnews server may crash without this */
		if (diameter4 < 4) {
			XFillRectangle(MI_DISPLAY(mi), bp->dbuf, bp->dbuf_gc,
				       x4, y4, diameter4, diameter4);
		} else {
			XFillArc(MI_DISPLAY(mi), bp->dbuf, bp->dbuf_gc,
				 x4, y4, diameter4, diameter4, 0, 23040);
		}
	}
}

static void
changeBubble(ModeInfo * mi)
{
	bubblestruct *bp = &bubbles[MI_SCREEN(mi)];
	int         i;

	for (i = 0; i < bp->nbubbles; i++) {
		if (bp->bubble[i].life != 0)
			updateBubble(mi, i);
	}
	i = NRAND(bp->nbubbles);
	if (bp->bubble[i].life == 0) {
		bp->bubble[i].x = NRAND(bp->width);
		if (bp->boil)
			bp->bubble[i].y = bp->height -
				((bp->height >= 16) ? NRAND(bp->height / 16) : 0);
		else
			bp->bubble[i].y = NRAND(bp->height);
		updateBubble(mi, i);
	}
}

static void
free_bubble(Display *display, bubblestruct *bp)
{
	if (bp->dbuf != None) {
		XFreePixmap(display, bp->dbuf);
		bp->dbuf = None;
	}
	if (bp->dbuf_gc != None) {
		XFreeGC(display, bp->dbuf_gc);
		bp->dbuf_gc = None;
	}
	if (bp->bubble != NULL) {
		free(bp->bubble);
		bp->bubble = (bubbletype *) NULL;
	}
}

void
init_bubble(ModeInfo * mi)
{
	bubblestruct *bp;
	int         size = MI_SIZE(mi);
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	XGCValues   gcv;

	if (bubbles == NULL) {
		if ((bubbles = (bubblestruct *) calloc(MI_NUM_SCREENS(mi),
					     sizeof (bubblestruct))) == NULL)
			return;
	}
	bp = &bubbles[MI_SCREEN(mi)];

	bp->width = MI_WIDTH(mi);
	bp->height = MI_HEIGHT(mi);
	bp->direction = NRAND(4);
	if (MI_IS_FULLRANDOM(mi))
		bp->boil = (Bool) (LRAND() & 1);
	else
		bp->boil = boil;

	if (size < -MINSIZE)
		bp->d = NRAND(MIN(-size, MAX(MINSIZE,
		   MIN(bp->width, bp->height) / 2)) - MINSIZE + 1) + MINSIZE;
	else if (size < MINSIZE) {
		if (!size)
			bp->d = MAX(MINSIZE, MIN(bp->width, bp->height) / 2);
		else
			bp->d = MINSIZE;
	} else
		bp->d = MIN(size, MAX(MINSIZE,
				      MIN(bp->width, bp->height) / 2));
	bp->nbubbles = MI_COUNT(mi);
	if (bp->nbubbles < -MINBUBBLES) {
		bp->nbubbles = NRAND(-bp->nbubbles - MINBUBBLES + 1) + MINBUBBLES;
	} else if (bp->nbubbles < MINBUBBLES)
		bp->nbubbles = MINBUBBLES;
	if (bp->bubble != NULL)
		free(bp->bubble);
	if ((bp->bubble = (bubbletype *) calloc(bp->nbubbles,
			sizeof (bubbletype))) == NULL) {
		free_bubble(display, bp);
		return;
	}
	if (MI_NPIXELS(mi) > 2)
		bp->color = NRAND(MI_NPIXELS(mi));

	if (bp->dbuf != None)
		XFreePixmap(display, bp->dbuf);
	if ((bp->dbuf = XCreatePixmap(display, window, bp->width, bp->height,
			1)) == None) {
		free_bubble(display, bp);
		return;
	}
	/* Do not want any exposure events from XCopyPlane */
	XSetGraphicsExposures(display, MI_GC(mi), False);

	gcv.foreground = 1;
	gcv.background = 0;
	gcv.function = GXcopy;
	gcv.graphics_exposures = False;
	gcv.line_width = 2;
	if (bp->dbuf_gc)
		XFreeGC(display, bp->dbuf_gc);
	if ((bp->dbuf_gc = XCreateGC(display, bp->dbuf,
	  		GCForeground | GCBackground | GCLineWidth | GCFunction,
			&gcv)) == None) {
		free_bubble(display, bp);
		return;
	}
	MI_CLEARWINDOW(mi);
}

void
draw_bubble(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	GC          gc = MI_GC(mi);
	bubblestruct *bp;

	if (bubbles == NULL)
		return;
	bp = &bubbles[MI_SCREEN(mi)];
	if (bp->bubble == NULL)
		return;

	MI_IS_DRAWN(mi) = True;
	if (MI_NPIXELS(mi) <= 2)
		XSetForeground(display, gc, MI_WHITE_PIXEL(mi));
	else {
		bp->color = (bp->color + 1) % MI_NPIXELS(mi);
		XSetForeground(display, gc, MI_PIXEL(mi, bp->color));
	}
	if (bp->dbuf) {
		XSetForeground(display, bp->dbuf_gc, 0);
		XFillRectangle(display, bp->dbuf, bp->dbuf_gc,
			       0, 0, bp->width, bp->height);
		XSetForeground(display, bp->dbuf_gc, 1);
		changeBubble(mi);
		XCopyPlane(display, bp->dbuf, window, gc,
			   0, 0, bp->width, bp->height, 0, 0, 1);
	}
}

void
release_bubble(ModeInfo * mi)
{
	if (bubbles != NULL) {
		int         screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
			free_bubble(MI_DISPLAY(mi), &bubbles[screen]);
		free(bubbles);
		bubbles = (bubblestruct *) NULL;
	}
}

void
refresh_bubble(ModeInfo * mi)
{
	/* Do nothing, it will refresh by itself */
}

#endif /* MODE_bubble */
