/*
 * lockward.c:	First attempt at an Xscreensaver.
 *
 * Leo L. Schwab					2007.08.17
 ****
 * Copyright (c) 2007 Leo L. Schwab
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to permit
 * persons to whom the Software is furnished to do so, subject to the
 * following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
 * NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
 * USE OR OTHER DEALINGS IN THE SOFTWARE.
 */
#include <ctype.h>
#include <strings.h>

#include "xlockmore.h"
#include "colors.h"


/***************************************************************************
 * #defines
 */
#ifdef USE_GL /* whole file */

#define	DEFAULTS	"*delay:	20000	\n"\
			"*showFPS:	False	\n"

#define	release_lockward	0


#define	NUMOF(x)	(sizeof ((x)) / sizeof ((*x)))

#define	NBLADES		12
#define	NSPINNERS	4
#define	NRADII		8
#define	COLORIDX_SHF	4
#define	SUBDIV		6


/***************************************************************************
 * Structure definitions.
 */
struct lockward_context;			/*  Forward declaration.  */

#define int8_t   char
#define int16_t  short
#define int32_t  int
#define uint8_t  unsigned char
#define uint16_t unsigned short
#define uint32_t unsigned int

typedef struct bladestate {
	uint8_t		outer, inner;	/*  Radii  */
} bladestate;

typedef struct spinnerstate {
	GLfloat		rot;	/*  Terminal rotation after count expires */
	GLfloat		rotinc;	/*  Per-frame increment to rot.  */
	XColor		*colors;
	bladestate	*bladeidx;
	int		ncolors;	/*  n.4 fixed-point  */
	int		ccolor;		/*  n.4 fixed-point  */
	int		colorinc;	/*  n.4 fixed-point  */
	int		rotcount;
	uint8_t		nblades;
} spinnerstate;

typedef struct blinkstate {
	int		(*drawfunc) (struct lockward_context *ctx,
			             struct blinkstate *bs);
	uint32_t	*noise;	/*  For draw_blink_segment_scatter()  */
	GLfloat		color[4];
	uint32_t	val;
	int16_t		dwell;	/*  <0: sharp  >0: decay  */
	int16_t		dwellcnt;
	uint8_t		type;
	int8_t		counter;
	int8_t		direction;
	int8_t		radius;
} blinkstate;

enum blinktype {
	BTYPE_RADIAL_SINGLE = 0,
	BTYPE_RADIAL_RANDOM,
	BTYPE_RADIAL_SEQ,
	BTYPE_RADIAL_DOUBLESEQ,
	BTYPE_SEGMENT_SINGLE,
	BTYPE_SEGMENT_RANDOM,
	BTYPE_CONCENTRIC_SINGLE,
	BTYPE_CONCENTRIC_RANDOM,
	BTYPE_CONCENTRIC_SEQ,
	BTYPE_SEGMENT_SCATTER,
	MAX_BTYPE
};

typedef struct { GLfloat x,y,z; } XYZ;

typedef struct lockward_context {
	GLXContext	*glx_context;

	spinnerstate	spinners[NSPINNERS];
	blinkstate	blink;

        /* This used to put vertexes into lists without putting begin/end
           into the same list!  I didn't even know that worked.  Well, it
           doesn't work with jwzgles, so I changed it to not do that. */
     /* GLuint		blades_outer, blades_inner; */
        XYZ points_outer[NRADII][SUBDIV+1];
        XYZ points_inner[NRADII][SUBDIV+1];

	GLuint		rings;
	Bool		blendmode;
	int		nextblink;
	int		fps;

} lockward_context;


/***************************************************************************
 * Prototypes.
 */
ENTRYPOINT void free_lockward (ModeInfo *mi);


/***************************************************************************
 * Global variables.
 */
static lockward_context	*g_ctx = NULL;
static Bool		g_blink_p = True;
static int		g_blades = NBLADES;
static int		g_rotateidle_min,
			g_rotateidle_max;
static int		g_blinkidle_min,
			g_blinkidle_max;
