#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <malloc.h>
#include <time.h>
#include <math.h>
#include "tk.h"
#include "trackbal.h"

#define WIDTH 4
#define HEIGHT 5
#define PIECES 10
#define OFFSETX -2
#define OFFSETY -2.5
#define OFFSETZ -0.5

typedef char Config[HEIGHT][WIDTH];

struct puzzle {
    struct puzzle *backptr;
    struct puzzle *solnptr;
    Config pieces;
    struct puzzle *next;
    unsigned hashvalue;
};

#define HASHSIZE 10691

struct puzzlelist {
    struct puzzle *puzzle;
    struct puzzlelist *next;
};

static char convert[PIECES+1] = {0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 4};

static unsigned char colors[PIECES+1][3] = {
  {  0,  0,  0},
  {255,255,127},
  {255,255,127},
  {255,255,127},
  {255,255,127},
  {255,127,255},
  {255,127,255},
  {255,127,255},
  {255,127,255},
  {255,127,127},
  {255,255,255},
};

static struct puzzle *hashtable[HASHSIZE];
static struct puzzle *startPuzzle;
static struct puzzlelist *puzzles;
static struct puzzlelist *lastentry;

#define MOVE_SPEED 0.2
static unsigned char movingPiece;
static float move_x,move_y;
static float curquat[4];
static int doubleBuffer=1;
static int depth=1;

static char xsize[PIECES+1] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2 };
static char ysize[PIECES+1] = { 0, 1, 1, 1, 1, 2, 2, 2, 2, 1, 2 };
static float zsize[PIECES+1] = { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.6 };
static GLuint listbase;

static Config startConfig = {
    {  8, 10, 10,  7 },
    {  8, 10, 10,  7 },
    {  6,  9,  9,  5 },
    {  6,  4,  3,  5 },
    {  2,  0,  0,  1 }
};

static Config thePuzzle = {
    {  8, 10, 10,  7 },
    {  8, 10, 10,  7 },
    {  6,  9,  9,  5 },
    {  6,  4,  3,  5 },
    {  2,  0,  0,  1 }
};

static int xadds[4]={-1, 0, 1, 0};
static int yadds[4]={ 0,-1, 0, 1};

static long W = 400, H = 300;
static GLint viewport[4];

#define srandom srand
#define random() (rand() >> 2)

unsigned hash(Config config)
{
    int i,j,value;

    value=0;
    for (i=0; i<HEIGHT; i++) {
	for (j=0; j<WIDTH; j++) {
	    value=value+convert[config[i][j]];
	    value*=6;
	}
    }
    return(value);
}

int solution(Config config)
{
    if (config[4][1]==10 && config[4][2]==10) return(1);
    return(0);
}

float boxcoords[][3] = {
    { 0.2, 0.2, 0.9 },
    { 0.8, 0.2, 0.9 },
    { 0.8, 0.8, 0.9 },
    { 0.2, 0.8, 0.9 },
    { 0.2, 0.1, 0.8 },
    { 0.8, 0.1, 0.8 },
    { 0.9, 0.2, 0.8 },
    { 0.9, 0.8, 0.8 },
    { 0.8, 0.9, 0.8 },
    { 0.2, 0.9, 0.8 },
    { 0.1, 0.8, 0.8 },
    { 0.1, 0.2, 0.8 },
    { 0.2, 0.1, 0.2 },
    { 0.8, 0.1, 0.2 },
    { 0.9, 0.2, 0.2 },
    { 0.9, 0.8, 0.2 },
    { 0.8, 0.9, 0.2 },
    { 0.2, 0.9, 0.2 },
    { 0.1, 0.8, 0.2 },
    { 0.1, 0.2, 0.2 },
    { 0.2, 0.2, 0.1 },
    { 0.8, 0.2, 0.1 },
    { 0.8, 0.8, 0.1 },
    { 0.2, 0.8, 0.1 },
};

float boxnormals[][3] = {
    { 0, 0, 1 },		/*  0 */
    { 0, 1, 0 },
    { 1, 0, 0 },
    { 0, 0,-1 },
    { 0,-1, 0 },
    {-1, 0, 0 },
    { 0.7071, 0.7071, 0.0000},	/*  6 */
    { 0.7071,-0.7071, 0.0000},
    {-0.7071, 0.7071, 0.0000},
    {-0.7071,-0.7071, 0.0000},
    { 0.7071, 0.0000, 0.7071},	/* 10 */
    { 0.7071, 0.0000,-0.7071},
    {-0.7071, 0.0000, 0.7071},
    {-0.7071, 0.0000,-0.7071},
    { 0.0000, 0.7071, 0.7071},	/* 14 */
    { 0.0000, 0.7071,-0.7071},
    { 0.0000,-0.7071, 0.7071},
    { 0.0000,-0.7071,-0.7071},
    { 0.5774, 0.5774, 0.5774},	/* 18 */
    { 0.5774, 0.5774,-0.5774},
    { 0.5774,-0.5774, 0.5774},
    { 0.5774,-0.5774,-0.5774},
    {-0.5774, 0.5774, 0.5774},
    {-0.5774, 0.5774,-0.5774},
    {-0.5774,-0.5774, 0.5774},
    {-0.5774,-0.5774,-0.5774},
};

