/* snafooz, "The" squishy foam puzzle. Trademark, registered or otherwise, of
 * Idea Lab (idealab.net). No permission secured from Idea Lab, despite my
 * best efforts; they simply didn't respond.
 */
/*
 * 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.
 *
 * This is the first major thing I've done in GL. As such, all errors are
 * mine, because I was too lazy to read up on how to do it right. Several
 * ideas (and bugfixes) came from tooling with lightlab
 * (http://www.jwz.org/lightlab/)
 */

/* TODO:
   - "solveme" mode
   X make it do something when solved
   - some wackiness here that I've not figured out
   - have it print out the solution on the console
   - interactive mode
   - texture on front side of the 2x2 area
   - have one, need to do it better, though
   - figure out logic for permutations, needed for larger shapes where "pick
   10 of 12" applies
   - use an array of pieces instead of piecemin/piecemax
   - populate the array using permutations
   - further optimise the piece drawing
   - collision detection on all the time, not just at landing
   - better use of pieces in brute force solveme mode
   - need a pseudo-random way of picking them
   - can't be random because that screws up the brute force
   - complex shapes
   - 3x1x1
   - 2x2x1
   - 2x2x2
   - 3x3x1 cross
   - 3x3x3 cross
   - flat shape data (i.e. return the pieces to their frame)
   - can use a 2x3 grid without having to define the frame
   - use linked list in snafu()
   - use collision detection in snafu()
   - multiple collision detection points
   - "show collisions" should light up the entire collision area
   - mode where shapes are wireframe and impact areas are lit
   - kill off more globals
   - change time-based trigger to mechanics-based
*/

/* xscreensaver glue: cribbed from other hacks */
#include <X11/Intrinsic.h>

#ifdef STANDALONE
# define PROGCLASS "snafooz"
# define HACK_INIT snafooz_init
# define HACK_DRAW snafooz_draw
# define HACK_RESHAPE snafooz_reshape
# define snafooz_opts xlockmore_opts
# define DEFAULTS "*delay: 20000\n"\
                  "*showFPS: False\n" \
                  "*DriftSpeed: 0.05\n" \

#include "xlockmore.h"
#else
# include "xlock.h"
#endif

#undef countof
#define countof(x) (sizeof((x))/sizeof((*x)))

#include <GL/glu.h>

/* Wow. Everyone is defining their own linked list where necessary. Ok, ME
 * TOO. Note, this isn't part of the stock xscreensaver init, but it needs to
 * be defined before the snafoozstruct below */
typedef struct _occupied {
    int owner;
    GLfloat x;
    GLfloat y;
    GLfloat z;
    struct _occupied *next;
} occupied_list;

/* This keeps track of what we've tried in brute-force solveme mode */
typedef struct _tried {
    int piece;
    int flipped;
    int rotated;
    int place;
    int ok;
    struct _tried *next;
} tried_list;

#include "rotator.h"

typedef struct {
    /* xscreensaver goop */
    GLXContext *glx_context;
    Window window;

    /* local goop */
    occupied_list *occupied;
    tried_list *tried;
    GLfloat lastcollision[3];
    int showcollision;
    time_t lastplaced;
    int busy;
    rotator *rot;
    double rotx, roty, rotz;
    XImage *texture;
    int puzzle;
    int minpiece;
    int maxpiece;
    int solved;
} snafoozstruct;

static snafoozstruct *snafooz = NULL;

static int showcollisions;
static int wireframe;
static int showframe;
static float driftspeed;
static int puzzle;

static XrmOptionDescRec opts[] = {
    { "-showcollisions", ".snafooz.showcollisions", XrmoptionNoArg, (caddr_t) "true" },
    { "-wireframe", ".snafooz.wireframe", XrmoptionNoArg, (caddr_t) "true" },
    { "-showframe", ".snafooz.showframe", XrmoptionNoArg, (caddr_t) "true" },
    { "-driftspeed", ".snafooz.driftspeed", XrmoptionSepArg, 0 },
    { "-puzzle", ".snafooz.puzzle", XrmoptionSepArg, 0 },
};

static argtype vars[] = {
    {(caddr_t *) &showcollisions, "showcollisions", "ShowCollisions", "False", t_Bool },
    {(caddr_t *) &wireframe, "wireframe", "Wireframe", "False", t_Bool },
    {(caddr_t *) &showframe, "showframe", "ShowFrame", "False", t_Bool },
    {(caddr_t *) &driftspeed, "driftspeed", "DriftSpeed", "0.05", t_Float },
    {(caddr_t *) &puzzle, "puzzle", "Puzzle", "0", t_Int },
};

ModeSpecOpt snafooz_opts = { countof(opts), opts, countof(vars), vars, NULL };

/* End of xscreensaver glue */

/* This is the texture that goes on the front face */
#include "xpm-ximage.h"
#include "../images/snafooz.xpm"

/*
 * This is supposed to be defined in math.h. However, gcc doesn't appear to
 * give you certain functions if you don't have certain defines enabled when
 * math.h is included. This prototype is probably a bad idea, but it makes my
 * code work, thanks.
 */
extern double round( double x );

/*
 * A single point, with integer coords. Could use Xpoint for this?
 */
typedef struct {
    int x;
    int y;
} pt;

/*
 * Snafooz piece outlines; collections of points defining the outer edge.
 */
/* YELLOW */
pt shape0[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 3, 2 }, { 3, 1 }, { 2, 1 },
    { 2, -1 }, { 3, -1 }, { 3, -2 }, { 2, -2 }, { 2, -3 },
    { 1, -3 }, { 1, -2 }, { -1, -2 }, { -1, -3 }, { -2, -3 },
    { -2, -1 }, { -3, -1 },
};

pt shape1[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 2, 2 }, { 2, 1 }, { 3, 1 },
    { 3, -1 }, { 2, -1 }, { 2, -3 }, { 1, -3 }, { 1, -2 },
    { -1, -2 }, { -1, -3 }, { -2, -3 }, { -2, -1 }, { -3, -1 },
};

pt shape2[] = {
    { -3, 1 }, { -3, 3 }, { -2, 3 }, { -2, 2 }, { -1, 2 },
    { -1, 3 }, { 1, 3 }, { 1, 2 }, { 2, 2 }, { 2, 3 },
    { 3, 3 }, { 3, 1 }, { 2, 1 }, { 2, -1 }, { 3, -1 },
    { 3, -3 }, { 2, -3 }, { 2, -2 }, { 1, -2 }, { 1, -3 },
    { 0, -3 }, { 0, -2 }, { -1, -2 }, { -1, -3 }, { -3, -3 },
    { -3, -2 }, { -2, -2 }, { -2, -1 }, { -3, -1 }, { -3, 0 },
    { -2, 0 }, { -2, 1 },
};

pt shape3[] = {
    { -3, 1 }, { -3, 2 }, { -1, 2 }, { -1, 3 }, { 0, 3 },
    { 0, 2 }, { 1, 2 }, { 1, 3 }, { 2, 3 }, { 2, 2 },
    { 3, 2 }, { 3, 1 }, { 2, 1 }, { 2, -1 }, { 3, -1 },
    { 3, -2 }, { 1, -2 }, { 1, -3 }, { 0, -3 }, { 0, -2 },
    { -1, -2 }, { -1, -3 }, { -2, -3 }, { -2, -2 }, { -3, -2 },
    { -3, -1 }, { -2, -1 }, { -2, 1 },
};