static int		g_blinkdwell_min,
			g_blinkdwell_max;

#define DEF_BLINK		"True"
#define DEF_ROTATEIDLEMIN	"1000"
#define DEF_ROTATEIDLEMAX	"6000"
#define DEF_BLINKIDLEMIN	"1000"
#define DEF_BLINKIDLEMAX	"9000"
#define DEF_BLINKDWELLMIN	"100"
#define DEF_BLINKDWELLMAX	"600"


static XrmOptionDescRec opts[] = {
	{ "-blink",	".blink",  XrmoptionNoArg, "on" },
	{ "+blink",	".blink",  XrmoptionNoArg, "off" },
	{ "-rotateidle-min",	".rotateidlemin",	XrmoptionSepArg,	0 },
	{ "-rotateidle-max",	".rotateidlemax",	XrmoptionSepArg,	0 },
	{ "-blinkidle-min",	".blinkidlemin",	XrmoptionSepArg,	0 },
	{ "-blinkidle-max",	".blinkidlemax",	XrmoptionSepArg,	0 },
	{ "-blinkdwell-min",	".blinkdwellmin",	XrmoptionSepArg,	0 },
	{ "-blinkdwell-max",	".blinkdwellmax",	XrmoptionSepArg,	0 },
};

static argtype vars[] = {
	{ &g_blink_p, "blink",	"Blink", DEF_BLINK, t_Bool	},
	{ &g_rotateidle_min, "rotateidlemin",	"Rotateidlemin", DEF_ROTATEIDLEMIN, t_Int	},
	{ &g_rotateidle_max, "rotateidlemax",	"Rotateidlemax", DEF_ROTATEIDLEMAX, t_Int	},
	{ &g_blinkidle_min, "blinkidlemin", "Blinkidlemin", DEF_BLINKIDLEMIN, t_Int },
	{ &g_blinkidle_max, "blinkidlemax", "Blinkidlemax", DEF_BLINKIDLEMAX, t_Int },
	{ &g_blinkdwell_min, "blinkdwellmin", "Blinkdwellmin", DEF_BLINKDWELLMIN, t_Int },
	{ &g_blinkdwell_max, "blinkdwellmax", "Blinkdwellmax", DEF_BLINKDWELLMAX, t_Int },
};

static OptionStruct desc[] = {
	{ "-/+blink", "Turn on/off blinking effects." },
	{ "-rotateidle-min", "Minimum idle time for rotators, in milliseconds." },
	{ "-rotateidle-max", "Maximum idle time for rotators, in milliseconds." },
	{ "-blinkidle-min", "Minimum idle time between blink effects, in milliseconds." },
	{ "-blinkidle-max", "Maximum idle time between blink effects, in milliseconds." },
	{ "-blinkdwell-min", "Minimum dwell time for blink effects, in milliseconds." },
	{ "-blinkdwell-max", "Maximum dwell time for blink effects, in milliseconds." },
};

ENTRYPOINT ModeSpecOpt lockward_opts = {
	NUMOF(opts), opts, NUMOF(vars), vars, desc
};


/***************************************************************************
 * Window management.
 */
ENTRYPOINT void
reshape_lockward (ModeInfo *mi, int width, int height)
{
	lockward_context *ctx = &g_ctx[MI_SCREEN (mi)];
	GLfloat h = (GLfloat) height / (GLfloat) width;
        int y = 0;

        if (width > height * 5) {   /* tiny window: show middle */
          height = width * 9/16;
          y = -height/2;
          h = height / (GLfloat) width;
        }

	glXMakeCurrent (MI_DISPLAY (mi), MI_WINDOW (mi), *ctx->glx_context);

	glViewport (0, y, (GLint) width, (GLint) height);

	glMatrixMode (GL_PROJECTION);
	glLoadIdentity ();
	if (height > width)
		glOrtho (-8.0, 8.0, -8.0 * h, 8.0 * h, -1, 1);
	else
		glOrtho (-8.0 / h, 8.0 / h, -8.0, 8.0, -1, 1);

	glMatrixMode (GL_MODELVIEW);
}