int boxfaces[][4] = {
    {  0,  1,  2,  3 },		/*  0 */
    {  9,  8, 16, 17 },
    {  6, 14, 15,  7 },
    { 20, 23, 22, 21 },
    { 12, 13,  5,  4 },
    { 19, 11, 10, 18 },
    {  7, 15, 16,  8 },		/*  6 */
    { 13, 14,  6,  5 },
    { 18, 10,  9, 17 },
    { 19, 12,  4, 11 },
    {  1,  6,  7,  2 },		/* 10 */
    { 14, 21, 22, 15 },
    { 11,  0,  3, 10 },
    { 20, 19, 18, 23 },
    {  3,  2,  8,  9 },		/* 14 */
    { 17, 16, 22, 23 },
    {  4,  5,  1,  0 },
    { 20, 21, 13, 12 },
    {  2,  7,  8, -1 },		/* 18 */
    { 16, 15, 22, -1 },
    {  5,  6,  1, -1 },
    { 13, 21, 14, -1 },
    { 10,  3,  9, -1 },
    { 18, 17, 23, -1 },
    { 11,  4,  0, -1 },
    { 20, 12, 19, -1 },
};

#define NBOXFACES (sizeof(boxfaces)/sizeof(boxfaces[0]))

/* Draw a box.  Bevel as desired. */
void drawBox(int piece, float xoff, float yoff)
{
    int xlen, ylen;
    int i,j,k;
    float x,y,z;
    float zlen;
    float *v;

    xlen=xsize[piece];
    ylen=ysize[piece];
    zlen=zsize[piece];

    glColor3ubv(colors[piece]);
    glBegin(GL_QUADS);
    for (i=0; i<18; i++) {
	glNormal3fv(boxnormals[i]);
	for (k=0; k<4; k++) {
	    if (boxfaces[i][k] == -1) continue;
	    v=boxcoords[boxfaces[i][k]];
	    x=v[0] + OFFSETX;
	    if (v[0] > 0.5) x += xlen-1;
	    y=v[1] + OFFSETY;
	    if (v[1] > 0.5) y += ylen-1;
	    z=v[2] + OFFSETZ;
	    if (v[2] > 0.5) z += zlen-1;
	    glVertex3f(xoff+x,yoff+y,z);
	}
    }
    glEnd();
    glBegin(GL_TRIANGLES);
    for (i=18; i<NBOXFACES; i++) {
	glNormal3fv(boxnormals[i]);
	for (k=0; k<3; k++) {
	    if (boxfaces[i][k] == -1) continue;
	    v=boxcoords[boxfaces[i][k]];
	    x=v[0] + OFFSETX;
	    if (v[0] > 0.5) x += xlen-1;
	    y=v[1] + OFFSETY;
	    if (v[1] > 0.5) y += ylen-1;
	    z=v[2] + OFFSETZ;
	    if (v[2] > 0.5) z += zlen-1;
	    glVertex3f(xoff+x,yoff+y,z);
	}
    }
    glEnd();
}

float containercoords[][3] = {
    { -0.1, -0.1,  1.0 },
    { -0.1, -0.1, -0.1 },
    {  4.1, -0.1, -0.1 },
    {  4.1, -0.1,  1.0 },
    {  1.0, -0.1,  0.6 },	/*  4 */
    {  3.0, -0.1,  0.6 },
    {  1.0, -0.1,  0.0 },
    {  3.0, -0.1,  0.0 },
    {  1.0,  0.0,  0.0 },	/*  8 */
    {  3.0,  0.0,  0.0 },
    {  3.0,  0.0,  0.6 },
    {  1.0,  0.0,  0.6 },
    {  0.0,  0.0,  1.0 },	/* 12 */
    {  4.0,  0.0,  1.0 },
    {  4.0,  0.0,  0.0 },
    {  0.0,  0.0,  0.0 },
    {  0.0,  5.0,  0.0 },	/* 16 */
    {  0.0,  5.0,  1.0 },
    {  4.0,  5.0,  1.0 },
    {  4.0,  5.0,  0.0 },
    { -0.1,  5.1, -0.1 },	/* 20 */
    {  4.1,  5.1, -0.1 },
    {  4.1,  5.1,  1.0 },
    { -0.1,  5.1,  1.0 },
};