pt shape4[] = {
    { -3, 1 }, { -3, 3 }, { -1, 3 }, { -1, 2 }, { 1, 2 },
    { 1, 3 }, { 3, 3 }, { 3, 1 }, { 2, 1 }, { 2, -1 },
    { 3, -1 }, { 3, -3 }, { 2, -3 }, { 2, -2 }, { 1, -2 },
    { 1, -3 }, { 0, -3 }, { 0, -2 }, { -1, -2 }, { -1, -3 },
    { -3, -3 }, { -3, -1 }, { -2, -1 }, { -2, 1 },
};

pt shape5[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 2, 2 }, { 2, 1 }, { 3, 1 },
    { 3, 0 }, { 2, 0 }, { 2, -1 }, { 3, -1 }, { 3, -2 },
    { 1, -2 }, { 1, -3 }, { -1, -3 }, { -1, -2 }, { -2, -2 },
    { -2, -1, }, { -3, -1 },
};

/* GREEN */
pt shape6[] = {
    { -3, 1 }, { -3, 3 }, { -1, 3 }, { -1, 2 }, { 1, 2 },
    { 1, 3 }, { 3, 3 }, { 3, 1 }, { 2, 1 }, { 2, 0 },
    { 3, 0 }, { 3, -1 }, { 2, -1 }, { 2, -2 }, { 1, -2 },
    { 1, -3 }, { -1, -3 }, { -1, -2 }, { -2, -2 }, { -2, -1 },
    { -3, -1 }, { -3, 0 }, { -2, 0 }, { -2, 1 },
};

pt shape7[] = {
    { -3, 0 }, { -3, 1 }, { -2, 1 }, { -2, 3 }, { -1, 3 },
    { -1, 2 }, { 1, 2 }, { 1, 3 }, { 3, 3 }, { 3, 1 },
    { 2, 1 }, { 2, -1 }, { 3, -1 }, { 3, -3 }, { 1, -3 },
    { 1, -2 }, { -1, -2 }, { -1, -3 }, { -3, -3 }, { -3, -1 },
    { -2, -1 }, { -2, 0 },
};

pt shape8[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 3, 2 }, { 3, 1 }, { 2, 1 },
    { 2, -1 }, { 3, -1 }, { 3, -3 }, { 1, -3 }, { 1, -2 },
    { 0, -2 }, { 0, -3 }, { -1, -3 }, { -1, -2 }, { -2, -2 },
    { -2, -1 }, { -3, -1 },
};

pt shape9[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 3, 2 }, { 3, 1 }, { 2, 1 },
    { 2, -1 }, { 3, -1 }, { 3, -2 }, { 1, -2 }, { 1, -3 },
    { 0, -3 }, { 0, -2 }, { -1, -2 }, { -1, -3 }, { -2, -3 },
    { -2, -1, }, { -3, -1 },
};

pt shape10[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 0, 3 }, { 0, 2 }, { 1, 2 }, { 1, 3 }, { 3, 3 },
    { 3, 2 }, { 2, 2 }, { 2, 1 }, { 3, 1 }, { 3, -1 },
    { 2, -1 }, { 2, -2 }, { 1, -2 }, { 1, -3 }, { -1, -3 },
    { -1, -2 }, { -2, -2 }, { -2, -1 }, { -3, -1 },
};

pt shape11[] = {
    { -3, 1 }, { -3, 3 }, { -1, 3 }, { -1, 2 }, { 1, 2 },
    { 1, 3 }, { 2, 3 }, { 2, 2 }, { 3, 2 }, { 3, 1 },
    { 2, 1 }, { 2, -1 }, { 3, -1 }, { 3, -2 }, { 1, -2 },
    { 1, -3 }, { -1, -3 }, { -1, -2 }, { -3, -2 }, { -3, -1 },
    { -2, -1 }, { -2, 1 },
};

/* BLUE */
pt shape12[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 3, 2 }, { 3, 1 }, { 2, 1 },
    { 2, -1 }, { 3, -1 }, { 3, -3 }, { 2, -3 }, { 2, -2 },
    { 1, -2 }, { 1, -3 }, { -1, -3 }, { -1, -2 }, { -3, -2 },
    { -3, -1 }, { -2, -1 }, { -2, 0 }, { -3, 0 },
};

pt shape13[] = {
    { -3, 1 }, { -3, 3 }, { -1, 3 }, { -1, 2 }, { 1, 2 },
    { 1, 3 }, { 2, 3 }, { 2, 2 }, { 3, 2 }, { 3, 1 },
    { 2, 1 }, { 2, 0 }, { 3, 0 }, { 3, -1 }, { 2, -1 },
    { 2, -2 }, { 1, -2 }, { 1, -3 }, { -1, -3 }, { -1, -2 },
    { -2, -2 }, { -2, -3 }, { -3, -3 }, { -3, -1 }, { -2, -1 },
    { -2, 1 },
};

pt shape14[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 2, 2 }, { 2, 1 }, { 3, 1 },
    { 3, -1 }, { 2, -1 }, { 2, -3 }, { 1, -3 }, { 1, -2 },
    { -1, -2 }, { -1, -3 }, { -2, -3 }, { -2, -1 }, { -3, -1 },
};

/* actually the wrong orientation, but I'm too lazy to retype all this data */
pt shape15[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 3, 2 }, { 3, 1 }, { 2, 1 },
    { 2, -1 }, { 3, -1 }, { 3, -2 }, { 2, -2 }, { 2, -3 },
    { 1, -3 }, { 1, -2 }, { -1, -2 }, { -1, -3 }, { -2, -3 },
    { -2, -1 }, { -3, -1 },
};

pt shape16[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 3, 2 }, { 3, 1 }, { 2, 1 },
    { 2, -1 }, { 3, -1 }, { 3, -3 }, { 1, -3 }, { 1, -2 },
    { 0, -2 }, { 0, -3 }, { -1, -3 }, { -1, -2 }, { -2, -2 },
    { -2, -3 }, { -3, -3 }, { -3, -1 }, { -2, -1 }, { -2, 0 },
    { -3, 0 },
};

pt shape17[] = {
    { -3, 1 }, { -3, 3 }, { -1, 3 }, { -1, 2 }, { 1, 2 },
    { 1, 3 }, { 3, 3 }, { 3, 1 }, { 2, 1 }, { 2, -1 },
    { 3, -1 }, { 3, -2 }, { 1, -2 }, { 1, -3 }, { -1, -3 },
    { -1, -2 }, { -2, -2 }, { -2, -3 }, { -3, -3 }, { -3, -1 },
    { -2, -1 }, { -2, 1 },
};

/* RED */
/* orientation wrong again */
pt shape18[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 2, 2 }, { 2, 1 }, { 3, 1 },
    { 3, -1 }, { 2, -1 }, { 2, -3 }, { 1, -3 }, { 1, -2 },
    { -1, -2 }, { -1, -3 }, { -2, -3 }, { -2, -1 }, { -3, -1 },
};

/* ditto */
pt shape19[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 3, 2 }, { 3, 1 }, { 2, 1 },
    { 2, -1 }, { 3, -1 }, { 3, -3 }, { 1, -3 }, { 1, -2 },
    { -1, -2 }, { -1, -3 }, { -2, -3 }, { -2, -1 }, { -3, -1 },
};

pt shape20[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -3, 2 }, { -3, 3 },
    { -1, 3 }, { -1, 2 }, { 1, 2 }, { 1, 3 }, { 2, 3 },
    { 2, 1 }, { 3, 1 }, { 3, 0 }, { 2, 0 }, { 2, -1 },
    { 3, -1 }, { 3, -2 }, { 1, -2 }, { 1, -3 }, { -1, -3 },
    { -1, -2 }, { -2, -2 }, { -2, -1 }, { -3, -1 },
};

