/* -*- Mode: C; tab-width: 4 -*- */
/* dragon --- Oskar van Deventer's Hexagonal Dragons Maze */

#if !defined( lint ) && !defined( SABER )
static const char sccsid[] = "@(#)dragon.c	5.24 2007/01/18 xlockmore";

#endif

/*-
 * Copyright (c) 2001 by 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.
 *
 * From "Fractal Garden Maze" by Oskar van Deventer, Cubism For Fun
 * Issue 54 February 2001 p16, 17.  The maze itself is called a "Hexagonal
 * Dragons Maze", named after its similarity to the Dragon Curve (see
 * turtle.c).
 *
 * Revision History:
 * 18-Jan-2007: Added vertical option.
 * 07-Mar-2001: Started writing, based on demon mode
 */

/*-
  Grid     Number of Neighbors
  ----     ------------------
  Hexagon  6
*/

#ifdef STANDALONE
#define MODE_dragon
#define PROGCLASS "Dragon"
#define HACK_INIT init_dragon
#define HACK_DRAW draw_dragon
#define dragon_opts xlockmore_opts
#define DEFAULTS "*delay: 2000000 \n" \
 "*cycles: 16 \n" \
 "*size: -24 \n" \
 "*ncolors: 64 \n" \
 "*neighbors: 6 \n"
#define UNIFORM_COLORS
#include "xlockmore.h"		/* in xscreensaver distribution */
#else /* STANDALONE */
#include "xlock.h"		/* in xlockmore distribution */

#endif /* STANDALONE */
#include "automata.h"

#ifdef MODE_dragon

#define DEF_VERTICAL "False"

static Bool vertical;

static XrmOptionDescRec opts[] =
{
	{(char *) "-vertical", (char *) ".dragon.vertical", XrmoptionNoArg, (caddr_t) "on"},
	{(char *) "+vertical", (char *) ".dragon.vertical", XrmoptionNoArg, (caddr_t) "off"}

};
static argtype vars[] =
{
	{(void *) & vertical, (char *) "vertical", (char *) "Vertical", (char *) DEF_VERTICAL, t_Bool}
};
static OptionStruct desc[] =
{
        {(char *) "-/+vertical", (char *) "change orientation for hexagons"}
};

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

#ifdef USE_MODULES
ModStruct   dragon_description =
{"dragon", "init_dragon", "draw_dragon", "release_dragon",
 "refresh_dragon", "init_dragon", (char *) NULL, &dragon_opts,
 2000000, 1, 16, -24, 64, 1.0, "",
 "Shows Deventer's Hexagonal Dragons Maze", 0, NULL};

#endif

#include "bitmaps/gray1.xbm"

#define STATES 3
#define LISTS 2
#define REDRAWSTEP 2000		/* How many cells to draw per cycle */
#define MINGRIDSIZE 16
#define MINSIZE 4

/* Singly linked list */
typedef struct _CellList {
	XPoint      pt;
	struct _CellList *next;
} CellList;

typedef struct {
	Bool        vertical;
	int         generation;
	int         xs, ys;
	int         xb, yb;
	int         nrows, ncols;
	int         width, height;
	int         addlist;
	int         redrawing, redrawpos;
	int        *ncells;
	unsigned long color;
	CellList  **cellList;
	unsigned char *oldcell;
	GC          stippledGC;
	Pixmap      graypix;
	XPoint      hexagon[6];
} dragonstruct;

static dragonstruct *dragons = (dragonstruct *) NULL;

static void
drawcell(ModeInfo * mi, int col, int row, unsigned char state)
{
	dragonstruct *dp = &dragons[MI_SCREEN(mi)];
	Display    *display = MI_DISPLAY(mi);
	GC          gc;

	if (!state) {
		XSetForeground(display, MI_GC(mi), MI_WHITE_PIXEL(mi));
		gc = MI_GC(mi);
	} else if (state == 1) {
		XSetForeground(display, MI_GC(mi), MI_BLACK_PIXEL(mi));
		gc = MI_GC(mi);
	} else if (MI_NPIXELS(mi) > 2) {
		XSetForeground(display, MI_GC(mi), dp->color);
		gc = MI_GC(mi);
	} else {
		XGCValues   gcv;

		gcv.stipple = dp->graypix;
		gcv.foreground = MI_WHITE_PIXEL(mi);
		gcv.background = MI_BLACK_PIXEL(mi);
		XChangeGC(display, dp->stippledGC,
			  GCStipple | GCForeground | GCBackground, &gcv);
		gc = dp->stippledGC;
	}
	{
		int ccol = 2 * col + !(row & 1), crow = 2 * row;

		if (dp->vertical) {
			dp->hexagon[0].x = dp->xb + ccol * dp->xs;
			dp->hexagon[0].y = dp->yb + crow * dp->ys;
		} else {
			dp->hexagon[0].y = dp->xb + ccol * dp->xs;
			dp->hexagon[0].x = dp->yb + crow * dp->ys;
		}
		if (dp->xs == 1 && dp->ys == 1)
			XDrawPoint(display, MI_WINDOW(mi), gc,
				dp->hexagon[0].x, dp->hexagon[0].y);
		else
			XFillPolygon(display, MI_WINDOW(mi), gc,
				dp->hexagon, 6, Convex, CoordModePrevious);
	}
}