float containernormals[][3] = {
    { 0,-1, 0 },
    { 0,-1, 0 },
    { 0,-1, 0 },
    { 0,-1, 0 },
    { 0,-1, 0 },
    { 0, 1, 0 },
    { 0, 1, 0 },
    { 0, 1, 0 },
    { 1, 0, 0 },
    { 1, 0, 0 },
    { 1, 0, 0 },
    {-1, 0, 0 },
    {-1, 0, 0 },
    {-1, 0, 0 },
    { 0, 1, 0 }, 
    { 0, 0,-1 },
    { 0, 0,-1 },
    { 0, 0, 1 },
    { 0, 0, 1 },
    { 0, 0, 1 },
    { 0, 0, 1 },
    { 0, 0, 1 },
    { 0, 0, 1 },
    { 0, 0, 1 },
};

int containerfaces[][4] = {
    {  1,  6,  4,  0 },
    {  0,  4,  5,  3 },
    {  1,  2,  7,  6 },
    {  7,  2,  3,  5 },
    { 16, 19, 18, 17 },

    { 23, 22, 21, 20 },
    { 12, 11,  8, 15 },
    { 10, 13, 14,  9 },

    { 15, 16, 17, 12 },
    {  2, 21, 22,  3 },
    {  6,  8, 11,  4 },

    {  1,  0, 23, 20 },
    { 14, 13, 18, 19 },
    {  9,  7,  5, 10 },

    { 12, 13, 10, 11 },	

    {  1, 20, 21,  2 },
    {  4, 11, 10,  5 },

    { 15,  8, 19, 16 },
    { 19,  8,  9, 14 },
    {  8,  6,  7,  9 },
    {  0,  3, 13, 12 },
    { 13,  3, 22, 18 },
    { 18, 22, 23, 17 },
    { 17, 23,  0, 12 },
};

#define NCONTFACES (sizeof(containerfaces)/sizeof(containerfaces[0]))

/* Draw the container */
void drawContainer(void)
{
    int i,k;
    float *v;

    /* Y is reversed here because the model has it reversed */

    /* Arbitrary bright wood-like color */
    glColor3ub(209, 103, 23);
    glBegin(GL_QUADS);
    for (i=0; i<NCONTFACES; i++) {
	v=containernormals[i];
	glNormal3f(v[0], -v[1], v[2]);
	for (k=3; k>=0; k--) {
	    v=containercoords[containerfaces[i][k]];
	    glVertex3f(v[0]+OFFSETX, -(v[1]+OFFSETY), v[2]+OFFSETZ);
	}
    }
    glEnd();
}

void drawAll(int withTags)
{
    int i,j;
    int piece;
    char done[PIECES+1];
    float m[4][4];

    build_rotmatrix(m, curquat); 
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(0,0,10, 0,0,0, 0,-1,0);
    glMultMatrixf(&(m[0][0]));

    if (depth) {
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    } else {
	glClear(GL_COLOR_BUFFER_BIT);
    }
    for (i=1; i <= PIECES; i++) {
	done[i] = 0;
    }
    glLoadName(0);
    drawContainer();
    for (i=0; i<HEIGHT; i++) {
	for (j=0; j<WIDTH; j++) {
	    piece = thePuzzle[i][j];
	    if (piece == 0) continue;
	    if (done[piece]) continue;
	    done[piece] = 1;
	    glLoadName(piece);
	    if (piece == movingPiece) {
		drawBox(piece, move_x, move_y);
	    } else {
		drawBox(piece, j, i);
	    }
	}
    }
}

void redraw(void)
{
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45, 1.0, 0.1, 100.0);

    drawAll(GL_FALSE);

    if (doubleBuffer) tkSwapBuffers();
}

void solidifyChain(struct puzzle *puzzle)
{
    int i;

    i=0;
    while (puzzle->backptr) {
	i++;
	puzzle->backptr->solnptr = puzzle;
	puzzle=puzzle->backptr;
    }
    printf("%d moves to complete!\n", i);
}

int addConfig(Config config, struct puzzle *back)
{
    unsigned hashvalue;
    struct puzzle *newpiece;
    struct puzzlelist *newlistentry;

    hashvalue=hash(config);

    newpiece=hashtable[hashvalue % HASHSIZE];
    while (newpiece != NULL) {
	if (newpiece->hashvalue == hashvalue) {
	    int i,j;

	    for (i=0; i<WIDTH; i++) {
		for (j=0; j<HEIGHT; j++) {
		    if (convert[config[j][i]] != 
		        convert[newpiece->pieces[j][i]]) goto nomatch;
		}
	    }
	    return 0;
	}
nomatch:
	newpiece=newpiece->next;
    }

    newpiece=(struct puzzle *) malloc(sizeof(struct puzzle));
    newpiece->next=hashtable[hashvalue % HASHSIZE];
    newpiece->hashvalue=hashvalue;
    memcpy(newpiece->pieces, config, HEIGHT*WIDTH);
    newpiece->backptr=back;
    newpiece->solnptr=NULL;
    hashtable[hashvalue % HASHSIZE]=newpiece;

    newlistentry=(struct puzzlelist *) malloc(sizeof(struct puzzlelist));
    newlistentry->puzzle=newpiece;
    newlistentry->next=NULL;

    if (lastentry) {
	lastentry->next=newlistentry;
    } else {
	puzzles=newlistentry;
    }
    lastentry=newlistentry;

    if (back == NULL) {
	startPuzzle = newpiece;
    }

    if (solution(config)) {
	solidifyChain(newpiece);
	return 1;
    }

    return 0;
}