pt shape21[] = {
    { -3, 1 }, { -3, 3 }, { -1, 3 }, { -1, 2 }, { 1, 2 },
    { 1, 3 }, { 2, 3 }, { 2, 2 }, { 3, 2 }, { 3, 1 },
    { 2, 1 }, { 2, 0 }, { 3, 0 }, { 3, -1 }, { 2, -1 },
    { 2, -2 }, { 3, -2 }, { 3, -3 }, { 1, -3 }, { 1, -2 },
    { -1, -2 }, { -1, -3 }, { -3, -3 }, { -3, -1 }, { -2, -1 },
    { -2, 1 },
};

pt shape22[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 0, 3 }, { 0, 2 }, { 1, 2 }, { 1, 3 }, { 2, 3 },
    { 2, 1 }, { 3, 1 }, { 3, -1 }, { 2, -1 }, { 2, -3 },
    { 1, -3 }, { 1, -2 }, { -1, -2 }, { -1, -3 }, { -2, -3 },
    { -2, -2 }, { -3, -2 }, { -3, -1 }, { -2, -1 }, { -2, 0 },
    { -3, 0 },
};

pt shape23[] = {
    { -3, 1 }, { -3, 3 }, { -2, 3 }, { -2, 2 }, { -1, 2 },
    { -1, 3 }, { 1, 3 }, { 1, 2 }, { 3, 2 }, { 3, 1 },
    { 2, 1 }, { 2, -1 }, { 3, -1 }, { 3, -3 }, { 2, -3 },
    { 2, -2 }, { 1, -2 }, { 1, -3 }, { 0, -3 }, { 0, -2 },
    { -1, -2 }, { -1, -3 }, { -3, -3 }, { -3, -2 }, { -2, -2 },
    { -2, -1 }, { -3, -1 }, { -3, 0 }, { -2, 0 }, { -2, 1 },
};

/* PURPLE */
pt shape24[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 2, 2 }, { 2, 1 }, { 3, 1 },
    { 3, -1 }, { 2, -1 }, { 2, -2 }, { 3, -2 }, { 3, -3 },
    { 1, -3 }, { 1, -2 }, { -1, -2 }, { -1, -3 }, { -3, -3 },
    { -3, -2 }, { -2, -2 }, { -2, -1 }, { -3, -1 },
};

pt shape25[] = {
    { -3, 1 }, { -2, 1 }, { -2, 3 }, { -1, 3 }, { -1, 2 },
    { 1, 2 }, { 1, 3 }, { 2, 3 }, { 2, 2 }, { 3, 2 },
    { 3, 1 }, { 2, 1 }, { 2, -1 }, { 3, -1 }, { 3, -2 },
    { 1, -2 }, { 1, -3 }, { 0, -3 }, { 0, -2 }, { -2, -2 },
    { -2, 0 }, { -3, 0 },
};

pt shape26[] = {
    { -3, 1 }, { -3, 2 }, { -1, 2 }, { -1, 3 }, { 1, 3 },
    { 1, 2 }, { 3, 2 }, { 3, 1 }, { 2, 1 }, { 2, 0 },
    { 3, 0 }, { 3, -2 }, { 1, -2 }, { 1, -3 }, { -1, -3 },
    { -1, -2 }, { -3, -2 }, { -3, -1 }, { -2, -1 }, { -2, 1 },
};

pt shape27[] = {
    /* orientation wrong again */
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 2, 2 }, { 2, 1 }, { 3, 1 },
    { 3, -1 }, { 2, -1 }, { 2, -3 }, { 1, -3 }, { 1, -2 },
    { -1, -2 }, { -1, -3 }, { -2, -3 }, { -2, -1 }, { -3, -1 },
};

pt shape28[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 3, 2 }, { 3, 1 }, { 2, 1 },
    { 2, -1 }, { 3, -1 }, { 3, -3 }, { 1, -3 }, { 1, -2 },
    { -1, -2 }, { -1, -3 }, { -3, -3 }, { -3, -2 }, { -2, -2 },
    { -2, -1 }, { -3, -1 },
};

pt shape29[] = {
    { -3, 1 }, { -3, 3 }, { -1, 3 }, { -1, 2 }, { 1, 2 },
    { 1, 3 }, { 3, 3 }, { 3, 1 }, { 2, 1 }, { 2, -1 },
    { 3, -1 }, { 3, -3 }, { 1, -3 }, { 1, -2 }, { -1, -2 },
    { -1, -3 }, { -3, -3 }, { -3, 0 }, { -2, 0 }, { -1, 1 },
};

/* Orange */
pt shape30[] = {
    /* orientation wrong again */
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 2, 2 }, { 2, 1 }, { 3, 1 },
    { 3, -1 }, { 2, -1 }, { 2, -3 }, { 1, -3 }, { 1, -2 },
    { -1, -2 }, { -1, -3 }, { -2, -3 }, { -2, -1 }, { -3, -1 },
};

pt shape31[] = {
    { -3, 1 }, { -3, 2 }, { -1, 2 }, { -1, 3 }, { 1, 3 },
    { 1, 2 }, { 2, 2 }, { 2, 1 }, { 3, 1 }, { 3, -1 },
    { 2, -1 }, { 2, -2 }, { 3, -2 }, { 3, -3 }, { 1, -3 },
    { 1, -2 }, { -1, -2 }, { -1, -3 }, { -2, -3 }, { -2, -2 },
    { -3, -2 }, { -3, -1 }, { -2, -1 }, { -2, 1 },
};

pt shape32[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -1, 2 }, { -1, 3 },
    { 1, 3 }, { 1, 2 }, { 3, 2 }, { 3, 1 }, { 2, 1 },
    { 2, 0 }, { 3, 0 }, { 3, -1 }, { 2, -1 }, { 2, -2 },
    { 3, -2 }, { 3, -3 }, { 1, -3 }, { 1, -2 }, { -1, -2 },
    { -1, -3 }, { -3, -3 }, { -3, -2 }, { -2, -2 }, { -2, -1 },
    { -3, -1 },
};

pt shape33[] = {
    { -3, 1 }, { -3, 3 }, { -2, 3 }, { -2, 2 }, { -1, 2 },
    { -1, 3 }, { 1, 3 }, { 1, 2 }, { 2, 2 }, { 2, 1 },
    { 3, 1 }, { 3, -1 }, { 2, -1 }, { 2, -2 }, { 1, -2 },
    { 1, -3 }, { -1, -3 }, { -1, -2 }, { -3, -2 }, { -3, -1 },
    { -2, -1 }, { -2, 1 },
};

pt shape34[] = {
    { -3, 1 }, { -2, 1 }, { -2, 2 }, { -3, 2 }, { -3, 3 },
    { -1, 3 }, { -1, 2 }, { 1, 2 }, { 1, 3 }, { 3, 3 },
    { 3, 1 }, { 2, 1 }, { 2, -1 }, { 3, -1 }, { 3, -2 },
    { 2, -2 }, { 2, -3 }, { 1, -3 }, { 1, -2 }, { 0, -2 },
    { 0, -3 }, { -1, -3 }, { -1, -2 }, { -2, -2 }, { -2, -3 },
    { -3, -3 }, { -3, -1 }, { -2, -1 }, { -2, 0 }, { -3, 0 },
};