static Bool
addtolist(ModeInfo * mi, int col, int row)
{
	dragonstruct *dp = &dragons[MI_SCREEN(mi)];
	CellList   *current;

	current = dp->cellList[dp->addlist];
	if ((dp->cellList[dp->addlist] = (CellList *)
		malloc(sizeof (CellList))) == NULL) {
		return False;
	}
	dp->cellList[dp->addlist]->pt.x = col;
	dp->cellList[dp->addlist]->pt.y = row;
	dp->cellList[dp->addlist]->next = current;
	dp->ncells[dp->addlist]++;
	return True;
}

#ifdef DEBUG
static void
print_state(ModeInfo * mi, int state)
{
	dragonstruct *dp = &dragons[MI_SCREEN(mi)];
	CellList   *locallist;
	int         i = 0;

	locallist = dp->cellList[state];
	(void) printf("state %d\n", state);
	while (locallist) {
		(void) printf("%d	x %d, y %d\n", i,
			      locallist->pt.x, locallist->pt.y);
		locallist = locallist->next;
		i++;
	}
}

#endif

static void
free_alist(dragonstruct * dp, int list)
{
	CellList   *current;

	while (dp->cellList[list]) {
		current = dp->cellList[list];
		dp->cellList[list] = dp->cellList[list]->next;
		free(current);
	}
	dp->cellList[list] = (CellList *) NULL;
	if (dp->ncells != NULL)
		dp->ncells[list] = 0;
}


static void
free_list(dragonstruct * dp)
{
	int         list;

	for (list = 0; list < LISTS; list++)
		free_alist(dp, list);
	free(dp->cellList);
	dp->cellList = (CellList **) NULL;
	if (dp->ncells != NULL) {
		free(dp->ncells);
		dp->ncells = (int *) NULL;
	}
}

static void
free_struct(dragonstruct * dp)
{
	if (dp->cellList != NULL) {
		free_list(dp);
	}
	if (dp->oldcell != NULL) {
		free(dp->oldcell);
		dp->oldcell = (unsigned char *) NULL;
	}
}

static void
free_dragon(Display *display, dragonstruct *dp)
{
	if (dp->stippledGC != None) {
		XFreeGC(display, dp->stippledGC);
		dp->stippledGC = None;
	}
	if (dp->graypix != None) {
		XFreePixmap(display, dp->graypix);
		dp->graypix = None;
	}
	free_struct(dp);
}

#if 0
static Bool
draw_state(ModeInfo * mi, int state)
{
	dragonstruct *dp = &dragons[MI_SCREEN(mi)];
	Display    *display = MI_DISPLAY(mi);
	GC          gc;
	CellList   *current;

	if (!state) {
		XSetForeground(display, MI_GC(mi), MI_WHITE_PIXEL(mi));
		gc = MI_GC(mi);
	} else if (state == 1) {
		XSetForeground(display, MI_GC(mi), MI_BLACK_PIXEL(mi));
		gc = MI_GC(mi);
	} else if (MI_NPIXELS(mi) > 2) {
		XSetForeground(display, MI_GC(mi), dp->color);
		gc = MI_GC(mi);
	} else {
		XGCValues   gcv;

		gcv.stipple = dp->graypix;
		gcv.foreground = MI_WHITE_PIXEL(mi);
		gcv.background = MI_BLACK_PIXEL(mi);
		XChangeGC(display, dp->stippledGC,
			  GCStipple | GCForeground | GCBackground, &gcv);
		gc = dp->stippledGC;
	}
	{	/* Draw right away, slow */
		current = dp->cellList[state];
		while (current) {
			int         col, row, ccol, crow;

			col = current->pt.x;
			row = current->pt.y;
			ccol = 2 * col + !(row & 1), crow = 2 * row;
			if (dp->vertical) {
				dp->hexagon[0].x = dp->xb + ccol * dp->xs;
				dp->hexagon[0].y = dp->yb + crow * dp->ys;
			} else {
				dp->hexagon[0].y = dp->xb + ccol * dp->xs;
				dp->hexagon[0].x = dp->yb + crow * dp->ys;
			}
			if (dp->xs == 1 && dp->ys == 1)
				XDrawPoint(display, MI_WINDOW(mi), gc,
					dp->hexagon[0].x, dp->hexagon[0].y);
			else
				XFillPolygon(display, MI_WINDOW(mi), gc,
					dp->hexagon, 6, Convex, CoordModePrevious);
			current = current->next;
		}
	}
	XFlush(MI_DISPLAY(mi));
	return True;
}
#endif