/* Checks if a space can move */
int canmove0(Config pieces, int x, int y, int dir, Config newpieces) 
{
    char piece;
    int xadd, yadd;
    int l,m;

    xadd=xadds[dir];
    yadd=yadds[dir];

    if (x+xadd<0 || x+xadd>=WIDTH ||
	y+yadd<0 || y+yadd>=HEIGHT) return 0;
    piece=pieces[y+yadd][x+xadd];
    if (piece==0) return 0;
    memcpy(newpieces, pieces, HEIGHT*WIDTH);
    for (l=0; l<WIDTH; l++) { 
	for (m=0; m<HEIGHT; m++) {
	    if (newpieces[m][l]==piece)
		newpieces[m][l]=0;
	}
    }
    xadd= -xadd;
    yadd= -yadd;
    for (l=0; l<WIDTH; l++) { 
	for (m=0; m<HEIGHT; m++) {
	    if (pieces[m][l]==piece) {
		int newx, newy;

		newx=l+xadd;
		newy=m+yadd;
		if (newx<0 || newx>=WIDTH ||
		    newy<0 || newy>=HEIGHT) return 0;
		if (newpieces[newy][newx] != 0) return 0;
		newpieces[newy][newx]=piece;
	    }
	}
    }
    return 1;
}

/* Checks if a piece can move */
int canmove(Config pieces, int x, int y, int dir, Config newpieces) 
{
    int xadd, yadd;

    xadd=xadds[dir];
    yadd=yadds[dir];

    if (x+xadd<0 || x+xadd>=WIDTH ||
	y+yadd<0 || y+yadd>=HEIGHT) return 0;
    if (pieces[y+yadd][x+xadd] == pieces[y][x]) {
	return canmove(pieces, x+xadd, y+yadd, dir, newpieces);
    }
    if (pieces[y+yadd][x+xadd] != 0) return 0;
    return canmove0(pieces, x+xadd, y+yadd, (dir+2) % 4, newpieces);
}

int generateNewConfigs(struct puzzle *puzzle)
{
    int i,j,k;
    Config pieces;
    Config newpieces;

    memcpy(pieces, puzzle->pieces, HEIGHT*WIDTH);
    for (i=0; i<WIDTH; i++) {
	for (j=0; j<HEIGHT; j++) {
	    if (pieces[j][i] == 0) {
		for (k=0; k<4; k++) {
		    if (canmove0(pieces, i, j, k, newpieces)) {
			if (addConfig(newpieces, puzzle)) return 1;
		    }
		}
	    }
	}
    }
    return 0;
}

void freeSolutions(void)
{
    struct puzzlelist *nextpuz;
    struct puzzle *puzzle, *next;
    int i;

    while (puzzles) {
	nextpuz = puzzles->next;
	free((char *) puzzles);
	puzzles=nextpuz;
    }
    lastentry = NULL;
    for (i=0; i<HASHSIZE; i++) {
	puzzle = hashtable[i];
	hashtable[i] = NULL;
	while (puzzle) {
	    next = puzzle->next;
	    free((char *) puzzle);
	    puzzle = next;
	}
    }
    startPuzzle = NULL;
}