ENTRYPOINT Bool
lockward_handle_event (ModeInfo *mi, XEvent *event)
{
	lockward_context *ctx = &g_ctx[MI_SCREEN (mi)];

	if (event->xany.type == KeyPress) {
		KeySym	keysym;
		char	c = 0;

		XLookupString (&event->xkey, &c, 1, &keysym, 0);
		if (c == ' ' || c == '\t') {
			ctx->blendmode ^= 1;
			return True;
		}
	}

	return False;
}


/***************************************************************************
 * "Blade" routines.
 */
static void
random_blade_rot (lockward_context *ctx, struct spinnerstate *ss)
{
	/*
	 * The circle is divided up in to g_blades divisions.  The idea here
	 * is to rotate to an exact division point.
	 *
	 * The target rotation is computed via random numbers.
	 *
	 * The time it takes to get there is a maximum of six seconds per
	 * division, and a minimum of one second (no matter how far away it
	 * is), and is selected by random numbers.
	 *
	 * The time value is converted into frames, and a per-frame rotation
	 * is computed.
	 *
	 * During rendering, we approach the target rotation by subtracting
	 * from it the per-frame rotation times the number of outstanding
	 * ticks.  Doing it this way means we'll hit the target rotation
	 * exactly, without low-order errors creeping in to the values (at
	 * least not nearly as quickly).
	 */
	GLfloat	d;
	int	dist;

	dist = random() % g_blades + 1;

	ss->rotcount = random() % (6 * dist * ctx->fps - ctx->fps)
		     + ctx->fps;

	if (random() & 4)
		dist = -dist;
	d = dist * 360.0 / (GLfloat) g_blades;
	ss->rot += d;
	ss->rotinc = d / (GLfloat) ss->rotcount;
}


/*
 * A "blade" is pie-wedge shaped flat thing that is rotated around where the
 * apex is/would be.  Initially envisioned as 1/12th of a circle, but that
 * could be configurable.  The inner and outer edges are rounded off using
 * six subdivisions so that, when multiple blades are assembled, it looks
 * more like a circle and less like a polygon.
 *
 * The blade is assembled as a tri-fan.  It is oriented centered at 3
 * o'clock.  The blade is composed of two display lists -- arcs, essentially
 * -- the outer and the inner one.  The outer one *must* be called before
 * the inner one, or the blade clockwise-ness will be wrong, and become
 * invisible.  Arcs of various radii are compiled.
 */

static void
gen_blade_arcs (lockward_context *ctx)
{
	GLfloat	here, there, step;
	int	i, n;

	here = 0;
	there = M_PI * 2.0 / g_blades;
	step = there / SUBDIV;
	here -= SUBDIV * step / 2.0;

	/*
	 * Build outer blade arcs.
	 * Start at left side of outer radius.  Strike all its vertices.
	 */
	for (n = 0;  n < NRADII;  ++n) {
          /* glNewList (ctx->blades_outer + n, GL_COMPILE); */
          XYZ *a = ctx->points_outer[n];
          int j = 0;
          for (i = SUBDIV;  i >= 0;  --i) {
            /* glVertex3f (cos (here + step * i) * (n + 1.0),
                           sin (here + step * i) * (n + 1.0), 0); */
            a[j].x = cos (here + step * i) * (n + 1.0);
            a[j].y = sin (here + step * i) * (n + 1.0);
            a[j].z = 0;
            j++;
          }
          if (j != SUBDIV+1) abort();
          /* glEndList (); */
	}

	/*
	 * Build inner blade arcs.
	 * Move to inner radius, strike all vertices in opposite order.
	 */
	for (n = 0;  n < NRADII;  ++n) {
          /* glNewList (ctx->blades_inner + n, GL_COMPILE); */
          XYZ *a = ctx->points_inner[n];
          int j = 0;
          for (i = 0;  i <= SUBDIV;  ++i) {
            /* glVertex3f (cos (here + step * i) * (n + 1.0),
		           sin (here + step * i) * (n + 1.0), 0); */
            a[j].x = cos (here + step * i) * (n + 1.0);
            a[j].y = sin (here + step * i) * (n + 1.0);
            a[j].z = 0;
            j++;
          }
          if (j != SUBDIV+1) abort();
          /* glEndList (); */
	}
}