pt shape35[] = {
    { -3, 1 }, { -2, 1 }, { -2, 3 }, { -1, 3 }, { -1, 2 },
    { 1, 2 }, { 1, 3 }, { 2, 3 }, { 2, 2 }, { 3, 2 },
    { 3, 1 }, { 2, 1 }, { 2, -1 }, { 3, -1 }, { 3, -3 },
    { 1, -3 }, { 1, -2 }, { -1, -2 }, { -1, -3 }, { -2, -3 },
    { -2, -2 }, { -3, -2 }, { -3, -1 }, { -2, -1 }, { -2, 0 },
    { -3, 0 },
};

/*
 * All the pieces together
 */
pt *pieces[] = {
    shape0,
    shape1,
    shape2,
    shape3,
    shape4,
    shape5,
    shape6,
    shape7,
    shape8,
    shape9,
    shape10,
    shape11,
    shape12,
    shape13,
    shape14,
    shape15,
    shape16,
    shape17,
    shape18,
    shape19,
    shape20,
    shape21,
    shape22,
    shape23,
    shape24,
    shape25,
    shape26,
    shape27,
    shape28,
    shape29,
    shape30,
    shape31,
    shape32,
    shape33,
    shape34,
    shape35,
};

/* Size of each piece (number of points) */
#define countpts(x) (sizeof(x)/sizeof(pt))
int sizes[] = {
    countpts(shape0),
    countpts(shape1),
    countpts(shape2),
    countpts(shape3),
    countpts(shape4),
    countpts(shape5),
    countpts(shape6),
    countpts(shape7),
    countpts(shape8),
    countpts(shape9),
    countpts(shape10),
    countpts(shape11),
    countpts(shape12),
    countpts(shape13),
    countpts(shape14),
    countpts(shape15),
    countpts(shape16),
    countpts(shape17),
    countpts(shape18),
    countpts(shape19),
    countpts(shape20),
    countpts(shape21),
    countpts(shape22),
    countpts(shape23),
    countpts(shape24),
    countpts(shape25),
    countpts(shape26),
    countpts(shape27),
    countpts(shape28),
    countpts(shape29),
    countpts(shape30),
    countpts(shape31),
    countpts(shape32),
    countpts(shape33),
    countpts(shape34),
    countpts(shape35),
};

/*
 * colours
 */
typedef struct {
    int min;
    int max;
    GLfloat r;
    GLfloat g;
    GLfloat b;
} colour_range_t;

colour_range_t colour_range[] = {
    /*max,min,r,g,b */
    {  0,  5, 1, 1, 0 },
    {  6, 11, 0, 1, 0 },
    { 12, 17, 0, 0, 1 },
    { 18, 23, 1, 0, 0 },
    { 24, 29, 1, 0, 1 },
    { 30, 35, 1, 0.2, 0.2 },
};

#define YELLOW 0
#define GREEN 1
#define BLUE 2
#define RED 3
#define PURPLE 4
#define ORANGE 5

/*
 * A translation vector
 */
typedef struct {
    GLfloat x;
    GLfloat y;
    GLfloat z;
} translate_t;

/*
 * Rotations are generally going to be in degrees, hence ints rather than
 * floats
 */
typedef struct {
    int x;
    int y;
    int z;
} rotate_t;

/*
 * transform to get a piece from the origin (0,0,0) to a given position,
 * including rotation about the vector and back-to-front flipping if
 * required.
 */
typedef struct {
    int piece;
    time_t added;
    int rotated;
    int flipped;
    GLfloat offset;
    GLfloat direction;
    translate_t translate;
    rotate_t rotate;
} transform_t;

/*
 * transforms to make your basic Snafooz cube
 */
transform_t cube[] = {
    /* piece, added, rotated, flipped, offset, direction, translate, rotate */
    { -1, 0, 0, 0, 0, 0, { 0, 0,  2.5 }, {  90,   0,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, 0,  2.5 }, {   0,  90,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, 0,  2.5 }, {   0,   0,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, 0,  2.5 }, { -90,   0,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, 0,  2.5 }, {   0, -90,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, 0,  2.5 }, {   0,   180, 0  },},
};

transform_t box2x1x1[] = {
    { -1, 0, 0, 0, 0, 0, { 0, 0,  5 }, {  90,   0,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, 0,  5 }, { -90,   0,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, -2.5,  2.5 }, {   0,  90,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, -2.5,  2.5 }, {   0,   0,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, -2.5,  2.5 }, {   0, -90,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, -2.5,  2.5 }, {   0,   180, 0  },},
    { -1, 0, 0, 0, 0, 0, { 0, 2.5,  2.5 }, {   0,  90,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, 2.5,  2.5 }, {   0,   0,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, 2.5,  2.5 }, {   0, -90,   0  },},
    { -1, 0, 0, 0, 0, 0, { 0, 2.5,  2.5 }, {   0,   180, 0  },},
};

/* And this is the list of possible puzzles */
transform_t *puzzles[] = {
    cube,
    box2x1x1,
};

#define counttransforms(x) (sizeof(x)/sizeof(transform_t))
int puzzle_sizes[] = {
    counttransforms( cube ),
    counttransforms( box2x1x1 ),
};

/* This is how close the pieces are pushed together. When this is set to 1.0,
 * you can't see the joins except where GL flickers.
 */
GLfloat dist = 1.0;

/* Related: how far away a piece is when it's first placed or when we decide
   to remove it to the unused pile. Ideally I'd like shapes to drift out
   until they're offscreen, but I don't know how to test for that just
   yet. */
#define MAX_OFFSET 4

/* Lighting */
GLfloat l0_ambient[] = { 0.7, 0.7, 0.7, 1.0 };
GLfloat l0_specular[] = { 1, 1, 1, 1 };
GLfloat l0_diffuse[] = { 1, 1, 1, 1 };
GLfloat l0_position[] = { -4, 2, 5, 1 };


/* These are to avoid the presence of magic numbers in the code */
/* Which direction a face is facing in */
#define TOP_FACE    1
#define BOTTOM_FACE 2
#define LEFT_FACE   3
#define RIGHT_FACE  4
#define FRONT_FACE  5 /* unused */
#define BACK_FACE   6  /* unused */

/* Size of a snafooz piece */
#define SNAFOOZ_WIDTH  6
#define SNAFOOZ_HEIGHT 6

/*
 * Draw a single Snafooz piece
 */