int continueSolving(void)
{
    struct puzzle *nextpuz;    
    int i,j;
    int movedPiece;
    int movedir;
    int fromx, fromy;
    int tox, toy;

    if (startPuzzle == NULL) return 0;
    if (startPuzzle->solnptr == NULL) {
	freeSolutions();
	return 0;
    }

    nextpuz = startPuzzle->solnptr;
    movedPiece=0;
    movedir=0;
    for (i=0; i<HEIGHT; i++) {
	for (j=0; j<WIDTH; j++) {
	    if (startPuzzle->pieces[i][j] != nextpuz->pieces[i][j]) {
		if (startPuzzle->pieces[i][j]) {
		    movedPiece=startPuzzle->pieces[i][j];
		    fromx=j;
		    fromy=i;
		    if (i<HEIGHT-1 && nextpuz->pieces[i+1][j] == movedPiece) {
			movedir=3;
		    } else {
			movedir=2;
		    }
		    goto found_piece;
		} else {
		    movedPiece=nextpuz->pieces[i][j];
		    if (i<HEIGHT-1 && 
			    startPuzzle->pieces[i+1][j] == movedPiece) {
			fromx=j;
			fromy=i+1;
			movedir=1;
		    } else {
			fromx=j+1;
			fromy=i;
			movedir=0;
		    }
		    goto found_piece;
		}
	    }
	}
    }
    printf("What!  No change?\n");
    freeSolutions();
    return 0;

found_piece:
    if (!movingPiece) {
	movingPiece = movedPiece;
	move_x = fromx;
	move_y = fromy;
    }
    move_x += xadds[movedir] * MOVE_SPEED;
    move_y += yadds[movedir] * MOVE_SPEED;

    tox = fromx + xadds[movedir];
    toy = fromy + yadds[movedir];

    if (move_x > tox - MOVE_SPEED/2 && move_x < tox + MOVE_SPEED/2 &&
	    move_y > toy - MOVE_SPEED/2 && move_y < toy + MOVE_SPEED/2) {
	startPuzzle = nextpuz;
	movingPiece=0;
    }

    memcpy(thePuzzle, startPuzzle->pieces, HEIGHT*WIDTH);
    return 1;
}

int solvePuzzle(void)
{
    struct puzzlelist *nextpuz;
    int i;

    if (solution(thePuzzle)) {
	printf("Puzzle already solved!\n");
	return 0;
    }
    addConfig(thePuzzle, NULL);
    i=0;

    while (puzzles) {
	i++;
	if (generateNewConfigs(puzzles->puzzle)) break;
	nextpuz=puzzles->next;
	free((char *) puzzles);
	puzzles=nextpuz;
    }
    if (puzzles == NULL) {
	freeSolutions();
	printf("I can't solve it! (%d positions examined)\n", i);
	return 1;
    }
    return 1;
}

int selectPiece(int mousex, int mousey) 
{
    long hits;
    GLuint selectBuf[1024];
    GLuint closest;
    GLuint dist;

    glSelectBuffer(1024, selectBuf);
    (void) glRenderMode(GL_SELECT);
    glInitNames();

    /* Because LoadName() won't work with no names on the stack */
    glPushName(-1);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPickMatrix(mousex, H-mousey, 4, 4, viewport);
    gluPerspective(45, 1.0, 0.1, 100.0);

    drawAll(GL_TRUE);

    hits = glRenderMode(GL_RENDER);
    if (hits <= 0) {
	return 0;
    }

    closest=0;
    dist=4294967295;
    while (hits) {
	if (selectBuf[(hits-1)*4+1] < dist) {
	    dist = selectBuf[(hits-1)*4+1];
	    closest = selectBuf[(hits-1)*4+3];
	}
	hits--;
    }
    return closest;
}

void nukePiece(int piece)
{
    int i,j;

    for (i=0; i<HEIGHT; i++) {
	for (j=0; j<WIDTH; j++) {
	    if (thePuzzle[i][j] == piece) {
		thePuzzle[i][j] = 0;
	    }
	}
    }
}

void multMatrices(const GLfloat a[16], const GLfloat b[16], GLfloat r[16])
{
    int i, j;

    for (i = 0; i < 4; i++) {
        for (j = 0; j < 4; j++) {
            r[i*4+j] =
                a[i*4+0]*b[0*4+j] +
                a[i*4+1]*b[1*4+j] +
                a[i*4+2]*b[2*4+j] +
                a[i*4+3]*b[3*4+j];
        }
    }
}

void makeIdentity(GLfloat m[16])
{
    m[0+4*0] = 1; m[0+4*1] = 0; m[0+4*2] = 0; m[0+4*3] = 0;
    m[1+4*0] = 0; m[1+4*1] = 1; m[1+4*2] = 0; m[1+4*3] = 0;
    m[2+4*0] = 0; m[2+4*1] = 0; m[2+4*2] = 1; m[2+4*3] = 0;
    m[3+4*0] = 0; m[3+4*1] = 0; m[3+4*2] = 0; m[3+4*3] = 1;
}