static void
gen_rings (lockward_context *ctx)
{
	GLfloat step;
	int	i, n;

	step = M_PI * 2.0 / (g_blades * SUBDIV);

	for (n = 0;  n < NRADII - 1;  ++n) {
		glNewList (ctx->rings + n, GL_COMPILE);
		glBegin (GL_TRIANGLE_STRIP);
		for (i = g_blades * SUBDIV;  i >= 0;  --i) {
			glVertex3f (cos (step * i) * (n + 1.0),
			            sin (step * i) * (n + 1.0), 0);
			glVertex3f (cos (step * i) * (n + 2.0),
			            sin (step * i) * (n + 2.0), 0);
		}
		glEnd();
		glEndList ();
	}
}


/***************************************************************************
 * "Blink" routines.
 */
static int
calc_interval_frames (lockward_context *ctx, int min, int max)
{
	/*
	 * Compute random interval between min and max milliseconds.
	 * Returned value is in frames.
	 */
	register int i;

	i = min;
	if (max > min)
		i += random() % (max - min);

	return i * ctx->fps / 1000;
}

static void
set_alpha_by_dwell (struct blinkstate *bs)
{
	if (bs->dwell > 0)
		bs->color[3] = (GLfloat) bs->dwellcnt / (GLfloat) bs->dwell;
	else
		bs->color[3] = bs->dwellcnt > (-bs->dwell >> 2)  ?  1.0  : 0.0;
}


static void
draw_blink_blade (lockward_context *ctx, int inner, int outer,
                  Bool begin_p)
{
  int i;
  if (begin_p) glBegin (GL_TRIANGLE_FAN);
  /* glCallList (ctx->blades_outer + outer); */
  for (i = 0; i < countof(*ctx->points_outer); i++)
    glVertex3f(ctx->points_outer[outer][i].x,
               ctx->points_outer[outer][i].y,
               ctx->points_outer[outer][i].z);

  /* glCallList (ctx->blades_inner + inner); */
  for (i = 0; i < countof(*ctx->points_inner); i++)
    glVertex3f(ctx->points_inner[inner][i].x,
               ctx->points_inner[inner][i].y,
               ctx->points_inner[inner][i].z);
  if (begin_p) glEnd();
}

static int
draw_blink_radial_random (lockward_context *ctx, struct blinkstate *bs)
{
	int i;

	/*
	 * There is no sense of direction in a random sweep, so re-use the
	 * 'direction' field to hold the current blade we're messing with.
	 */
	if (bs->dwellcnt < 0) {
		if (bs->counter <= 0) {
			bs->drawfunc = NULL;
			return 0;
		}

		/*
		 * Find available blade.  Potentially very slow, depending on
		 * how unlucky we are.
		 */
		do {
			i = random() % g_blades;
		} while (bs->val & (1 << i));
		bs->val |= (1 << i);	/*  Mark as used.  */
		bs->direction = i;
		if ((bs->dwellcnt = bs->dwell) < 0)
			bs->dwellcnt = -bs->dwellcnt;
		
		if (    bs->type == BTYPE_SEGMENT_SINGLE
		    ||  bs->type == BTYPE_SEGMENT_RANDOM)
			bs->radius = random() % (NRADII - 1);

		--bs->counter;
	}

	set_alpha_by_dwell (bs);
	glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
	glColor4fv (bs->color);
	glRotatef (bs->direction * 360.0 / (GLfloat) g_blades, 0, 0, 1);
	if (bs->radius >= 0)
		draw_blink_blade (ctx, bs->radius, bs->radius + 1, True);
	else
		draw_blink_blade (ctx, 0, NRADII - 1, True);

	--bs->dwellcnt;

	return SUBDIV + SUBDIV;
}