static Bool
SetSoup(ModeInfo * mi)
{
	dragonstruct *dp = &dragons[MI_SCREEN(mi)];
	int         row, col, mrow = 0;

	for (row = dp->nrows - 1; row >= 0; --row) {
		for (col = dp->ncols - 1; col >= 0; --col) {
			if (!addtolist(mi, col, row))
				return False;
		}
		mrow += dp->ncols;
	}
	dp->addlist = !dp->addlist;
	return True;
}

void
init_dragon(ModeInfo * mi)
{
	Display    *display = MI_DISPLAY(mi);
	Window      window = MI_WINDOW(mi);
	int         size = MI_SIZE(mi);
	dragonstruct *dp;

	if (dragons == NULL) {
		if ((dragons = (dragonstruct *) calloc(MI_NUM_SCREENS(mi),
					      sizeof (dragonstruct))) == NULL)
			return;
	}
	dp = &dragons[MI_SCREEN(mi)];

	dp->generation = 0;
	dp->redrawing = 0;
	if (MI_NPIXELS(mi) <= 2) {
		if (dp->stippledGC == None) {
			XGCValues   gcv;

			gcv.fill_style = FillOpaqueStippled;
			if ((dp->stippledGC = XCreateGC(display, window,
				 GCFillStyle, &gcv)) == None) {
				free_dragon(display, dp);
				return;
			}
		}
		if (dp->graypix == None) {
			if ((dp->graypix = XCreateBitmapFromData(display, window,
				(char *) gray1_bits, gray1_width, gray1_height)) == None) {
				free_dragon(display, dp);
				return;
			}
		}
	}
	free_struct(dp);
	if (MI_NPIXELS(mi) > 2)
		dp->color = MI_PIXEL(mi, NRAND(MI_NPIXELS(mi)));

	if ((dp->cellList = (CellList **) calloc(STATES,
			sizeof (CellList *))) == NULL) {
		free_dragon(display, dp);
		return;
	}
	if ((dp->ncells = (int *) calloc(STATES, sizeof (int))) == NULL) {
		free_dragon(display, dp);
		return;
	}

	dp->addlist = 0;
        if (MI_IS_FULLRANDOM(mi)) {
		dp->vertical = (Bool) (LRAND() & 1);
	} else {
		dp->vertical = vertical;
	}
	dp->width = MI_WIDTH(mi);
	dp->height = MI_HEIGHT(mi);
	{
		int nccols, ncrows, i;

		if (!dp->vertical) {
			dp->height = MI_WIDTH(mi);
			dp->width = MI_HEIGHT(mi);
		}
		if (dp->width < 8)
			dp->width = 8;
		if (dp->height < 8)
			dp->height = 8;
		if (size < -MINSIZE)
			dp->ys = NRAND(MIN(-size, MAX(MINSIZE, MIN(dp->width, dp->height) /
				      MINGRIDSIZE)) - MINSIZE + 1) + MINSIZE;
		else if (size < MINSIZE) {
			if (!size)
				dp->ys = MAX(MINSIZE, MIN(dp->width, dp->height) / MINGRIDSIZE);
			else
				dp->ys = MINSIZE;
		} else
			dp->ys = MIN(size, MAX(MINSIZE, MIN(dp->width, dp->height) /
					       MINGRIDSIZE));
		dp->xs = dp->ys;
		nccols = MAX(dp->width / dp->xs - 2, 2);
		ncrows = MAX(dp->height / dp->ys - 1, 4);
		dp->ncols = nccols / 2;
		dp->nrows = 2 * (ncrows / 4);
		dp->xb = (dp->width - dp->xs * nccols) / 2 + dp->xs / 2;
		dp->yb = (dp->height - dp->ys * (ncrows / 2) * 2) / 2 -
			dp->ys / 4;
		for (i = 0; i < 6; i++) {
			if (dp->vertical) {
				dp->hexagon[i].x =
					dp->xs * hexagonUnit[i].x;
				dp->hexagon[i].y =
					((dp->ys + 1) * hexagonUnit[i].y /
					2) * 4 / 3;
			} else {
				dp->hexagon[i].y =
					dp->xs * hexagonUnit[i].x;
				dp->hexagon[i].x =
					((dp->ys + 1) * hexagonUnit[i].y /
					2) * 4 / 3;
			}
		}
	}

	MI_CLEARWINDOW(mi);

	if ((dp->oldcell = (unsigned char *)
		calloc(dp->ncols * dp->nrows,
			sizeof (unsigned char))) == NULL) {
		free_dragon(display, dp);
		return;
	}
	if (!SetSoup(mi)) {
		free_dragon(display, dp);
		return;
	}
}