/*
** inverse = invert(src)
*/
int invertMatrix(const GLfloat src[16], GLfloat inverse[16])
{
    int i, j, k, swap;
    double t;
    GLfloat temp[4][4];

    for (i=0; i<4; i++) {
	for (j=0; j<4; j++) {
	    temp[i][j] = src[i*4+j];
	}
    }
    makeIdentity(inverse);

    for (i = 0; i < 4; i++) {
	/*
	** Look for largest element in column
	*/
	swap = i;
	for (j = i + 1; j < 4; j++) {
	    if (fabs(temp[j][i]) > fabs(temp[i][i])) {
		swap = j;
	    }
	}

	if (swap != i) {
	    /*
	    ** Swap rows.
	    */
	    for (k = 0; k < 4; k++) {
		t = temp[i][k];
		temp[i][k] = temp[swap][k];
		temp[swap][k] = t;

		t = inverse[i*4+k];
		inverse[i*4+k] = inverse[swap*4+k];
		inverse[swap*4+k] = t;
	    }
	}

	if (temp[i][i] == 0) {
	    /*
	    ** No non-zero pivot.  The matrix is singular, which shouldn't
	    ** happen.  This means the user gave us a bad matrix.
	    */
	    return 0;
	}

	t = temp[i][i];
	for (k = 0; k < 4; k++) {
	    temp[i][k] /= t;
	    inverse[i*4+k] /= t;
	}
	for (j = 0; j < 4; j++) {
	    if (j != i) {
		t = temp[j][i];
		for (k = 0; k < 4; k++) {
		    temp[j][k] -= temp[i][k]*t;
		    inverse[j*4+k] -= inverse[i*4+k]*t;
		}
	    }
	}
    }
    return 1;
}

/*
** This is a screwball function.  What it does is the following:
** Given screen x and y coordinates, compute the corresponding object space 
**   x and y coordinates given that the object space z is 0.9 + OFFSETZ.
** Since the tops of (most) pieces are at z = 0.9 + OFFSETZ, we use that 
**   number.
*/
int computeCoords(int piece, int mousex, int mousey, 
	GLfloat *selx, GLfloat *sely)
{
    GLfloat modelMatrix[16];
    GLfloat projMatrix[16];
    GLfloat finalMatrix[16];
    GLfloat in[4];
    GLfloat a,b,c,d;
    GLfloat top, bot;
    GLfloat z;
    GLfloat w;
    GLfloat height;

    if (piece == 0) return 0;
    height = zsize[piece] - 0.1 + OFFSETZ;

    glGetFloatv(GL_PROJECTION_MATRIX, projMatrix);
    glGetFloatv(GL_MODELVIEW_MATRIX, modelMatrix);
    multMatrices(modelMatrix, projMatrix, finalMatrix);
    if (!invertMatrix(finalMatrix, finalMatrix)) return 0;

    in[0] = (2.0 * (mousex - viewport[0]) / viewport[2]) - 1;
    in[1] = (2.0 * ((H - mousey) - viewport[1]) / viewport[3]) - 1;

    a = in[0] * finalMatrix[0*4+2] +
	    in[1] * finalMatrix[1*4+2] + 
	    finalMatrix[3*4+2];
    b = finalMatrix[2*4+2];
    c = in[0] * finalMatrix[0*4+3] +
	    in[1] * finalMatrix[1*4+3] +
	    finalMatrix[3*4+3];
    d = finalMatrix[2*4+3];

    /* 
    ** Ok, now we need to solve for z:
    **   (a + b z) / (c + d z) = height.
    ** ("height" is the height in object space we want to solve z for)
    **
    ** ==>  a + b z = height c + height d z
    **      bz - height d z = height c - a
    **      z = (height c - a) / (b - height d)
    */
    top = height * c - a;
    bot = b - height * d;
    if (bot == 0.0) return 0;

    z = top / bot;

    /* 
    ** Ok, no problem.
    ** Now we solve for x and y.  We know that w = c + d z, so we compute it.
    */
    w = c + d * z;

    /*
    ** Now for x and y:
    */
    *selx = (in[0] * finalMatrix[0*4+0] +
	    in[1] * finalMatrix[1*4+0] +
	    z * finalMatrix[2*4+0] +
	    finalMatrix[3*4+0]) / w - OFFSETX;
    *sely = (in[0] * finalMatrix[0*4+1] +
	    in[1] * finalMatrix[1*4+1] +
	    z * finalMatrix[2*4+1] +
	    finalMatrix[3*4+1]) / w - OFFSETY;
    return 1;
}

static int selected;
static int selectx, selecty;
static float selstartx, selstarty;

void grabPiece(int piece, float selx, float sely)
{
    int hit;

    selectx=selx;
    selecty=sely;
    if (selectx < 0 || selecty < 0 || selectx >= WIDTH || selecty >= HEIGHT) {
	return;
    }
    hit = thePuzzle[selecty][selectx];
    if (hit != piece) return;
    if (hit) {
	movingPiece=hit;
	while (selectx > 0 && thePuzzle[selecty][selectx-1] == movingPiece) {
	    selectx--;
	}
	while (selecty > 0 && thePuzzle[selecty-1][selectx] == movingPiece) {
	    selecty--;
	}
	move_x=selectx;
	move_y=selecty;
	selected=1;
	selstartx=selx;
	selstarty=sely;
    } else {
	selected=0;
    }
}

