/*
 * Xburn, modified by JAR from
 *
 * Xfire, a forest fire simulator for X windows.
 * 
 * Michael Creutz   creutz@wind.phy.bnl.gov
 * 
:!cc -O % -lm -lX11
 */

# include <X11/Xlib.h>
# include <X11/Xutil.h>
# include <X11/Xos.h>
# include <X11/Xatom.h>
# include <stdio.h>
# include <stdlib.h>
# include <time.h>
# include <math.h>

/* lattice dimensions (plus two for boundaries): */
# define NROWS 160
# define NCOLS 192
# define VOLUME (NROWS*NCOLS)

int             block = 1;	/* default to small blocks */

char            field[2][VOLUME];	/* to store the system */
int             old = 0, new = 1;
# define barren 0
# define tree 1
# define fire 2
# define burnt 3
long            translate[256];	/* for converting colors */

int             paused = 0;
int             iteration = 0;
int             iter_tot = 0;
double		p_init = 0.0;
int		ntrees_init = 0;
int		ntrees = 0;
int		nfires = 0;
int             mask;
static char    *progname;
char            stringbuffer[100];

/* for fast but crude random number generation */
# define RSIZE 127
int             births[RSIZE];
int             randomizer = 57, birthindex = 0;

/* dimensions for placing things */
#define PLAYTOP 60

/* various window stuff */
Display        *display;
int             screen;
Window          window, quitbutton, pausebutton, playground;
GC              gc, gcr, gcxor, gccolor;
XImage         *spinimage = NULL;
XFontStruct    *font = NULL;
int             font_height;
unsigned int    windowwidth, windowheight;
XSizeHints      size_hints;
int             darkcolor, lightcolor, black, white;

main(argc, argv)
	int             argc;
	char          **argv;
{
	unsigned int    width, height;
	int             i, x, y;
	XEvent          report;
	progname = argv[0];
	if (argc < 2)
		exit(1);
	p_init = atof(argv[1]);	/* bring in (required) fraction filled */
	if ((p_init < 0.0) || (p_init > 1.0))
		exit(1);
	ntrees_init = (int) ((double) (VOLUME) * p_init);
	if (argc > 2)		/* show image with bigger blocks */
		block = atoi(argv[2]);
	if (block > 4)
		block = 4;
	if (block < 1)
		block = 1;
	openwindow(argc, argv);
	/* mask used for random site selection */
	mask = 1;
	while (mask < VOLUME)
		mask = 1 | (mask << 1);
	/* set initial state random filled up to p_init */
	srand48( (long) time( (time_t *)0 ) );
	for (i = 0; i < VOLUME; i++)
		field[old][i] = field[new][i] = barren;
	ntrees = 0;
	while (ntrees < ntrees_init) {
		i = (int) (VOLUME * drand48());
		if (field[new][i] == barren) {
			field[old][i] = field[new][i] = tree;
			ntrees++;
		}
	}
	/* start fires along east boundary and its reflection west */
	for (i = 0; i < VOLUME; i += NCOLS) {
		field[old][i] = field[old][i + NCOLS - 2] = fire;
		nfires += 1;
	}
	/* set up array for fast random number generation */
	for (i = 0; i < RSIZE; i++)
		births[i] = (lrand48() >> 2) & mask;

	/* loop forever, looking for events */
	while (1) {
		if (paused | XPending(display)) {
			XNextEvent(display, &report);
			switch (report.type) {
			case Expose:
				if (report.xexpose.count != 0)
					break;	/* more in queue, wait for
						 * them */
				repaint();
				break;
			case ConfigureNotify:
				width = report.xconfigure.width;
				height = report.xconfigure.height;
				if ((width < size_hints.min_width) || (height < size_hints.min_height)) {
					fprintf(stderr, "%s: window too small to proceed.\n", progname);
					XUnloadFont(display, font->fid);
					XFreeGC(display, gc);
					XCloseDisplay(display);
					exit(1);
				}
				break;
			case ButtonPress:
				if (report.xbutton.window == quitbutton) {
					XUnloadFont(display, font->fid);
					XFreeGC(display, gc);
					XCloseDisplay(display);
					exit(1);
				} else if (report.xbutton.window == pausebutton) {
					paused = 1 - paused;
					mypause();
				} else if (report.xbutton.window == playground) {	/* start a fire */
					x = report.xbutton.x / block;
					y = report.xbutton.y / block;
					field[old][x + NCOLS * y] = fire;
					showpic();
				} else	/* update for clicks outside buttons */
					update();
				break;
			default:
				break;
			}
		} else
			update();
	}
}