pt *snafu( int shape )
{
    int i;
    pt *occupied = NULL; /* for collision detection */
    int lazy_ass_grid[ SNAFOOZ_HEIGHT ][ SNAFOOZ_WIDTH ]; /* size of snafooz piece */
    int x, y;
    int occ_ptr = 0;

    /* Init the grid */
    /* There's nothing this grid is doing that can't be done with the
     * occupied list and a little more intelligent code.
     */
    for ( y = 0; y < 6; y ++ ) {
        for ( x = 0; x < 6; x ++ ) {
            lazy_ass_grid[ x ][ y ] = ( x > 0 && x < 5 && y > 0 && y < 5 );
        }
    }


    /*
     * The maximum occupied array is 20 items, since we don't care about
     * the centrepiece.
     */
    if (( occupied = (pt *) calloc( sizeof( pt ), 20 )) == (pt *)NULL ) {
        /* well, we could explode here with -ENOMEM, but the thing
         * will still work without collision detection  */
        /* return NULL; */
    }


    /* Colour setup */
    for ( i = 0; i < sizeof( colour_range ) / sizeof( colour_range_t ); i++ ) {
        if ( shape >= colour_range[ i ].min && shape <= colour_range[ i ].max ) {
            GLfloat colour[4];
            colour[ 0 ] = colour_range[ i ].r;
            colour[ 1 ] = colour_range[ i ].g;
            colour[ 2 ] = colour_range[ i ].b;
            colour[ 3 ] = 1.0;
            glMaterialfv( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE, colour );
            break;
        }
    }

    glFrontFace( GL_CCW );

    /* front - winds CCW */
    glEnable( GL_TEXTURE_2D );
    glBegin( GL_QUADS );
    glNormal3f( 0, 0, 1 );
    glTexCoord2f( 1, 1 ); glVertex3f(  2, -2,  0.5 );
    glTexCoord2f( 0, 1 ); glVertex3f(  2,  2,  0.5 );
    glTexCoord2f( 0, 0 ); glVertex3f( -2,  2,  0.5 );
    glTexCoord2f( 1, 0 ); glVertex3f( -2, -2,  0.5 );
    glEnd();
    glDisable( GL_TEXTURE_2D );

    /* back - winds CW */
    /* Still not 100% clear on why that is, but it works, and I'm not
     * going to argue. */
    glBegin( GL_QUADS );
    glNormal3f( 0, 0, -1 );
    glVertex3f( -2, -2, -0.5 );
    glVertex3f( -2,  2, -0.5 );
    glVertex3f(  2,  2, -0.5 );
    glVertex3f(  2, -2, -0.5 );
    glEnd();

    /* edge pieces.
     * Basically, trace the outer edge of the shape, then figure out if
     * "cover" pieces are needed for the front and back of what we've
     * just outlined.
     * There's some overlap here, because dammit it works and I've not
     * fixed it. Some parts do get drawn a few more times than is
     * strictly necessary, though.
     */
    for ( i = 0; i < sizes[shape]; i++ ) {
        int x1, y1, x2, y2, x3, y3, x4, y4;
        int side = 0;

        /* First set of coords is easy */
        x2 = pieces[shape][i].x;
        y2 = pieces[shape][i].y;

        /* Figure out the second set of coords */
        if ( i == 0 ) {
            x1 = pieces[shape][ sizes[shape]-1 ].x;
            y1 = pieces[shape][ sizes[shape]-1 ].y;
        } else {
            x1 = pieces[shape][ i - 1 ].x;
            y1 = pieces[shape][ i - 1 ].y;
        }

        /* and the third */
        if ( i < sizes[shape] - 1 ) {
            x3 = pieces[shape][ i + 1 ].x;
            y3 = pieces[shape][ i + 1 ].y;
        } else {
            x3 = pieces[shape][ 0 ].x;
            y3 = pieces[shape][ 0 ].y;
        }

        /* Figure out which way this faces */
        if ( x1 < x2 ) {
            side = TOP_FACE;
        } else if ( x1 > x2 ) {
            side = BOTTOM_FACE;
        } else {
            if ( y1 < y2 ) {
                side = LEFT_FACE;
            } else {
                side = RIGHT_FACE;
            }
        }

        /* Draw the damn thing */
        glBegin( GL_QUADS );
        glNormal3f( side == LEFT_FACE ? -1 : ( side == RIGHT_FACE ? 1 : 0 ),
                    side == BOTTOM_FACE ? -1 : ( side == TOP_FACE ? 1 : 0 ),
                    0 );
        glVertex3f( x2, y2,  0.5 );
        glVertex3f( x2, y2, -0.5 );
        glVertex3f( x1, y1, -0.5 );
        glVertex3f( x1, y1,  0.5 );
        glEnd();

        /* Figure out if we're on a concave/convex line segment */
        if ( side == TOP_FACE ) {
            if ( y3 > y2 ) { /* _| */
                continue;
            }
        } else if ( side == BOTTOM_FACE ) {
            if ( y3 < y2 ) { /* |~ */
                continue;
            }
        } else if ( side == LEFT_FACE ) {
            if ( x3 < x2 ) { /* ~| */
                continue;
            }
        } else {
            if ( x3 > x2 ) { /* |_ */
                continue;
            }
        }

        /* Don't bother if the piece is entirely within the 2x2
         * centrepiece, i.e. already drawn */
        if ( x1 >= -2 && x2 >= -2 && x3 >= -2 &&
             x2 <= 2 && x2 <= 2 && x3 <= 2 &&
             y1 >= -2 && y2 >= -2 && y3 >= -2 &&
             y1 <= 2 && y2 <= 2 && y3 <= 2 ) {
            continue;
        }

        /* If we made it this far, we've got more drawin' to do. */

        /* Figure out the fourth corner */
        if ( side == TOP_FACE || side == BOTTOM_FACE ) {
            x4 = x1;
            y4 = y3;
        } else {
            x4 = x3;
            y4 = y1;
        }

        /* Need to figure out what squares this occupies */
        {
            int bottom_left_x = MIN( x1, MIN( x2, x3 ));
            int bottom_left_y = MIN( y1, MIN( y2, y3 ));
            int top_right_x = MAX( x1, MAX( x2, x3 ));
            int top_right_y = MAX( y1, MAX( y2, y3 ));
            int xi, yi;

            for ( xi = bottom_left_x; xi < top_right_x; xi++ ) {
                for ( yi = bottom_left_y; yi < top_right_y; yi++ ) {
                    /* Hey, lazy_ass_grid has a purpose!
                       stops some overlap, yay */
                    if ( lazy_ass_grid[ xi + 3 ][ yi + 3 ] ) {
                        continue;
                    } else {
                        lazy_ass_grid[ xi + 3 ][ yi + 3 ] = 1;
                    }
                }
            }
        }

        glBegin( GL_QUADS );
        glNormal3f( 0, 0, 1 ); /* Front */
        glVertex3f( x4, y4, 0.5 );
        glVertex3f( x3, y3, 0.5 );
        glVertex3f( x2, y2, 0.5 );
        glVertex3f( x1, y1, 0.5 );
        glEnd();

        glBegin( GL_QUADS );
        glNormal3f( 0, 0, -1 ); /* Back */
        glVertex3f( x1, y1, -0.5 );
        glVertex3f( x2, y2, -0.5 );
        glVertex3f( x3, y3, -0.5 );
        glVertex3f( x4, y4, -0.5 );
        glEnd();
    }


    /* We can build the occupancy list now */
    for ( y = 0; y < SNAFOOZ_HEIGHT; y ++ ) {
        for ( x = 0; x < SNAFOOZ_WIDTH; x ++ ) {
            if ( x > 0 && x < 5 && y > 0 && y < 5 ) {
                continue;
            }
            if ( lazy_ass_grid[ x ][ y ] ) {
                occupied[ occ_ptr ].x = x - 3;
                occupied[ occ_ptr ].y = y - 3;
                occ_ptr++;
            }
        }
    }

    /* these are 'can't happen' values */
    occupied[ occ_ptr ].x = 0;
    occupied[ occ_ptr ].y = 0;

    return occupied;
}

/* Remove a piece from the tried list */
void print_tried( tried_list *tried )
{
    while( tried ) {
        printf( "%d[ %d ] ", tried->place, tried->piece );
        tried = tried->next;
    }
}

tried_list *remove_tried( tried_list *tried, int piece )
{
    tried_list *prev = NULL, *current = tried, *next = NULL;

    while( current ) {
        next = current->next;
        if ( current->piece == piece ) {
            if ( prev != NULL ) {
                prev->next = next;
            } else {
                /* head of list */
                tried = next;
            }
            free( current );
            print_tried( tried );
            printf( "REMOVED %d\n", piece );
        } else {
            prev = current;
        }
        current = next;
    }

    return tried;
}