void moveSelection(float selx, float sely)
{
    float deltax, deltay;
    int dir;
    Config newpieces;

    if (!selected) return;
    deltax = selx - selstartx;
    deltay = sely - selstarty;

    if (fabs(deltax) > fabs(deltay)) {
	deltay = 0;
	if (deltax > 0) {
	    if (deltax > 1) deltax = 1;
	    dir = 2;
	} else {
	    if (deltax < -1) deltax = -1;
	    dir = 0;
	}
    } else {
	deltax = 0;
	if (deltay > 0) {
	    if (deltay > 1) deltay = 1;
	    dir = 3;
	} else {
	    if (deltay < -1) deltay = -1;
	    dir = 1;
	}
    }
    if (canmove(thePuzzle, selectx, selecty, dir, newpieces)) {
	move_x = deltax + selectx;
	move_y = deltay + selecty;
	if (deltax > 0.5) {
	    memcpy(thePuzzle, newpieces, HEIGHT*WIDTH);
	    selectx++;
	    selstartx++;
	} else if (deltax < -0.5) {
	    memcpy(thePuzzle, newpieces, HEIGHT*WIDTH);
	    selectx--;
	    selstartx--;
	} else if (deltay > 0.5) {
	    memcpy(thePuzzle, newpieces, HEIGHT*WIDTH);
	    selecty++;
	    selstarty++;
	} else if (deltay < -0.5) {
	    memcpy(thePuzzle, newpieces, HEIGHT*WIDTH);
	    selecty--;
	    selstarty--;
	}
    } else {
	if (deltay > 0 && thePuzzle[selecty][selectx] == 10 &&
		selectx == 1 && selecty == 3) {
	    /* Allow visual movement of solution piece outside of the box */
	    move_x = selectx;
	    move_y = sely - selstarty + selecty;
	} else {
	    move_x = selectx;
	    move_y = selecty;
	}
    }
}

void dropSelection(void)
{
    if (!selected) return;
    movingPiece = 0;
    selected = 0;
}

static int left_mouse, right_mouse;
static int mousex, mousey;
static int solving;
static int spinning;
static float lastquat[4];
static int sel_piece;

static void Reshape(int width, int height)
{

    W = width;
    H = height;
    glViewport(0, 0, W, H);
    glGetIntegerv(GL_VIEWPORT, viewport);
}

static GLenum Key(int key, GLenum mask)
{
    int piece;
    int x, y;

    if (!left_mouse && !right_mouse) {
	switch(key) {
	  case TK_ESCAPE:
	    tkQuit();
	  case TK_d:
	  case TK_D:
	    if (solving) {
		freeSolutions();
		solving=0;
		movingPiece=0;
	    }
	    tkGetMouseLoc(&x, &y);
	    piece = selectPiece(x, y);
	    if (piece) {
		nukePiece(piece);
	    }
	    break;
	  case TK_S:
	  case TK_s:
	    if (solving) {
		freeSolutions();
		solving=0;
		movingPiece=0;
	    } else {
		printf("Solving...\n");
		if (solvePuzzle()) {
		    solving = 1;
		}
	    }
	    break;
	  case TK_R:
	  case TK_r:
	    if (solving) {
		freeSolutions();
		solving=0;
		movingPiece=0;
	    }
	    memcpy(thePuzzle, startConfig, HEIGHT*WIDTH);
	    break;
	  case TK_b:
	  case TK_B:
	    depth=1-depth;
	    if (depth) {
		glEnable(GL_DEPTH_TEST);
	    } else {
		glDisable(GL_DEPTH_TEST);
	    }
	    break;
	  default:
	    return GL_FALSE;
	}
    }
    return GL_TRUE;
}

static GLenum MouseUp(int mouseX, int mouseY, GLenum button)
{

    if (button & TK_LEFTBUTTON) {
	left_mouse = GL_FALSE;
	dropSelection();
	return GL_TRUE;
    } else if (button & TK_RIGHTBUTTON) {
	right_mouse = GL_FALSE;
	return GL_TRUE;
    }
    return GL_FALSE;
}

static GLenum MouseDown(int mouseX, int mouseY, GLenum button)
{
    int piece;
    float selx, sely;

    mousex = mouseX;
    mousey = mouseY;
    if (button & TK_LEFTBUTTON) {
	if (solving) {
	    freeSolutions();
	    solving=0;
	    movingPiece=0;
	}
	left_mouse = GL_TRUE;
	sel_piece = selectPiece(mousex, mousey);
	if (computeCoords(sel_piece, mousex, mousey, &selx, &sely)) {
	    grabPiece(sel_piece, selx, sely);
	}
	return GL_TRUE;
    } else if (button & TK_RIGHTBUTTON) {
	right_mouse = GL_TRUE;
	return GL_TRUE;
    }
    return GL_FALSE;
}