static int
draw_blink_radial_sequential (lockward_context *ctx, struct blinkstate *bs)
{
	if (bs->dwellcnt < 0) {
		if (bs->counter <= 0) {
			bs->drawfunc = NULL;
			return 0;
		}
		if ((bs->dwellcnt = bs->dwell) < 0)
			bs->dwellcnt = -bs->dwellcnt;
		--bs->counter;
	}

	set_alpha_by_dwell (bs);
	glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
	glColor4fv (bs->color);
	glRotatef ((bs->counter * bs->direction + (int) bs->val)
	            * 360.0 / (GLfloat) g_blades,
	           0, 0, 1);
	draw_blink_blade (ctx, 0, NRADII - 1, True);

	--bs->dwellcnt;

	return SUBDIV + SUBDIV;
}

static int
draw_blink_radial_doubleseq (lockward_context *ctx, struct blinkstate *bs)
{
	int polys;

	if (bs->dwellcnt < 0) {
		if (bs->counter <= 0) {
			bs->drawfunc = NULL;
			return 0;
		}
		if ((bs->dwellcnt = bs->dwell) < 0)
			bs->dwellcnt = -bs->dwellcnt;
		--bs->counter;
	}

	set_alpha_by_dwell (bs);
	glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
	glColor4fv (bs->color);

	glPushMatrix ();
	glRotatef (((int) bs->val + bs->counter) * 360.0 / (GLfloat) g_blades,
	           0, 0, 1);
	draw_blink_blade (ctx, 0, NRADII - 1, True);
	glPopMatrix ();
	polys = SUBDIV + SUBDIV;

	if (bs->counter  &&  bs->counter < g_blades / 2) {
		glRotatef (((int) bs->val - bs->counter)
		            * 360.0 / (GLfloat) g_blades,
		           0, 0, 1);
		draw_blink_blade (ctx, 0, NRADII - 1, True);
		polys += SUBDIV + SUBDIV;
	}

	--bs->dwellcnt;

	return polys;
}

static int
draw_blink_concentric_random (lockward_context *ctx, struct blinkstate *bs)
{
	int i;

	if (bs->dwellcnt < 0) {
		if (bs->counter <= 0) {
			bs->drawfunc = NULL;
			return 0;
		}

		do {
			i = random() % (NRADII - 1);
		} while (bs->val & (1 << i));
		bs->val |= (1 << i);
		bs->direction = i;
		if ((bs->dwellcnt = bs->dwell) < 0)
			bs->dwellcnt = -bs->dwellcnt;

		--bs->counter;
	}

	set_alpha_by_dwell (bs);
	glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
	glColor4fv (bs->color);
	glCallList (ctx->rings + bs->direction);

	--bs->dwellcnt;

	return g_blades * SUBDIV * 2;
}

static int
draw_blink_concentric_sequential (lockward_context *ctx, struct blinkstate *bs)
{
	if (bs->dwellcnt < 0) {
		if (bs->counter <= 0) {
			bs->drawfunc = NULL;
			return 0;
		}
		if ((bs->dwellcnt = bs->dwell) < 0)
			bs->dwellcnt = -bs->dwellcnt;
		--bs->counter;
	}

	set_alpha_by_dwell (bs);
	glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
	glColor4fv (bs->color);
	if (bs->direction > 0)
		glCallList (ctx->rings + (NRADII - 2) - bs->counter);
	else
		glCallList (ctx->rings + bs->counter);

	--bs->dwellcnt;

	return g_blades * SUBDIV * 2;
}