/* Mark a piece on the tried list as NOT OK (collided) */
tried_list *collide_tried( tried_list *tried, int piece )
{
    tried_list *current = tried;

    while( current ) {
        if ( current->piece == piece ) {
            current->ok = 0;
            printf( "Collided: %d\n", piece );
        }
        current = current->next;
    }

    return tried;
}

/* check if a piece is in use in the current puzzle or not. return -1 if it's
 * ok or return the index in the current puzzle if
 * it's realy in use
 */
int used( ModeInfo *mi, int piece )
{
    snafoozstruct *gp = &snafooz[MI_SCREEN(mi)];
    int puzzle = gp->puzzle;
    int i;

    for ( i = 0; i < puzzle_sizes[ puzzle ]; i++ ) {
        if ( puzzles[puzzle][i].piece == piece ) {
            return i;
        }
    }

    return -1;
}

/*
 * pick a random piece and throw it into the puzzle
 */
void pickpiece( ModeInfo *mi )
{
    snafoozstruct *gp = &snafooz[MI_SCREEN(mi)];
    int piece = -1, p;
    int place = -1;
    int flipped = 0;
    int rotated = 0;
    int puzzle = gp->puzzle;
    int min = gp->minpiece;
    int max = gp->maxpiece;

    while( piece == -1 ) {
        if ( gp->tried == NULL || gp->tried->ok ) {
            /* The placed pieces stack is empty, or the last
               piece we placed is ok. Pick a piece and somewhere
               to put it. */
            for ( p = min; p <= max; p++ ) {
                if ( used( mi, p ) == -1 ) {
                    piece = p;
                    break;
                }
            }

            /* pick a spot */
            for ( p = 0; p < puzzle_sizes[ puzzle ]; p++ ) {
                if ( puzzles[ puzzle ][ p ].piece == -1 ) {
                    place = p;
                    break;
                }
            }

            /* If we can't pick a piece, they're all used. If we
             * can't find a place, they're all occupied. Thus,
             * we've solved the puzzle. */
            if ( place == -1 && place == -1 ) {
                gp->lastplaced = time( NULL );
                gp->solved = 1;
                printf( "Apparently, this is solved now.\n" );
                return;
            }

            printf( "PICK: piece %d place %d\n", piece, place );
        } else {
            /* Check the last piece we tried */
            piece = gp->tried->piece;
            place = gp->tried->place;
            flipped = gp->tried->flipped;
            rotated = gp->tried->rotated;

            printf( "LIST: last try piece %d place %d flip %d rotate %d\n",
                    piece, place, flipped, rotated );

            /* Increase rotation */
            rotated += 90;
            printf( "LIST: rotated to %d\n", rotated );

            /* If we've come full circle, try a different place */
            if ( rotated > 270 ) {
                rotated = 0;

                /* Find the next free slot */
                while( place++ < puzzle_sizes[ puzzle ]) {
                    if ( puzzles[puzzle][place].piece == -1 ) {
                        /* fake out the last thing we
                           tried */
                        break;
                    }
                }

                printf( "LIST: moved to place %d\n", place );

                /* If we've tried all places, flip over the
                   piece and try again. */
                if ( place > puzzle_sizes[puzzle] ) {
                    place = -1;
                    /* Find the next free slot */
                    while( place++ < puzzle_sizes[puzzle]) {
                        if ( puzzles[puzzle][place].piece == -1 ) {
                            break;
                        }
                    }

                    /* XXX */
                    if ( place == puzzle_sizes[ puzzle ] ) {
                        printf( "ERROR: nowhere to go\n" );
                        exit( 1 );
                    }

                    flipped++;

                    printf( "LIST: flipped to %d\n", flipped );
                    if ( flipped > 1 ) {
                        flipped = 0;
                        gp->tried = remove_tried( gp->tried, piece );
                        printf( "LIST: backtrack\n" );
                        if ( gp->tried ) {
                            int k;
                            printf( "Piece %d\n", gp->tried->piece );
                            gp->tried = collide_tried( gp->tried, piece );
                            for ( k = 0; k < puzzle_sizes[ puzzle ]; k++ ) {
                                if ( puzzles[ puzzle ][k].piece == piece ) {
                                    puzzles[puzzle][k].direction = driftspeed;
                                    puzzles[puzzle][k].offset = 1;
                                    break;
                                }
                            }
                            return;
                        } else {
                            printf( "Can't do anything with %d\n",
                                    piece );
                        }
                        piece = -1;
                    }
                }
            }

            if ( piece != -1 ) {
                printf( "LIST: trying piece %d place %d flip %d rotate %d\n",
                        piece, place, flipped, rotated );
            } else {
                printf( "LIST: trying again\n" );
            }

        }
    }

    if ( piece == -1 ) {
        /* might have solved the puzzle, dood */
        printf( "Can't find a piece to use\n" );
        exit( 1 );
    }

    if ( place == -1 ) {
        printf( "can't find anywhere to put this piece!\n" );
        exit( 1 );
    }

    gp->lastplaced = time( NULL );
    gp->busy = 1;

    if (used( mi, piece ) >= 0 ) {
        return; /* letting the piece exit the puzzle */
    } else {
        if ( puzzles[ puzzle ][ place ].piece == -1 ) {
            tried_list *new;

            puzzles[ puzzle ][ place ].piece = piece;
            puzzles[ puzzle ][ place ].rotated = rotated;
            puzzles[ puzzle ][ place ].offset = MAX_OFFSET;
            puzzles[ puzzle ][ place ].direction = -driftspeed;  /* drift in  */
            puzzles[ puzzle ][ place ].flipped = flipped;
            puzzles[ puzzle ][ place ].added = gp->lastplaced;

            if (( new = calloc( sizeof( tried_list ), 1 )) == NULL ) {
                /* XXX ENOMEM */
                exit( 1 );
            } else {
                /* nuke the old one */
                gp->tried = remove_tried( gp->tried, piece );
                new->piece = piece;
                new->place = place;
                new->rotated = rotated;
                new->flipped = flipped;
                new->next = gp->tried;
                new->ok = 1;
                gp->tried = new;
                print_tried( gp->tried );
                printf( "ADDED %d in %d\n", piece, place );
            }

        }
    }
}

/* check for a collision. returns the owner of the location if there's a
 * collision, or -1 for no collision */
int check_collision( occupied_list *occupied, GLfloat x, GLfloat y, GLfloat z )
{
    occupied_list *luke = occupied;

    while( luke ) {
        if ( luke->x == x && luke->y == y && luke->z == z ) {
            return luke->owner;
        } else {
            luke = luke->next;
        }
    }
    return -1;
}

/* Add an item to the occupied list. */
occupied_list *add_occupied( occupied_list *occupied, int owner, GLfloat x, GLfloat y, GLfloat z )
{
    occupied_list *new = calloc( sizeof( occupied_list ), 1 );
    occupied_list *luke = occupied;

    if ( new != (occupied_list *)NULL ) {

        new->owner = owner;
        new->x = x;
        new->y = y;
        new->z = z;

        if ( occupied ) {
            while( luke->next ) {
                luke = luke->next;
            }
            luke->next = new;
        } else {
            occupied = new;
        }
    } /* else ENOMEM XXX */

    return occupied;
}

/* Remove a piece from the occupied list */
occupied_list *remove_occupied( occupied_list *occupied, int piece )
{
    occupied_list *prev = NULL, *current = occupied, *next = NULL;

    while( current ) {
        next = current->next;
        if ( current->owner == piece ) {
            if ( prev != NULL ) {
                prev->next = next;
            } else {
                /* head of list */
                occupied = next;
            }
            free( current );
        } else {
            prev = current;
        }
        current = next;
    }

    return occupied;
}