update()
{
	int             i, newtrees = VOLUME / 32;
	fixboundary();
# if 0
	/* grow new trees */
	while (newtrees) {
		newtrees--;
		i = VOLUME;
		while (i >= VOLUME) {
			i = births[birthindex];
			births[birthindex] ^= births[randomizer];
			if ((++birthindex) >= RSIZE)
				birthindex = 0;
			if ((++randomizer) >= RSIZE)
				randomizer = 0;
		}
		if (field[old][i] != fire)
			field[new][i] = field[old][i] = tree;
	}
# endif
	/* spread fires */
	for (i = NCOLS; i < VOLUME - NCOLS; i++) {
		if (fire == field[old][i]) {
			if (tree == field[new][i - 1])
				field[new][i - 1] = fire;
			if (tree == field[new][i + 1])
				field[new][i + 1] = fire;
			if (tree == field[new][i - NCOLS])
				field[new][i - NCOLS] = fire;
			if (tree == field[new][i + NCOLS])
				field[new][i + NCOLS] = fire;
/* 			field[old][i] = field[new][i] = barren; */
			field[old][i] = field[new][i] = burnt;
		}
	}
# if 1
	/* fix top and bottom boundaries */
	for (i = 0; i < NCOLS; i++)
		if (fire == field[old][i])
			if (tree == field[new][i + NCOLS])
				field[new][i + NCOLS] = fire;
	for (i = VOLUME - NCOLS; i < VOLUME; i++)
		if (fire == field[old][i])
			if (tree == field[new][i - NCOLS])
				field[new][i - NCOLS] = fire;
# endif
	old = new;
	new = 1 - new;
	showpic();
/* 	if ((iter_tot % 25) == 0) { */
	if (1) {
		ntrees = nfires = 0;
		for (i = NCOLS; i < VOLUME - NCOLS; i++) {
			ntrees += (field[old][i] == tree);
			nfires += (field[old][i] == fire);
		}
		iteration = 0;
		sprintf(stringbuffer, "%d iter, %d fires     ", iter_tot, nfires);
		XDrawImageString(display, window, gccolor, 20, PLAYTOP + 25 + block * NROWS,
				 stringbuffer, strlen(stringbuffer));
		sprintf(stringbuffer, 
			"%5.3f trees left, %5.3f start     ",
			(double) ntrees/(double) ntrees_init, p_init);
		XDrawImageString(display, window, gccolor, 20, PLAYTOP + 39 + block * NROWS,
				 stringbuffer, strlen(stringbuffer));
		if (nfires == 0) {
			paused = 1 - paused;
			mypause();
		}
	}
	iter_tot++;
	return;
}

showpic()
{
	int             row, col, i1, i2, color, j, j1, j2;
	char           *picture = (*spinimage).data;
	if (8 == (*spinimage).depth) {
		if (block > 1)	/* I wish I knew how to do this faster */
			for (row = 0; row < NROWS; row++)
				for (col = 0; col < NCOLS; col++) {
					color = translate[field[old][row * NCOLS + col]];
					j = block * (col + block * NCOLS * row);
					if (color != picture[j])
						for (i1 = 0; i1 < block; i1++) {
							j1 = i1 * block * NCOLS + j;
							for (i2 = 0; i2 < block; i2++)
								picture[j1 + i2] = color;
						}
				}
		else
			for (j = 0; j < VOLUME; j++)
				picture[j] = translate[field[old][j]];
	} else {		/* depth is not 8, use xputpixel (this is
				 * really ugly) */
		if (block > 1)	/* I wish I knew how to do this faster */
			for (row = 0; row < NROWS; row++)
				for (col = 0; col < NCOLS; col++) {
					color = translate[field[old][row * NCOLS + col]];
					if (color != XGetPixel(spinimage, j1 = block * col, j2 = block * row))
						for (i2 = 0; i2 < block; i2++)
							for (i1 = 0; i1 < block; i1++)
								XPutPixel(spinimage, j1 + i1, j2 + i2, color);
				}
		else
			for (j = 0; j < VOLUME; j++)
				XPutPixel(spinimage, j, 0, translate[field[old][j]]);
	}
	XPutImage(display, playground, gc, spinimage, 0, 0, 0, 0, block * NCOLS, block * NROWS);
	return;
}