static int
draw_blink_segment_scatter (lockward_context *ctx, struct blinkstate *bs)
{
	int i, polys = 0;

	if (bs->dwellcnt < 0) {
		if (bs->counter <= 0) {
			bs->drawfunc = NULL;
			return 0;
		}

		/*
		 * Init random noise array.  On average, 1/4 of the bits will
		 * be set, which should look nice.  (1/2 looks too busy.)
		 */
		for (i = g_blades;  --i >= 0; )
			bs->noise[i] = random() & random()
			             & ((1 << (NRADII - 1)) - 1);

		if ((bs->dwellcnt = bs->dwell) < 0)
			bs->dwellcnt = -bs->dwellcnt;
		--bs->counter;
	}

	set_alpha_by_dwell (bs);
	glBlendFunc (GL_DST_COLOR, GL_SRC_ALPHA);
	glColor4fv (bs->color);

	for (i = g_blades;  --i >= 0; ) {
		register uint32_t	bits;
		int			inner, outer;

		/*
		 * Find consecutive runs of 1 bits.  Keep going until we run
		 * out of them.
		 */
		for (bits = bs->noise[i];  bits; ) {
			inner = ffs (bits) - 1;
			bits = ~bits & ~((1 << inner) - 1);
			outer = ffs (bits) - 1;
			bits = ~bits & ~((1 << outer) - 1);

			glPushMatrix ();
			glRotatef (i * 360.0 / (GLfloat) g_blades, 0, 0, 1);
			draw_blink_blade (ctx, inner, outer, True);
			glPopMatrix ();

			polys += SUBDIV + SUBDIV;
		}
	}

	--bs->dwellcnt;

	return polys;
}

static void
random_blink (lockward_context *ctx, struct blinkstate *bs)
{
	bs->color[0]	=
	bs->color[1]	=
	bs->color[2]	=
	bs->color[3]	= 1.0;
	bs->dwellcnt	= -1;
	bs->radius	= -1;
	bs->dwell	= calc_interval_frames
			  (ctx, g_blinkdwell_min, g_blinkdwell_max);
	if (random() & 2)
		bs->dwell = -bs->dwell;

	bs->type = random() % MAX_BTYPE;

	switch (bs->type) {
	case BTYPE_RADIAL_SINGLE:
	case BTYPE_SEGMENT_SINGLE:
		bs->drawfunc = draw_blink_radial_random;
		bs->val = 0;
		bs->counter = 1;
		break;
	case BTYPE_RADIAL_RANDOM:
	case BTYPE_SEGMENT_RANDOM:
		bs->drawfunc = draw_blink_radial_random;
		bs->val = 0;
		bs->counter = g_blades;
		break;
	case BTYPE_RADIAL_SEQ:
		bs->drawfunc = draw_blink_radial_sequential;
		bs->val = random() % g_blades;  /*  Initial offset  */
		bs->direction = random() & 8  ?  1 :  -1;
		bs->counter = g_blades;
		break;
	case BTYPE_RADIAL_DOUBLESEQ:
		bs->drawfunc = draw_blink_radial_doubleseq;
		bs->val = random() % g_blades;  /*  Initial offset  */
		bs->counter = g_blades / 2 + 1;
		break;
	case BTYPE_CONCENTRIC_SINGLE:
		bs->drawfunc = draw_blink_concentric_random;
		bs->val = 0;
		bs->counter = 1;
		break;
	case BTYPE_CONCENTRIC_RANDOM:
		bs->drawfunc = draw_blink_concentric_random;
		bs->val = 0;
		bs->counter = NRADII - 1;
		break;
	case BTYPE_CONCENTRIC_SEQ:
		bs->drawfunc = draw_blink_concentric_sequential;
		bs->direction = random() & 8  ?  1 :  -1;
		bs->counter = NRADII - 1;
		break;
	case BTYPE_SEGMENT_SCATTER:
		bs->drawfunc = draw_blink_segment_scatter;
		bs->counter = random() % (g_blades / 2) + (g_blades / 2) + 1;
		break;
	}
}


/***************************************************************************
 * Main rendering routine.
 */