/* Erase the occupied list */
occupied_list *delete_occupied( occupied_list *occupied )
{
    occupied_list *luke = occupied;

    while( luke ) {
        luke = luke->next;
        free( occupied );
        occupied = luke;
    }

    return NULL;
}

/* Draw an impact area  XXX make this a full 1x1 cube */
void impactarea( GLfloat x, GLfloat y, GLfloat z )
{
    glPushMatrix();
    glLoadIdentity();
    glDisable( GL_CULL_FACE );
    glDisable( GL_DEPTH_TEST );
    glDisable( GL_LIGHTING );
    glColor3f( 0, 255, 0 );
    glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
    glBegin( GL_QUADS );
    glVertex3f( x - 0.5, y - 0.5, z - 0.5);
    glVertex3f( x - 0.5, y + 0.5, z - 0.5);
    glVertex3f( x + 0.5, y + 0.5, z + 0.5);
    glVertex3f( x + 0.5, y - 0.5, z + 0.5);
    glEnd();
    glEnable( GL_CULL_FACE );
    glEnable( GL_DEPTH_TEST );
    glEnable( GL_LIGHTING );
    glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
    glPopMatrix();
}

/* The main drawing function */
void snafooz_draw( ModeInfo *mi )
{
    Display *display = MI_DISPLAY( mi );
    Window window = MI_WINDOW( mi );
    snafoozstruct *gp;
    pt *this_one_occupied = NULL;
    GLfloat x,y,z;
    int i, j;
    int owner;
    int puzzle;

    /* don't bother if the global struct isn't set */
    if ( snafooz == NULL )
        return;

    gp = &snafooz[MI_SCREEN(mi)];

    MI_IS_DRAWN(mi) = True;

    /* Also don't bother if glx_context isn't set */
    if ( !gp->glx_context)
        return;

    glXMakeCurrent( display, window, *(gp->glx_context));

    glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );

    /* Apply rotations to the whole view */
    /* I should probably be doing smart things with the MODEL matrix, but
       this works. Basically, I run into gimbal lock problems. At least,
       I think that's what's happening. So screw it. */
    glMatrixMode( GL_PROJECTION );
    glPushMatrix();
    get_rotation( gp->rot, &gp->rotx, &gp->roty, &gp->rotz, True );
    glRotatef( gp->rotx * 360, 1, 0, 0 );
    glRotatef( gp->roty * 360, 0, 1, 0 );
    glRotatef( gp->rotz * 360, 0, 0, 1 );

    /* Show off the solution */
    if ( gp->solved ) {
        dist += ( driftspeed / 4 ) * gp->solved;
        if ( dist < 1 || dist > 2 ) {
            gp->solved = -(gp->solved);
        }
    }


    glMatrixMode( GL_MODELVIEW );

    puzzle = gp->puzzle;

    if ( showframe ) {
        for ( i = 0; i < puzzle_sizes[ puzzle ]; i++ ) {
            glLoadIdentity();
            /* Rotate to the correct orientation */
            glRotatef( puzzles[ puzzle ][ i ].rotate.x, 1, 0, 0 );
            glRotatef( puzzles[ puzzle ][ i ].rotate.y, 0, 1, 0 );
            glRotatef( puzzles[ puzzle ][ i ].rotate.z, 0, 0, 1 );

            /* Push the piece out to the correct location. */
            glTranslatef( puzzles[ puzzle ][ i ].translate.x,
                          puzzles[ puzzle ][ i ].translate.y,
                          puzzles[ puzzle ][ i ].translate.z );

            glDisable( GL_LIGHTING );
            glDisable( GL_CULL_FACE );
            if ( !wireframe )
                glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
            glColor3f( 1, 1, 1 );
            glBegin( GL_QUADS );
            glVertex3f( -3, -3, 0.5 );
            glVertex3f( -3, 3, 0.5 );
            glVertex3f( 3, 3, 0.5 );
            glVertex3f( 3, -3, 0.5 );
            glEnd();
            if ( !wireframe )
                glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
            glEnable( GL_CULL_FACE );
            glEnable( GL_LIGHTING );
        }
    }

    /* Figure out where to place the bits in use */
    for ( i = 0; i < puzzle_sizes[puzzle]; i++ ) {
        if ( puzzles[ puzzle ][ i ].piece != -1 ) {
            GLfloat matrix[16];
            GLfloat distance = dist * puzzles[ puzzle ][ i ].offset;

            glLoadIdentity();

            /* Rotate the piece to the correct orientation */
            glRotatef( puzzles[ puzzle ][ i ].rotate.x, 1, 0, 0 );
            glRotatef( puzzles[ puzzle ][ i ].rotate.y, 0, 1, 0 );
            glRotatef( puzzles[ puzzle ][ i ].rotate.z, 0, 0, 1 );

            /* Push the piece out to the correct location. We
             * only float in on the [translated, rotated] z axis,
             * because otherwise you get weird angular
             * drift which looks naff. */
            glTranslatef( puzzles[ puzzle ][ i ].translate.x /** distance*/,
                          puzzles[ puzzle ][ i ].translate.y /** distance*/,
                          puzzles[ puzzle ][ i ].translate.z * distance );

            /* Final Rotations:
             * One using the z of the translate vector as a
             * normal, which I sort of understand, but not
             * really.
             */
            glRotatef( puzzles[ puzzle ][ i ].rotated,
                       0, 0,
                       puzzles[ puzzle ][ i ].translate.z );

            /* And one to do the 180 flip if required */
            if ( puzzles[ puzzle ][ i ].flipped ) {
                glRotatef( 180, 1, 1, 0 );
            }

            this_one_occupied = snafu( puzzles[ puzzle ][ i ].piece );

            /* get the current matrix */
            glGetFloatv( GL_MODELVIEW_MATRIX, &matrix[ 0 ] );

            /* if the piece has landed, update the occupied
               matrix and do collision detection */
            if ( this_one_occupied != NULL && puzzles[ puzzle ][ i ].direction == 0 ) {
                GLfloat mx, my, mz;
                for( j = 0; j < 20; j++ ) {
                    x = this_one_occupied[ j ].x + 0.5;
                    y = this_one_occupied[ j ].y + 0.5;
                    z = 0;

                    /* breakout value */
                    if ( this_one_occupied[j].x == 0 && this_one_occupied[j].y == 0 )
                        break;

                    /* transpose data to correct location
                     * and store in occupied, check for
                     * abuse */
                    mx = x * matrix[0] +
                        y * matrix[4] +
                        z * matrix[8] +
                        matrix[12];

                    my = x * matrix[1] +
                        y * matrix[5] +
                        z * matrix[9] +
                        matrix[13];

                    mz = x * matrix[2] +
                        y * matrix[6] +
                        z * matrix[10] +
                        matrix[14];

                    x = mx; y = my; z = mz;


                    /* trim away excess floating point
                       bits */
                    x = round( x * 10 ) / 10;
                    y = round( y * 10 ) / 10;
                    z = round( z * 10 ) / 10;

                    /* make this an option, I think */
                    /* impactarea( x, y, z ); */

                    /* Check for collisions */
                    owner = check_collision( gp->occupied, x, y, z );
                    if (  owner != -1 && owner != puzzles[ puzzle ][ i ].piece ) {
                        /* make sure it's the
                           johnny-come-lately piece that
                           gets kicked */
                        int k;
                        for ( k = 0; k < puzzle_sizes[puzzle]; k ++ ) {
                            if ( puzzles[ puzzle ][ k ].piece == owner && puzzles[ puzzle ][ k ].added > puzzles[ puzzle ][ i ].added ) {
                                puzzles[ puzzle ][k].direction = driftspeed;
                                puzzles[ puzzle ][k].offset = 1;
                                gp->tried = collide_tried( gp->tried, puzzles[ puzzle ][k].piece );
                                owner = -1; /* flag */
                                break;
                            }
                        }
                        if ( owner != -1 ) {
                            puzzles[ puzzle ][i].direction = driftspeed;
                            gp->tried = collide_tried( gp->tried, puzzles[ puzzle ][i].piece );
                        }
                        gp->lastcollision[0] = x;
                        gp->lastcollision[1] = y;
                        gp->lastcollision[2] = z;
                        gp->showcollision = 1;
                        break; /* no point in checking more */
                    } else {
                        if ( owner != puzzles[ puzzle ][ i ].piece ) {
                            gp->occupied = add_occupied( gp->occupied, puzzles[ puzzle ][ i ].piece, x, y, z );
                            gp->busy = 0;
                        }
                    }
                }
                free( this_one_occupied );
            }

            /* drift the pieces in and out of place */
            if ( puzzles[ puzzle ][ i ].direction ) {
                puzzles[ puzzle ][ i ].offset += puzzles[ puzzle ][ i ].direction;
                if ( puzzles[ puzzle ][ i ].offset <= 1 ) {
                    puzzles[ puzzle ][ i ].offset = 1;
                    puzzles[ puzzle ][ i ].direction = 0;
                } else if ( puzzles[ puzzle ][ i ].offset > MAX_OFFSET ) {
                    /* remove the piece */
                    gp->occupied = remove_occupied( gp->occupied, puzzles[ puzzle ][ i ].piece );
                    puzzles[ puzzle ][ i ].direction = 0;
                    puzzles[ puzzle ][ i ].piece = -1;
                    gp->showcollision = 0;
                    gp->busy = 0;
                }
            }
        }
    }

    /* draw last collision point */
    if ( showcollisions ) {
        if ( gp->showcollision ) {
            /* XXX make this a full cube */
            /* XXX show ALL collision points */
            glLoadIdentity();
            glDisable( GL_CULL_FACE );
            glDisable( GL_DEPTH_TEST );
            glDisable( GL_LIGHTING );
            glColor3f( 1, 1, 1 );
            if ( wireframe ) {
                glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );
            }
            x = gp->lastcollision[0];
            y = gp->lastcollision[ 1 ];
            z = gp->lastcollision[2];
            glBegin( GL_QUADS );
            glVertex3f( x - 0.5, y - 0.5, z - 0.5);
            glVertex3f( x - 0.5, y + 0.5, z - 0.5);
            glVertex3f( x + 0.5, y + 0.5, z + 0.5);
            glVertex3f( x + 0.5, y - 0.5, z + 0.5);
            glEnd();
            glEnable( GL_CULL_FACE );
            glEnable( GL_DEPTH_TEST );
            glEnable( GL_LIGHTING );
            if ( wireframe ) {
                glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );
            }
        }
    }

    glMatrixMode( GL_PROJECTION );
    glPopMatrix();

    /* Display the unused pieces elsewhere */
    glMatrixMode( GL_MODELVIEW );

    for ( i = gp->minpiece; i <= gp->maxpiece; i++ ) {
        if ( used( mi, i ) < 0 ) {
            glLoadIdentity();

            /* Shift */
            glTranslatef( 10 * (( i % 6 ) - 2.5 ), -18 + 8 * ( i/6 ), -20 );

            /* it looks crap when all the pieces are exactly in
             * step, so do some arbitrary frobbing here */

            glRotatef( fmod(( gp->rotx * 360 + i * 60 ), 360 ), 1, 0, 0 );
            glRotatef( fmod(( gp->roty * 360 + i * 60 ), 360 ), 0, 1, 0 );
            glRotatef( fmod(( gp->rotz * 360 + i * 60 ), 360 ), 0, 0, 1 );

            /* now draw the damn thing */
            this_one_occupied = snafu( i );
            /* We don't actually use the occupied data here */
            if ( this_one_occupied != NULL )
                free( this_one_occupied );
        }
    }

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

    /* pick a piece if we've not done so in the last five seconds */
    if ( gp->lastplaced + 5 < time( NULL )) {
        gp->busy = 0;
    }

    if ( !gp->busy ) {
        pickpiece( mi );
    }

    glFlush();

    glXSwapBuffers( display, window );
}