fixboundary()
/* copies edges for periodicity */
{
	int             i;
	for (i = 0; i < NCOLS; i++) {
		field[old][i] = field[old][VOLUME - 2 * NCOLS + i];
		field[old][VOLUME - NCOLS + i] = field[old][NCOLS + i];
	}
	for (i = 0; i < VOLUME; i += NCOLS) {
		field[old][i] = field[old][i + NCOLS - 2];
		field[old][i + NCOLS - 1] = field[old][i + 1];
	}
	return;
}

repaint()
/* this fixes the window up whenever it is uncovered */
{
	XDrawString(display, quitbutton, gcr,
		    0, font_height, "quit", 4);
	mypause();

	/* write various strings */
	sprintf(stringbuffer, "%d by %d lattice", NCOLS - 2, NROWS - 2);
	XDrawString(display, window, gc, 5, 40, stringbuffer, strlen(stringbuffer));
	XDrawString(display, window, gc, NCOLS * block + 50, PLAYTOP + 25 + block * NROWS
		    ,"MJC", 3);
	showpic();
	return;
}

/* fix up the pause button */
mypause()
{
	if (0 == paused)
		XDrawImageString(display, pausebutton, gcr,
				 0, font_height, "pause", 5);
	else
		XDrawImageString(display, pausebutton, gcr,
				 0, font_height, " run ", 5);
	return;
}