ENTRYPOINT void
draw_lockward (ModeInfo *mi)
{
	lockward_context	*ctx = &g_ctx[MI_SCREEN (mi)];
	spinnerstate	*ss;
	Display		*dpy = MI_DISPLAY(mi);
	Window		window = MI_WINDOW(mi);
	int		i, n;

	GLfloat scolor[4] = {0.0, 0.0, 0.0, 0.5};

	if (!ctx->glx_context)
		return;

	glXMakeCurrent (MI_DISPLAY (mi), MI_WINDOW (mi), *ctx->glx_context);


	glClear (GL_COLOR_BUFFER_BIT);

	if (ctx->blendmode)
		glBlendFunc (GL_ONE, GL_ONE);
	else
		glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

	glPushMatrix ();
	glLoadIdentity ();

	mi->polygon_count = 0;

	for (n = NSPINNERS;  --n >= 0; ) {
		ss = &ctx->spinners[n];

		/*  Set color.  */
		i = ss->ccolor >> COLORIDX_SHF;
		scolor[0] = ss->colors[i].red   / 65535.0;
		scolor[1] = ss->colors[i].green / 65535.0;
		scolor[2] = ss->colors[i].blue  / 65535.0;
		glColor4fv (scolor);

		glPushMatrix ();
		glRotatef (ss->rot - ss->rotcount * ss->rotinc, 0, 0, 1);
		for (i = ss->nblades;  --i >= 0; ) {
                  glPushMatrix ();
                  glRotatef (360.0 * i / ss->nblades, 0, 0, 1);

                  glBegin (GL_TRIANGLE_FAN);
                  /* glCallList (ctx->blades_outer + ss->bladeidx[i].outer); */
                  /* glCallList (ctx->blades_inner + ss->bladeidx[i].inner); */
                  draw_blink_blade (ctx,
                                    ss->bladeidx[i].inner,
                                    ss->bladeidx[i].outer,
                                    False);
                  glEnd ();

                  glPopMatrix ();
                  mi->polygon_count += SUBDIV + SUBDIV;
		}
		glPopMatrix ();

		/*  Advance rotation.  */
		if (ss->rotcount) {
			if (ss->rotcount > 0)
				--ss->rotcount;
		} else {
			if (ss->rotinc == 0.0)
				random_blade_rot (ctx, ss);
			else {
				/*  Compute # of ticks to sit idle.  */
				ss->rotinc = 0.0;
				ss->rotcount =
				 calc_interval_frames (ctx,
				                       g_rotateidle_min,
				                       g_rotateidle_max);
			}
		}

		/*  Advance colors.  */
		if ((ss->ccolor += ss->colorinc) >= ss->ncolors)
			ss->ccolor -= ss->ncolors;
		else if (ss->ccolor < 0)
			ss->ccolor += ss->ncolors;
	}

	if (g_blink_p) {
		if (ctx->blink.drawfunc) {
			mi->polygon_count +=
			 ctx->blink.drawfunc (ctx, &ctx->blink);
		} else {
			if (ctx->nextblink > 0)
				--ctx->nextblink;
			else {
				/* Compute # of frames for blink idle time. */
				ctx->nextblink =
				 calc_interval_frames (ctx,
				                       g_blinkidle_min,
				                       g_blinkidle_max);
				random_blink (ctx, &ctx->blink);
			}
		}
	}
	glPopMatrix ();

	if (MI_IS_FPS (mi)) do_fps (mi);
	glFinish();

	glXSwapBuffers (dpy, window);
}


/***************************************************************************
 * Initialization/teardown.
 */