void snafooz_reshape( ModeInfo *mi, int w, int h )
{
    GLdouble aspect = w/h;

    glViewport( 0, 0, (GLint)w, (GLint)h );
    glMatrixMode( GL_PROJECTION );
    glLoadIdentity();
    glFrustum( -aspect, aspect, -1, 1, 1, 9999 );
    glTranslatef( 0, 2, -15 );
    glLineWidth(1);
    glPointSize(1);

    glClear( GL_COLOR_BUFFER_BIT );
}

/*
 * One-shot init function (set up lights and such)
 */
static void pinit( ModeInfo *mi )
{
    snafoozstruct *gp;

    gp = &snafooz[ MI_SCREEN( mi )];

    glEnable( GL_DEPTH_TEST );
    glEnable( GL_CULL_FACE );
    glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );

    glLightfv( GL_LIGHT0, GL_POSITION, l0_position );
    glLightfv( GL_LIGHT0, GL_AMBIENT, l0_ambient );
    glLightfv( GL_LIGHT0, GL_DIFFUSE, l0_diffuse );
    glLightfv( GL_LIGHT0, GL_SPECULAR, l0_specular );
    glEnable( GL_LIGHTING );
    glEnable( GL_LIGHT0 );
    glEnable( GL_TEXTURE_2D );
    glShadeModel( GL_SMOOTH );

    /* not sure this is right here, but hey */
    gluLookAt(0, 0, 30, /* eye */
              0, 0, 0,  /* center */
              0, 1, 0 );  /* up */


    /* init vars */
    gp->occupied = NULL;
    gp->lastplaced = 0;

    /* Texture */
    gp->texture = xpm_to_ximage( mi->dpy, mi->xgwa.visual, mi->xgwa.colormap, snafooz_xpm );

    glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST );
    glTexGeni( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
    glTexGeni( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR );
    glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE );

    glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA,
                  gp->texture->width /* width */,
                  gp->texture->width/* height */,
                  0/* border */,
                  GL_RGBA, GL_UNSIGNED_INT_8_8_8_8_REV,
                  gp->texture->data );

    gp->puzzle = puzzle; /* XXX random pick? */
    gp->minpiece = colour_range[YELLOW].min;
    gp->maxpiece = colour_range[ORANGE].max;
    gp->solved = 0;
}

void snafooz_init( ModeInfo *mi )
{
    snafoozstruct *gp;

    if ( snafooz == NULL ) {
        if (( snafooz = ( snafoozstruct *) calloc( MI_NUM_SCREENS(mi),
                                                   sizeof( snafoozstruct ))) == NULL )
            return;
    }

    gp = &snafooz[ MI_SCREEN( mi )];

    if ((gp->glx_context = init_GL( mi )) != NULL ) {
        snafooz_reshape( mi, MI_WIDTH( mi ), MI_HEIGHT( mi ));
        glDrawBuffer( GL_BACK );
        pinit( mi );

        {
            double rot_speed = 0.5;
            gp->rot = make_rotator( rot_speed, rot_speed, rot_speed, 1, 0, True );
        }

    } else {
        MI_CLEARWINDOW( mi );
    }
}