void
draw_dragon(ModeInfo * mi)
{
	int white, black;
	int         choose_layer, factor, orient, l;
	dragonstruct *dp;
	CellList *locallist;
	Bool detour = False;

	if (dragons == NULL)
		return;
	dp = &dragons[MI_SCREEN(mi)];
	if (dp->cellList == NULL)
		return;

	MI_IS_DRAWN(mi) = True;
	choose_layer= NRAND(6);
	if (dp->ncells[!dp->addlist] == 1) {
		/* Since the maze is infinite, it may not get to this last
		 * spot for a while.  Force it to do it right away so it
		 * does not appear to be stuck. */
		detour = True;
		white = black = 0; /* not used but make -Wall happy */
	} else {
		white = (choose_layer / 2);
		black = (choose_layer % 2) ?
			((white + 2) % 3) : ((white + 1) % 3);
		/* gray = (choose_layer % 2) ?
			((white + 1) % 3) : ((white + 2) % 3); */
	}
	locallist = dp->cellList[!dp->addlist];
	orient = dp->generation % 2;
	factor = 1;
	for (l = 0; l < dp->generation / 2; l++) {
		factor *= 3;
	}
	if (!locallist && dp->generation >= MI_CYCLES(mi)) {
		init_dragon(mi);
		return;
	}

	while (locallist) {
		int i, j, k;

		i = locallist->pt.x;
		j = locallist->pt.y;
		if (orient) {
			k = (j / factor) % 3;
		} else {
		 	if (j % 2) {
				/* Had trouble with this line... */
				k = ((i + factor / 2) / factor + 1) % 3;
			} else {
				k = (i / factor) % 3;
			}
		}
		if (detour) {
			k = (LRAND() & 1) + 1;
			dp->oldcell[j * dp->ncols + i] = k;
			drawcell(mi, i, j, k);
		} if (white == k) {
			dp->oldcell[j * dp->ncols + i] = 0;
			drawcell(mi, i, j, 0);
			if (!addtolist(mi, i, j)) {
				free_dragon(MI_DISPLAY(mi), dp);
				return;
			}
		} else if (black == k) {
			dp->oldcell[j * dp->ncols + i] = 1;
			drawcell(mi, i, j, 1);
		} else /* if (gray == k) */ {
			dp->oldcell[j * dp->ncols + i] = 2;
			drawcell(mi, i, j, 2);
		}
		dp->cellList[!dp->addlist] = dp->cellList[!dp->addlist]->next;
		free(locallist);
		dp->ncells[!dp->addlist]--;
		locallist = dp->cellList[!dp->addlist];
		if ((dp->cellList[!dp->addlist] == NULL) &&
		    (dp->cellList[dp->addlist] == NULL))
			dp->generation = 0;
	}
	dp->addlist = !dp->addlist;
	if (dp->redrawing) {
		int i;

		for (i = 0; i < REDRAWSTEP; i++) {
			if (dp->oldcell[dp->redrawpos] != 1) {
				drawcell(mi, dp->redrawpos % dp->ncols, dp->redrawpos / dp->ncols,
					 dp->oldcell[dp->redrawpos]);
			}
			if (++(dp->redrawpos) >= dp->ncols * dp->nrows) {
				dp->redrawing = 0;
				break;
			}
		}
	}
	dp->generation++;
}
void
release_dragon(ModeInfo * mi)
{
	if (dragons != NULL) {
		int         screen;

		for (screen = 0; screen < MI_NUM_SCREENS(mi); screen++)
			free_dragon(MI_DISPLAY(mi), &dragons[screen]);
		free(dragons);
		dragons = (dragonstruct *) NULL;
	}
}

void
refresh_dragon(ModeInfo * mi)
{
	dragonstruct *dp;

	if (dragons == NULL)
		return;
	dp = &dragons[MI_SCREEN(mi)];

	dp->redrawing = 1;
	dp->redrawpos = 0;
}

#endif /* MODE_dragon */