openwindow(argc, argv)
/*
 * a lot of this is taken from the basicwin program in the Xlib Programming
 * Manual
 */
	int             argc;
	char          **argv;
{
	char           *window_name = "Forest fires";
	char           *icon_name = "fires";
	Pixmap          icon_pixmap;
	char           *display_name = NULL;
	long            event_mask;
	XColor          xcolor, colorcell;
	Colormap        cmap;
	int             i;
	XEvent          report;

#define icon_bitmap_width 16
#define icon_bitmap_height 16
	static char     icon_bitmap_bits[] = {
		0x1f, 0xf8, 0x1f, 0x88, 0x1f, 0x88, 0x1f, 0x88, 0x1f, 0x88, 0x1f, 0xf8,
		0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0x1f, 0xf8, 0xff, 0xff,
	0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};

	/* open up the display */
	if ((display = XOpenDisplay(display_name)) == NULL) {
		fprintf(stderr, "%s: cannot connect to X server %s\n",
			progname, XDisplayName(display_name));
		exit(-1);
	}
	screen = DefaultScreen(display);
	cmap = DefaultColormap(display, screen);
	/* set the colors */
	darkcolor = black = BlackPixel(display, screen);
	lightcolor = white = WhitePixel(display, screen);
	translate[3] = white;
	translate[1] = black;
	translate[2] = lightcolor;
	translate[0] = darkcolor;
	if (XAllocNamedColor(display, cmap, "firebrick", &colorcell, &xcolor))
		darkcolor = colorcell.pixel;
	if (XAllocNamedColor(display, cmap, "wheat", &colorcell, &xcolor))
		lightcolor = colorcell.pixel;
	if (XAllocNamedColor(display, cmap, "black", &colorcell, &xcolor))
		translate[barren] = colorcell.pixel;
	if (XAllocNamedColor(display, cmap, "forest green", &colorcell, &xcolor))
		translate[tree] = colorcell.pixel;
	if (XAllocNamedColor(display, cmap, "yellow", &colorcell, &xcolor))
		translate[fire] = colorcell.pixel;
	/* purple means I screwed up somewhere */
	if (XAllocNamedColor(display, cmap, "purple", &colorcell, &xcolor))
		translate[3] = colorcell.pixel;

	/* fill out the color table for future uses */
	for (i = 4; i < 256; i++)
		translate[i] = translate[i % 4];

	/* make the main window */
	windowwidth = block * NCOLS + 85;
	windowheight = PLAYTOP + block * NROWS + 40;
	window = XCreateSimpleWindow(display, RootWindow(display, screen),
				  0, 0, windowwidth, windowheight, 4, black,
				     lightcolor);

	/* make the icon */
	icon_pixmap = XCreateBitmapFromData(display, window,
					icon_bitmap_bits, icon_bitmap_width,
					    icon_bitmap_height);

	size_hints.flags = PPosition | PSize | PMinSize;
	size_hints.min_width = windowwidth;
	size_hints.min_height = windowheight;
#ifdef X11R3
	size_hints.x = x;
	size_hints.y = y;
	size_hints.width = windowwidth;
	size_hints.height = windowheight;
	XSetStandardProperties(display, window, window_name, icon_name,
			       icon_pixmap, argv, argc, &size_hints);
#else
	{
		XWMHints        wm_hints;
		XClassHint      class_hints;
		XTextProperty   windowName, iconName;
		if (XStringListToTextProperty(&window_name, 1, &windowName) == 0) {
			fprintf(stderr, "%s: structure allocation for windowName failed.\n"
				,progname);
			exit(-1);
		}
		if (XStringListToTextProperty(&icon_name, 1, &iconName) == 0) {
			fprintf(stderr, "%s: structure allocation for iconName failed.\n"
				,progname);
			exit(-1);
		}
		wm_hints.initial_state = NormalState;
		wm_hints.input = True;
		wm_hints.icon_pixmap = icon_pixmap;
		wm_hints.flags = StateHint | IconPixmapHint | InputHint;
		class_hints.res_name = progname;
		class_hints.res_class = "Basicwin";
		XSetWMProperties(display, window, &windowName, &iconName,
			  argv, argc, &size_hints, &wm_hints, &class_hints);
	}
#endif

	/* make the buttons */
	quitbutton = XCreateSimpleWindow(display, window,
					 6, 0, 40, 20, 2, black, darkcolor);
	pausebutton = XCreateSimpleWindow(display, window,
					56, 0, 48, 20, 2, black, darkcolor);
	playground = XCreateSimpleWindow(display, window,
		42, PLAYTOP, block * NCOLS, block * NROWS, 2, black, white);

	/* pick the events to look for */
	event_mask = ExposureMask | ButtonPressMask | StructureNotifyMask;
	XSelectInput(display, window, event_mask);
	event_mask = ButtonPressMask;
	/*
	 * note that with this simple mask if one just covers a button it
	 * will not get redrawn.  I wonder if anyone will notice?  If I put
	 * the exposuremask in here, things flash irritatingly on being
	 * uncovered.
	 */
	XSelectInput(display, quitbutton, event_mask);
	XSelectInput(display, pausebutton, event_mask);
	XSelectInput(display, playground, event_mask);

	/* pick font: 9x15 is supposed to almost always be there */
	if ((font = XLoadQueryFont(display, "9x15")) == NULL) {
		fprintf(stderr, "%s: Cannot open 9x15 font\n", progname);
		exit(-1);
	}
	font_height = font->ascent + font->descent;

	/*
	 * make graphics contexts: gc for black on white gccolor for
	 * background and buttons gcr for reverse video gcxor for
	 * highlighting
	 */

	gc = XCreateGC(display, window, 0, NULL);
	XSetFont(display, gc, font->fid);
	XSetForeground(display, gc, black);
	XSetBackground(display, gc, white);

	gcr = XCreateGC(display, window, 0, NULL);
	XSetFont(display, gcr, font->fid);
	XSetForeground(display, gcr, lightcolor);
	XSetBackground(display, gcr, darkcolor);

	gccolor = XCreateGC(display, window, 0, NULL);
	XSetFont(display, gccolor, font->fid);
	XSetForeground(display, gccolor, darkcolor);
	XSetBackground(display, gccolor, lightcolor);

	gcxor = XCreateGC(display, window, 0, NULL);
	XSetFont(display, gcxor, font->fid);
	XSetForeground(display, gcxor, darkcolor ^ lightcolor);
	XSetFunction(display, gcxor, GXxor);

	/* show the window and buttons */
	XMapWindow(display, window);
	XMapWindow(display, quitbutton);
	XMapWindow(display, pausebutton);
	XMapWindow(display, playground);
	/* wait for playground to be displayed befor proceeding */
	i = 1;			/* a flag */
	while (i) {
		XNextEvent(display, &report);
		switch (report.type) {
		case Expose:
			if (report.xexpose.window != playground)
				i = 0;
		default:
			break;
		}
	}
	/* make the image structure */
	spinimage = XGetImage((Display *) display, (Drawable) playground,
			      0, 0, block * NCOLS, block * NROWS,
			      AllPlanes, ZPixmap);
	if (NULL == spinimage) {
		fprintf(stderr, "trouble creating image structure\n");
		exit(-1);
	}
	repaint();
	return;
}