ENTRYPOINT void 
init_lockward (ModeInfo *mi)
{
	lockward_context	*ctx;
	int		i, n;

	MI_INIT (mi, g_ctx);
	ctx = &g_ctx[MI_SCREEN (mi)];

	ctx->glx_context = init_GL (mi);

	reshape_lockward (mi, MI_WIDTH (mi), MI_HEIGHT (mi));

	glEnable (GL_CULL_FACE);
	glEnable (GL_BLEND);
	glDisable (GL_DEPTH_TEST);

	glShadeModel (GL_FLAT);
	glFrontFace (GL_CW);

	/* ctx->blades_outer	= glGenLists (NRADII); */
	/* ctx->blades_inner	= glGenLists (NRADII); */
	ctx->rings		= glGenLists (NRADII - 1);
	ctx->blendmode		= 0;
/* WTF? 	ctx->fps		= 1000000 / MI_DELAY (mi); */
        ctx->fps = 60;
	ctx->nextblink		= calc_interval_frames
				   (ctx, g_blinkidle_min, g_blinkidle_max);
	ctx->blink.drawfunc	= NULL;
	ctx->blink.noise	= malloc (sizeof (uint32_t) * g_blades);
	if (!ctx->blink.noise) {
		fprintf (stderr, "Can't allocate noise array.\n");
		exit (1);
	}

	gen_blade_arcs (ctx);
	gen_rings (ctx);

	for (i = NSPINNERS;  --i >= 0; ) {
		spinnerstate *ss = &ctx->spinners[i];

		ss->rot		= 0.0;
		ss->rotcount	= -1;

		/*  Establish rotation  */
		random_blade_rot (ctx, ss);

		/*
		 * Establish color cycling path and rate.  Rate avoids zero.
		 */
		ss->ncolors = 128;
		ss->colorinc = (random() & ((2 << COLORIDX_SHF) - 1))
		             - (1 << COLORIDX_SHF);
		if (ss->colorinc >= 0)
			++ss->colorinc;

		ss->colors = (XColor *) calloc (ss->ncolors, sizeof (XColor));
		if (!ss->colors) {
			fprintf (stderr,
			         "Can't allocate XColors for spinner %d.\n",
			         i);
			exit (1);
		}
		make_smooth_colormap (0, 0, 0,
				      ss->colors, &ss->ncolors,
				      False, 0, False);
		ss->ncolors <<= COLORIDX_SHF;

		/*
		 * Create blades.
		 */
		ss->nblades	= g_blades;
		ss->bladeidx	= malloc (sizeof (bladestate) * g_blades);
		if (!ss->bladeidx) {
			fprintf (stderr, "Can't allocate blades.\n");
			exit (1);
		}
		for (n = g_blades;  --n >= 0; ) {
			/*
			 * Establish blade radii.  Can't be equal.  Ensure
			 * outer > inner.
			 */
			do {
				ss->bladeidx[n].outer = random() & 7;
				ss->bladeidx[n].inner = random() & 7;
			} while (ss->bladeidx[n].outer ==
			         ss->bladeidx[n].inner);

			if (ss->bladeidx[n].outer < ss->bladeidx[n].inner) {
				uint8_t	tmp;

				tmp = ss->bladeidx[n].outer;
				ss->bladeidx[n].outer = ss->bladeidx[n].inner;
				ss->bladeidx[n].inner = tmp;
			}
		}
	}
}

ENTRYPOINT void
free_lockward (ModeInfo *mi)
{
	lockward_context	*ctx = &g_ctx[MI_SCREEN (mi)];
	int i;

        if (!ctx->glx_context) return;
	glXMakeCurrent (MI_DISPLAY (mi), MI_WINDOW (mi), *ctx->glx_context);

	if (ctx->blink.noise)
		free (ctx->blink.noise);
	if (glIsList (ctx->rings))
		glDeleteLists (ctx->rings, NRADII - 1);
	/* if (glIsList (ctx->blades_outer))
		glDeleteLists (ctx->blades_outer, NRADII);
	if (glIsList (ctx->blades_inner))
		glDeleteLists (ctx->blades_inner, NRADII); */

	for (i = NSPINNERS;  --i >= 0; ) {
		spinnerstate *ss = &ctx->spinners[i];

		if (ss->colors)
			free (ss->colors);
		if (ss->bladeidx)
			free (ss->bladeidx);
	}
}


XSCREENSAVER_MODULE ("Lockward", lockward)

#endif /* USE_GL */

/*  vim:se ts=8 sts=8 sw=8:  */