void animate(void)
{
    int piece;
    float selx, sely;
    int x, y;

    if (right_mouse || left_mouse) {
	tkGetMouseLoc(&x, &y);
	if (right_mouse && !left_mouse) {
	    if (mousex != x || mousey != y) {
		trackball(lastquat, 
			  2.0*(W-mousex)/W - 1.0, 
			  2.0*mousey/H - 1.0,
			  2.0*(W-x)/W - 1.0, 
			  2.0*y/H - 1.0);
		spinning = 1;
	    } else {
		spinning = 0;
	    }
	} else {
	    computeCoords(sel_piece, x, y, &selx, &sely);
	    moveSelection(selx, sely);
	}
	mousex = x;
	mousey = y;
    }
    if (spinning) {
	add_quats(lastquat, curquat, curquat);
    }
    redraw();
    if (solving) {
	if (!continueSolving()) {
	    solving = 0;
	}
    }
}

void init(void) 
{
    static float lmodel_ambient[] = { 0.0, 0.0, 0.0, 0.0 };
    static float lmodel_twoside[] = { GL_FALSE };
    static float lmodel_local[] = { GL_FALSE };
    static float light0_ambient[] = { 0.1, 0.1, 0.1, 1.0 };
    static float light0_diffuse[] = { 1.0, 1.0, 1.0, 0.0 };
    static float light0_position[] = { 0.8660254, 0.5, 1, 0 };
    static float light0_specular[] = { 0.0, 0.0, 0.0, 0.0 };
    static float bevel_mat_ambient[] = { 0.0, 0.0, 0.0, 1.0 };
    static float bevel_mat_shininess[] = { 40.0 };
    static float bevel_mat_specular[] = { 0.0, 0.0, 0.0, 0.0 };
    static float bevel_mat_diffuse[] = { 1.0, 0.0, 0.0, 0.0 };

    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    glEnable(GL_DEPTH_TEST);
    glClearDepth(1.0);

    glClearColor(0.5, 0.5, 0.5, 0.0);
    glLightfv(GL_LIGHT0, GL_AMBIENT, light0_ambient);
    glLightfv(GL_LIGHT0, GL_DIFFUSE, light0_diffuse);
    glLightfv(GL_LIGHT0, GL_SPECULAR, light0_specular);
    glLightfv(GL_LIGHT0, GL_POSITION, light0_position);
    glEnable(GL_LIGHT0);

    glLightModelfv(GL_LIGHT_MODEL_LOCAL_VIEWER, lmodel_local);
    glLightModelfv(GL_LIGHT_MODEL_TWO_SIDE, lmodel_twoside);
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, lmodel_ambient);
    glEnable(GL_LIGHTING);

    glMaterialfv(GL_FRONT, GL_AMBIENT, bevel_mat_ambient);
    glMaterialfv(GL_FRONT, GL_SHININESS, bevel_mat_shininess);
    glMaterialfv(GL_FRONT, GL_SPECULAR, bevel_mat_specular);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, bevel_mat_diffuse);

    glColorMaterial(GL_FRONT_AND_BACK, GL_DIFFUSE);
    glEnable(GL_COLOR_MATERIAL);
    glShadeModel(GL_FLAT);

    trackball(curquat, 0.0, 0.0, 0.0, 0.0);
    srandom(time(NULL));
}

static void Usage(void)
{
    printf("Usage: puzzle [-s]\n");
    printf("   -s:  Run in single buffered mode\n");
    exit(-1);
}

void main(long argc, char** argv)
{
    long i;

    for (i = 1; i < argc; i++) {
        if (argv[i][0] == '-') {
            switch (argv[i][1]) {
              case 's':
                doubleBuffer = 0;
                break;
              default:
                Usage();
            }
        } else {
            Usage();
        }
    }


    tkInitPosition(0, 0, W, H);

    tkInitDisplayMode(TK_DEPTH16|TK_RGB|TK_DOUBLE|TK_DIRECT);

    if (tkInitWindow("Puzzle") == GL_FALSE) {
        tkQuit();
    }

    init();

    glGetIntegerv(GL_VIEWPORT, viewport);

    printf("\n\n\n\n\n\n");
    printf("r   Reset puzzle\n");
    printf("s   Solve puzzle (may take a few seconds to compute)\n");
    printf("d   Destroy a piece - makes the puzzle easier\n");
    printf("b   Toggles the depth buffer on and off\n");
    printf("\n");
    printf("Right mouse spins the puzzle\n");
    printf("Left mouse moves pieces\n");


    tkExposeFunc(Reshape);
    tkReshapeFunc(Reshape);
    tkKeyDownFunc(Key);
    tkMouseDownFunc(MouseDown);
    tkMouseUpFunc(MouseUp);
    tkIdleFunc(animate);
    tkExec();
}