/*
 * @(#)Rubik.c
 *
 * Copyright 1994 - 2022  David A. Bagley, bagleyd AT verizon.net
 *
 * All rights reserved.
 *
 * 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, and that the name of the author not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.
 *
 * This program is distributed in the hope that it will be "useful",
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

/* Methods file for Rubik */

#include "file.h"
#include "rngs.h"
#include "sound.h"
#include "RubikP.h"
#include "Rubik2dP.h"
#include "Rubik3dP.h"
#ifdef HAVE_OPENGL
#include "RubikGLP.h"
#endif

#ifdef WINVER
#ifndef LOGPATH
#define LOGPATH "/usr/tmp"
#endif
#ifndef INIFILE
#define INIFILE "wrubik.ini"
#endif

#define SECTION "setup"

static const char *faceColorString[MAX_FACES] =
{
	"255 0 0",
	"255 255 0",
	"255 255 255",
	"0 255 0",
	"255 165 0",
	"0 0 255"
};

static const char faceColorChar[MAX_FACES] =
{'R', 'Y', 'W', 'G', 'O', 'B'};
#else

#if defined( USE_SOUND ) && defined( USE_NAS )
Display *dsp;
#endif

#ifndef LOGPATH
#ifdef VMS
#define LOGPATH "SYS$SCRATCH:"
#else
#define LOGPATH "/usr/tmp"
#endif
#endif

static Boolean setValuesPuzzle(Widget current, Widget request, Widget renew);
static void destroyPuzzle(Widget old);
static void initializePuzzle(Widget request, Widget renew);

RubikClassRec rubikClassRec =
{
	{
		(WidgetClass) & widgetClassRec,		/* superclass */
		(char *) "Rubik",	/* class name */
		sizeof (RubikRec),	/* widget size */
		NULL,		/* class initialize */
		NULL,		/* class part initialize */
		FALSE,		/* class inited */
		(XtInitProc) initializePuzzle,	/* initialize */
		NULL,		/* initialize hook */
		XtInheritRealize,	/* realize */
		NULL,		/* actions */
		0,		/* num actions */
		NULL,		/* resources */
		0,		/* num resources */
		NULLQUARK,	/* xrm class */
		TRUE,		/* compress motion */
		TRUE,		/* compress exposure */
		TRUE,		/* compress enterleave */
		TRUE,		/* visible interest */
		(XtWidgetProc) destroyPuzzle,	/* destroy */
		NULL,		/* resize */
		NULL,		/* expose */
		(XtSetValuesFunc) setValuesPuzzle,	/* set values */
		NULL,		/* set values hook */
		XtInheritSetValuesAlmost,	/* set values almost */
		NULL,		/* get values hook */
		NULL,		/* accept focus */
		XtVersion,	/* version */
		NULL,		/* callback private */
		NULL,		/* tm table */
		NULL,		/* query geometry */
		NULL,		/* display accelerator */
		NULL		/* extension */
	},
	{
		0		/* ignore */
	}
};

WidgetClass rubikWidgetClass = (WidgetClass) & rubikClassRec;

void
setPuzzle(RubikWidget w, int reason)
{
	rubikCallbackStruct cb;

	cb.reason = reason;
	XtCallCallbacks((Widget) w, (char *) XtNselectCallback, &cb);
}

static void
setPuzzleMove(RubikWidget w, int reason, int face, int position, int direction,
		Boolean control, int fast)
{
	rubikCallbackStruct cb;

	cb.reason = reason;
	cb.face = face;
	cb.position = position;
	cb.direction = direction;
	cb.control = control;
	cb.fast = fast;
	XtCallCallbacks((Widget) w, (char *) XtNselectCallback, &cb);
}
#endif

static void
loadFont(RubikWidget w)
{
#ifndef WINVER
	Display *display = XtDisplay(w);
	const char *altfontname = "-*-times-*-r-*-*-*-180-*";
	char buf[512];

	if (w->rubik.fontInfo) {
		XUnloadFont(XtDisplay(w), w->rubik.fontInfo->fid);
		XFreeFont(XtDisplay(w), w->rubik.fontInfo);
	}
	if (w->rubik.font && (w->rubik.fontInfo =
			XLoadQueryFont(display, w->rubik.font)) == NULL) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 512,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->rubik.font, altfontname);
#else
		(void) sprintf(buf,
			"Cannot open %s font.\nAttempting %s font as alternate\n",
			w->rubik.font, altfontname);
#endif
		DISPLAY_WARNING(buf);
		if ((w->rubik.fontInfo = XLoadQueryFont(display,
				altfontname)) == NULL) {
#ifdef HAVE_SNPRINTF
			(void) snprintf(buf, 512,
				"Cannot open %s alternate font.\nUse the -font option to specify a font to use.\n",
				altfontname);
#else
			(void) sprintf(buf,
				"Cannot open %s alternate font.\nUse the -font option to specify a font to use.\n",
				altfontname);
#endif
			DISPLAY_WARNING(buf);
		}
	}
	if (w->rubik.fontInfo) {
		w->rubik.letterOffset.x = XTextWidth(w->rubik.fontInfo, "8", 1)
			/ 2;
		w->rubik.letterOffset.y = w->rubik.fontInfo->max_bounds.ascent
			/ 2;
	} else
#endif
	{
		w->rubik.letterOffset.x = 3;
		w->rubik.letterOffset.y = 4;
	}
}

#ifndef LOGFILE
#define LOGFILE "rubik.log"
#endif

static int facesToDirection[MAX_FACES][MAX_FACES] =
{
	{-1, 3, 2, 1, -3, 0},
	{0, -1, 1, -3, 2, 3},
	{0, 3, -1, 1, 2, -3},
	{0, -3, 3, -1, 2, 1},
	{-3, 3, 0, 1, -1, 2},
	{2, 3, -3, 1, 0, -1}
};
static RubikLoc slideNextRow[MAX_FACES][MAX_ORIENT] =
{
	{
		{5, TOP},
		{3, RIGHT},
		{2, TOP},
		{1, LEFT}
	},
	{
		{0, RIGHT},
		{2, TOP},
		{4, LEFT},
		{5, BOTTOM}
	},
	{
		{0, TOP},
		{3, TOP},
		{4, TOP},
		{1, TOP}
	},
	{
		{0, LEFT},
		{5, BOTTOM},
		{4, RIGHT},
		{2, TOP}
	},
	{
		{2, TOP},
		{3, LEFT},
		{5, TOP},
		{1, RIGHT}
	},
	{
		{4, TOP},
		{3, BOTTOM},
		{0, TOP},
		{1, BOTTOM}
	}
};
static int rowToRotate[MAX_FACES][MAX_ORIENT] =
{
	{3, 2, 1, 5},
	{2, 4, 5, 0},
	{3, 4, 1, 0},
	{5, 4, 2, 0},
	{3, 5, 1, 2},
	{3, 0, 1, 4}
};

static RubikStack undo = {NULL, NULL, NULL, 0};
static RubikStack redo = {NULL, NULL, NULL, 0};

static void
checkPieces(RubikWidget w)
{
	char *buf1 = NULL, *buf2 = NULL;

	if (w->rubik.sizeX < MIN_FACETS) {
		intCat(&buf1,
			"Number of rubik in X direction out of bounds, use at least ",
			MIN_FACETS);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_FACETS);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->rubik.sizeX = DEFAULT_FACETS;
	}
	if (w->rubik.sizeY < MIN_FACETS) {
		intCat(&buf1,
			"Number of rubik in Y direction out of bounds, use at least ",
			MIN_FACETS);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_FACETS);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->rubik.sizeY = DEFAULT_FACETS;
	}
	if (w->rubik.sizeZ < MIN_FACETS) {
		intCat(&buf1,
			"Number of rubik in Z direction out of bounds, use at least ",
			MIN_FACETS);
		stringCat(&buf2, buf1, ", defaulting to ");
		free(buf1);
		intCat(&buf1, buf2, DEFAULT_FACETS);
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		w->rubik.sizeZ = DEFAULT_FACETS;
	}
	if (w->rubik.delay < 0) {
		intCat(&buf1, "Delay cannot be negative (",
			w->rubik.delay);
		stringCat(&buf2, buf1, "), taking absolute value");
		free(buf1);
		DISPLAY_WARNING(buf2);
		free(buf2);
		w->rubik.delay = -w->rubik.delay;
	}
}

void
faceSizes(const RubikWidget w, const int face,
		int *sizeOfRow, int *sizeOfColumn)
{
	switch (face) {
	case 0:	/* TOP */
	case 4:	/* BOTTOM */
		*sizeOfRow = w->rubik.sizeX;
		*sizeOfColumn = w->rubik.sizeZ;
		break;
	case 1:	/* LEFT */
	case 3:	/* RIGHT */
		*sizeOfRow = w->rubik.sizeZ;
		*sizeOfColumn = w->rubik.sizeY;
		break;
	case 2:	/* FRONT */
	case 5:	/* BACK */
		*sizeOfRow = w->rubik.sizeX;
		*sizeOfColumn = w->rubik.sizeY;
		break;
	default:
		{
			char *buf;

			intCat(&buf, "faceSizes: face ", face);
			DISPLAY_WARNING(buf);
			free(buf);
		}
		*sizeOfRow = 0;
		*sizeOfColumn = 0;
	}
}

int
sizeFace(const RubikWidget w, const int face)
{
	int sizeOfRow, sizeOfColumn;

	faceSizes(w, face, &sizeOfRow, &sizeOfColumn);
	return (sizeOfRow * sizeOfColumn);
}

int
sizeRow(const RubikWidget w, const int face)
{
	int sizeOfRow, sizeOfColumn;

	faceSizes(w, face, &sizeOfRow, &sizeOfColumn);
	return sizeOfRow;
}

static Boolean
checkFaceSquare(RubikWidget w, int face)
{
	int sizeOfRow, sizeOfColumn;

	faceSizes(w, face, &sizeOfRow, &sizeOfColumn);
	return (sizeOfRow == sizeOfColumn);
	/* Cubes can be made square with a 4x2 face where 90 degree turns
	 * should be permitted but that is kind of complicated for me.
	 * This can be done in 2 ways, where the side of the facets are
	 * the same size and one where one side (the side with half the
	 * number of facets) is twice the size of the other.  The first is
	 * complicated because faces of cubies can go under other faces.
	 * The second way is similar to "banded rubik" where scotch tape
	 * restricts the moves of the puzzle.  Here you have to keep track
	 * of the restrictions and show banded cubies graphically as one
	 * cube.
	 */
}

Boolean
checkSolved(const RubikWidget w, Boolean rotation)
{
	int face, position;
	RubikLoc test = {0, FALSE};

	for (face = 0; face < MAX_FACES; face++)
		for (position = 0; position < sizeFace(w, face); position++) {
			if (!position) {
				test.face = w->rubik.cubeLoc[face][position].face;
				test.rotation = w->rubik.cubeLoc[face][position].rotation;
			} else if (test.face !=		/* face */
				   w->rubik.cubeLoc[face][position].face ||
				   (rotation && test.rotation !=		/* STRT - MAX_ORIENT */
				  w->rubik.cubeLoc[face][position].rotation))
				return False;
		}
	return True;
}

#ifdef DEBUG
void
printCube(const RubikWidget w)
{
	int face, position, sizeOfRow, sizeOfColumn;

	for (face = 0; face < MAX_FACES; face++) {
		faceSizes(w, face, &sizeOfRow, &sizeOfColumn);
		for (position = 0; position < sizeOfRow * sizeOfColumn; position++) {
			(void) printf("%d %d  ", w->rubik.cubeLoc[face][position].face,
				w->rubik.cubeLoc[face][position].rotation);
			if (!((position + 1) % sizeOfRow))
				(void) printf("\n");
		}
		(void) printf("\n");
	}
	(void) printf("\n");
}

void
printRow(RubikWidget w, int orient, int size)
{
	int i;

	(void) printf("Row %d:\n", orient);
	for (i = 0; i < size; i++)
		(void) printf("%d %d  ", w->rubik.rowLoc[orient][i].face,
			w->rubik.rowLoc[orient][i].rotation);
	(void) printf("\n");
}

#endif

static void
drawSquare(const RubikWidget w, const int face, const int position, const int offset)
{
	if (w->rubik.dim == 2)
		drawSquare2D((Rubik2DWidget) w, face, position, offset);
	else if (w->rubik.dim == 3)
		drawSquare3D((Rubik3DWidget) w, face, position, offset);
}

static void
drawSquareAll(const RubikWidget w, const int face, const int position, const int offset)
{
	drawSquare(w, face, position, offset);
#ifdef HAVE_OPENGL
	if (w->rubik.dim == 4)
		drawAllPiecesGL((RubikGLWidget) w);
#endif
}

void
drawAllPieces(const RubikWidget w)
{
	int face, position;

#ifdef HAVE_OPENGL
	if (w->rubik.dim == 4) {
		drawAllPiecesGL((RubikGLWidget) w);
		return;
	}
#endif
	for (face = 0; face < MAX_FACES; face++)
		for (position = 0; position < sizeFace(w, face); position++)
			drawSquare(w, face, position, FALSE);
}

static void
drawFrame(const RubikWidget w, const Boolean focus)
{
	if (w->rubik.dim == 2)
		drawFrame2D((Rubik2DWidget) w, focus);
	else if (w->rubik.dim == 3)
		drawFrame3D((Rubik3DWidget) w, focus);
/*#ifdef HAVE_OPENGL
	else if (w->rubik.dim == 4)
		drawFrameGL((RubikGLWidget) w, focus);
#endif*/
}

static void
moveNoPieces(RubikWidget w)
{
	setPuzzle(w, ACTION_ILLEGAL);
}

static void
rotateFace(RubikWidget w, int face, int direction)
{
	int position, i, j, sizeOfRow, sizeOfColumn, sizeOnPlane;
	RubikLoc *faceLoc = NULL;

	faceSizes(w, face, &sizeOfRow, &sizeOfColumn);
	sizeOnPlane = sizeOfRow * sizeOfColumn;
	if ((faceLoc = (RubikLoc *) malloc(sizeof (RubikLoc) *
				(size_t) sizeOnPlane)) == NULL) {
		DISPLAY_ERROR("Not enough memory for rubik face position info, exiting.");
	}
	/* Read Face */
	for (position = 0; position < sizeOnPlane; position++)
		faceLoc[position] = w->rubik.cubeLoc[face][position];
	/* Write Face */
	for (position = 0; position < sizeOnPlane; position++) {
		i = position % sizeOfRow;
		j = position / sizeOfRow;
		if (direction == CW)
			w->rubik.cubeLoc[face][position] =
				faceLoc[(sizeOfRow - i - 1) * sizeOfRow + j];
		else if (direction == CCW)
			w->rubik.cubeLoc[face][position] =
				faceLoc[i * sizeOfRow + sizeOfColumn - j - 1];
		else		/* (direction == HALF) */
			w->rubik.cubeLoc[face][position] =
				faceLoc[sizeOfRow - i - 1 + (sizeOfColumn - j - 1) * sizeOfRow];

		w->rubik.cubeLoc[face][position].rotation =
			(char) ((w->rubik.cubeLoc[face][position].rotation +
			direction - MAX_ORIENT) % MAX_ORIENT);
		drawSquare(w, face, position, FALSE);
	}
	if (faceLoc != NULL)
		free(faceLoc);
}

static void
slideRC(RubikWidget w, int face, int direction, int h, int sizeOnOppAxis,
	int *newFace, int *newDirection, int *newH,
	int *rotate, Boolean *reverse)
{
	*newFace = slideNextRow[face][direction].face;
	*rotate = slideNextRow[face][direction].rotation;
	*newDirection = (*rotate + direction) % MAX_ORIENT;
	switch (*rotate) {
	case TOP:
		*newH = h;
		*reverse = False;
		break;
	case RIGHT:
		if (*newDirection == TOP || *newDirection == BOTTOM) {
			*newH = sizeOnOppAxis - 1 - h;
			*reverse = False;
		} else {	/* *newDirection == RIGHT ||
				   *newDirection == LEFT */
			*newH = h;
			*reverse = True;
		}
		break;
	case BOTTOM:
		*newH = sizeOnOppAxis - 1 - h;
		*reverse = True;
		break;
	case LEFT:
		if (*newDirection == TOP || *newDirection == BOTTOM) {
			*newH = h;
			*reverse = True;
		} else {	/* *newDirection == RIGHT ||
				   *newDirection == LEFT */
			*newH = sizeOnOppAxis - 1 - h;
			*reverse = False;
		}
		break;
	default:
		{
			char *buf;

			intCat(&buf, "slideRC: rotate ", *rotate);
			DISPLAY_WARNING(buf);
			free(buf);
		}
	}
}

static void
readRC(RubikWidget w, int face, int dir, int h, int orient, int size)
{
	int g, sizeOfRow;

	sizeOfRow = sizeRow(w, face);
	if (dir == TOP || dir == BOTTOM)
		for (g = 0; g < size; g++)
			w->rubik.rowLoc[orient][g] =
				w->rubik.cubeLoc[face][g * sizeOfRow + h];
	else			/* dir == RIGHT || dir == LEFT */
		for (g = 0; g < size; g++)
			w->rubik.rowLoc[orient][g] =
				w->rubik.cubeLoc[face][h * sizeOfRow + g];
}

static void
rotateRC(RubikWidget w, int rotate, int orient, int size)
{
	int g;

	for (g = 0; g < size; g++)
		w->rubik.rowLoc[orient][g].rotation =
			(char) ((w->rubik.rowLoc[orient][g].rotation +
			rotate) % MAX_ORIENT);
}

static void
reverseRC(RubikWidget w, int orient, int size)
{
	int g;
	RubikLoc temp;

	for (g = 0; g < size / 2; g++) {
		temp = w->rubik.rowLoc[orient][size - 1 - g];
		w->rubik.rowLoc[orient][size - 1 - g] = w->rubik.rowLoc[orient][g];
		w->rubik.rowLoc[orient][g] = temp;
	}
}

static void
writeRC(RubikWidget w, int face, int dir, int h, int orient, int size)
{
	int g, position, sizeOfRow;

	sizeOfRow = sizeRow(w, face);
	if (dir == TOP || dir == BOTTOM) {
		for (g = 0; g < size; g++) {
			position = g * sizeOfRow + h;
			w->rubik.cubeLoc[face][position] = w->rubik.rowLoc[orient][g];
			drawSquare(w, face, position, FALSE);
		}
	} else {		/* dir == RIGHT || dir == LEFT */
		for (g = 0; g < size; g++) {
			position = h * sizeOfRow + g;
			w->rubik.cubeLoc[face][position] = w->rubik.rowLoc[orient][g];
			drawSquare(w, face, position, FALSE);
		}
	}
}

static void
movePieces(RubikWidget w, int face, int position, int direction,
		Boolean control, int fast)
{
	int dir = direction, oldFace = face;
	int newFace, newDirection, rotate;
	int h, k, newH = 0;
	int i, j, sizeOfRow, sizeOfColumn, sizeOnAxis, sizeOnOppAxis;
	Boolean reverse = False;

#ifdef HAVE_OPENGL
	if (!control && fast != INSTANT && w->rubik.dim == 4) {
		movePiecesGL((RubikGLWidget) w, oldFace, position, dir,
			control, fast);
	}
#endif
	faceSizes(w, oldFace, &sizeOfRow, &sizeOfColumn);
	i = position % sizeOfRow;
	j = position / sizeOfRow;
	h = (dir == TOP || dir == BOTTOM) ? i : j;
	if (dir == TOP || dir == BOTTOM) {
		sizeOnAxis = sizeOfColumn;
		sizeOnOppAxis = sizeOfRow;
	} else {
		sizeOnAxis = sizeOfRow;
		sizeOnOppAxis = sizeOfColumn;
	}
	/* rotate sides CW or CCW or HALF */

	if (h == sizeOnOppAxis - 1) {
		newDirection = (dir == TOP || dir == BOTTOM) ?
			TOP : RIGHT;
		if (w->rubik.degreeTurn == 180)
			rotateFace(w, rowToRotate[oldFace][newDirection], HALF);
		else if (dir == TOP || dir == RIGHT)
			rotateFace(w, rowToRotate[oldFace][newDirection], CW);
		else		/* dir == BOTTOM || dir == LEFT */
			rotateFace(w, rowToRotate[oldFace][newDirection], CCW);
	}
	if (h == 0) {
		newDirection = (dir == TOP || dir == BOTTOM) ?
			BOTTOM : LEFT;
		if (w->rubik.degreeTurn == 180)
			rotateFace(w, rowToRotate[oldFace][newDirection], HALF);
		else if (dir == TOP || dir == RIGHT)
			rotateFace(w, rowToRotate[oldFace][newDirection], CCW);
		else		/* dir == BOTTOM || dir == LEFT */
			rotateFace(w, rowToRotate[oldFace][newDirection], CW);
	}
	/* Slide rows */
	readRC(w, oldFace, dir, h, 0, sizeOnAxis);
	if (w->rubik.degreeTurn == 180) {
		int sizeOnDepthAxis;

		slideRC(w, oldFace, dir, h, sizeOnOppAxis,
			&newFace, &newDirection, &newH, &rotate, &reverse);
		sizeOnDepthAxis = sizeFace(w, newFace) / sizeOnOppAxis;
		readRC(w, newFace, newDirection, newH, 1, sizeOnDepthAxis);
		rotateRC(w, rotate, 0, sizeOnAxis);
		if (reverse)
			reverseRC(w, 0, sizeOnAxis);
		oldFace = newFace;
		dir = newDirection;
		h = newH;
		for (k = 2; k <= MAX_ORIENT + 1; k++) {
			slideRC(w, oldFace, dir, h, sizeOnOppAxis,
			  &newFace, &newDirection, &newH, &rotate, &reverse);
			if (k != MAX_ORIENT && k != MAX_ORIENT + 1)
				readRC(w, newFace, newDirection, newH, k,
				     ((k & 1) == 1) ? sizeOnDepthAxis : sizeOnAxis);
			rotateRC(w, rotate, k - 2,
				 ((k & 1) == 1) ? sizeOnDepthAxis : sizeOnAxis);
			if (k != MAX_ORIENT + 1)
				rotateRC(w, rotate, k - 1,
				     ((k & 1) == 1) ? sizeOnAxis : sizeOnDepthAxis);
			if (reverse) {
				reverseRC(w, k - 2,
				     ((k & 1) == 1) ? sizeOnDepthAxis : sizeOnAxis);
				if (k != MAX_ORIENT + 1)
					reverseRC(w, k - 1,
						((k & 1) == 1) ? sizeOnAxis : sizeOnDepthAxis);
			}
			writeRC(w, newFace, newDirection, newH, k - 2,
				((k & 1) == 1) ? sizeOnDepthAxis : sizeOnAxis);
			oldFace = newFace;
			dir = newDirection;
			h = newH;
		}
	} else {
		for (k = 1; k <= MAX_ORIENT; k++) {
			slideRC(w, oldFace, dir, h, sizeOnOppAxis,
			  &newFace, &newDirection, &newH, &rotate, &reverse);
			if (k != MAX_ORIENT)
				readRC(w, newFace, newDirection, newH, k, sizeOnAxis);
			rotateRC(w, rotate, k - 1, sizeOnAxis);
			if (reverse)
				reverseRC(w, k - 1, sizeOnAxis);
			writeRC(w, newFace, newDirection, newH, k - 1, sizeOnAxis);
			oldFace = newFace;
			dir = newDirection;
			h = newH;
		}
	}
#ifdef HAVE_OPENGL
	if (!control && fast == INSTANT && w->rubik.dim == 4) {
		drawAllPiecesGL((RubikGLWidget) w);
	}
#endif
#ifdef DEBUG
	printCube(w);
#endif
}

static Boolean
moveAllPieces(RubikWidget w, const int face, const int position,
		const int direction, const Boolean control, int fast)
{
	int sizeOfRow, sizeOfColumn, sizeOnOppAxis, factor;
	Boolean newControl;

	faceSizes(w, face, &sizeOfRow, &sizeOfColumn);
	if (direction == TOP || direction == BOTTOM) {
		sizeOnOppAxis = sizeOfRow;
		factor = 1;
	} else {
		sizeOnOppAxis = sizeOfColumn;
		factor = sizeOfRow;
	}
	w->rubik.degreeTurn = (checkFaceSquare(w,
				rowToRotate[face][direction])) ? 90 : 180;
	if (control || sizeOnOppAxis == 1) {
		int k, newPosition, newFast;

		newControl = True;
		newFast = fast;

#ifdef HAVE_OPENGL
		if (newControl && w->rubik.dim == 4) {
			movePiecesGL((RubikGLWidget) w, face, position,
				direction, newControl, fast);
			newFast = INSTANT;
		}
#endif
		for (k = 0; k < sizeOnOppAxis; k++) {
			newPosition = k * factor;
			movePieces(w, face, newPosition, direction,
				newControl, newFast);
		}
	} else {
		newControl = False;
		movePieces(w, face, position, direction, control, fast);
	}
	return newControl;
}

void
movePuzzle(RubikWidget w, const int face, const int position,
		const int direction, const Boolean control, int fast)
{
	setPuzzleMove(w, ACTION_MOVED, face, position, direction,
		moveAllPieces(w, face, position, direction, control, fast),
		fast);
#ifdef USE_SOUND
	if (w->rubik.sound) {
		playSound(MOVESOUND);
	}
#endif
	setMove(&undo, direction, control, face, position);
	flushMoves(w, &redo, FALSE);
}

void
movePuzzleDelay(RubikWidget w, const int face, const int position,
		const int direction, const Boolean control)
{
	movePuzzle(w, face, position, direction, control, NORMAL);
	Sleep((unsigned int) w->rubik.delay);
}

static Boolean
selectPieces(RubikWidget w, const int x, const int y,
		int *face, int *position)
{
	if (w->rubik.dim == 2)
		return selectPieces2D((Rubik2DWidget) w, x, y,
			face, position);
	else if (w->rubik.dim == 3)
		return selectPieces3D((Rubik3DWidget) w, x, y,
			face, position);
#ifdef HAVE_OPENGL
	else if (w->rubik.dim == 4)
		return selectPiecesGL((RubikGLWidget) w, x, y,
			face, position);
#endif
	return False;
}

static int
checkMoveDir(RubikWidget w, int face, int position1, int position2,
		int *direction)
{
	int count = 0;
	int column1, column2, row1, row2, sizeOfRow;

	sizeOfRow = sizeRow(w, face);
	column1 = position1 % sizeOfRow;
	column2 = position2 % sizeOfRow;
	row1 = position1 / sizeOfRow;
	row2 = position2 / sizeOfRow;
	if (column1 == column2 && row1 != row2) {
		*direction = (row2 > row1) ? BOTTOM : TOP;
		count = 1;
	} else if (row1 == row2 && column1 != column2) {
		*direction = (column2 > column1) ? RIGHT : LEFT;
		count = 1;
	} else if (row1 == row2 && column1 == column2)
		count = 2;
	return count;
}


static Boolean
narrowSelection(RubikWidget w, int *face, int *position, int *direction)
{
	if (w->rubik.dim == 2)
		return narrowSelection2D((Rubik2DWidget) w, face, position, direction);
	else if (w->rubik.dim == 3)
		return narrowSelection3D((Rubik3DWidget) w, face, position, direction);
#ifdef HAVE_OPENGL
	else if (w->rubik.dim == 4)
		return narrowSelectionGL((RubikGLWidget) w, face, position, direction);
#endif
	return False;
}

static Boolean
positionPieces(RubikWidget w, int x, int y, int *face, int *position, int *direction)
{
	if (!selectPieces(w, x, y, face, position))
		return False;
	return narrowSelection(w, face, position, direction);
}

void
movePuzzleInput(RubikWidget w, int x, int y, int direction, int control)
{
	int face, position;

	if (!w->rubik.practice && !control && checkSolved(w, w->rubik.orient)) {
		moveNoPieces(w);
		return;
	}
	if (!positionPieces(w, x, y, &face, &position, &direction))
		return;
	control = (control) ? 1 : 0;
	movePuzzle(w, face, position, direction, control, NORMAL);
	if (!control && checkSolved(w, w->rubik.orient)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

void
resetPieces(RubikWidget w)
{
	int face, position, orient, sizeOfFace;

	for (face = 0; face < MAX_FACES; face++) {
		sizeOfFace = sizeFace(w, face);
		if (w->rubik.cubeLoc[face])
			free(w->rubik.cubeLoc[face]);
		if (!(w->rubik.cubeLoc[face] = (RubikLoc *)
				malloc(sizeof (RubikLoc) *
				(size_t) sizeOfFace))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
		if (startLoc[face])
			free(startLoc[face]);
		if (!(startLoc[face] = (RubikLoc *)
				malloc(sizeof (RubikLoc) *
				(size_t) sizeOfFace))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
		for (position = 0; position < sizeOfFace; position++) {
			w->rubik.cubeLoc[face][position].face = (char) face;
			w->rubik.cubeLoc[face][position].rotation = STRT - MAX_ORIENT;
		}
	}
	for (orient = 0; orient < MAX_ORIENT; orient++) {
		if (w->rubik.rowLoc[orient])
			free(w->rubik.rowLoc[orient]);
		if (!(w->rubik.rowLoc[orient] = (RubikLoc *)
				malloc(sizeof (RubikLoc) *
				(size_t) MAX_MAX_SIZE))) {
			DISPLAY_ERROR("Not enough memory, exiting.");
		}
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	w->rubik.started = False;
	w->rubik.currentFace = IGNORE_DIR;
	w->rubik.currentDirection = IGNORE_DIR;
}

static void
readFile(RubikWidget w, FILE *fp, char *name)
{
	int c, i, sizeX, sizeY, sizeZ, orient, practice, moves;
	char *buf1 = NULL, *buf2 = NULL;

	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &sizeX) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for sizeX\n");
		return;
	}
	if (sizeX >= MIN_FACETS) {
		for (i = w->rubik.sizeX; i < sizeX; i++) {
			setPuzzle(w, ACTION_INCX);
		}
		for (i = w->rubik.sizeX; i > sizeX; i--) {
			setPuzzle(w, ACTION_DECX);
		}
	} else {
		stringCat(&buf1, name, " corrupted: sizeX ");
		intCat(&buf2, buf1, sizeX);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_FACETS);
		free(buf1);
		stringCat(&buf1, buf2, " and MAXINT");
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &sizeY) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for sizeY\n");
		return;
	}
	if (sizeY >= MIN_FACETS) {
		for (i = w->rubik.sizeY; i < sizeY; i++) {
			setPuzzle(w, ACTION_INCY);
		}
		for (i = w->rubik.sizeY; i > sizeY; i--) {
			setPuzzle(w, ACTION_DECY);
		}
	} else {
		stringCat(&buf1, name, " corrupted: sizeY ");
		intCat(&buf2, buf1, sizeY);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_FACETS);
		free(buf1);
		stringCat(&buf1, buf2, " and MAXINT");
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &sizeZ) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for sizeZ\n");
		return;
	}
	if (sizeZ >= MIN_FACETS) {
		for (i = w->rubik.sizeZ; i < sizeZ; i++) {
			setPuzzle(w, ACTION_INCZ);
		}
		for (i = w->rubik.sizeZ; i > sizeZ; i--) {
			setPuzzle(w, ACTION_DECZ);
		}
	} else {
		stringCat(&buf1, name, " corrupted: sizeZ ");
		intCat(&buf2, buf1, sizeZ);
		free(buf1);
		stringCat(&buf1, buf2, " should be between ");
		free(buf2);
		intCat(&buf2, buf1, MIN_FACETS);
		free(buf1);
		stringCat(&buf1, buf2, " and MAXINT");
		free(buf2);
		DISPLAY_WARNING(buf1);
		free(buf1);
		return;
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &orient) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for orient\n");
		return;
	}
	if (w->rubik.orient != (Boolean) orient) {
		setPuzzle(w, ACTION_ORIENTIZE);
	}
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &practice) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for practice\n");
		return;
	}
	if (w->rubik.practice != (Boolean) practice) {
		setPuzzle(w, ACTION_PRACTICE);
	}
#ifdef WINVER
	resetPieces(w);
#endif
	while ((c = getc(fp)) != EOF && c != SYMBOL);
	if (fscanf(fp, "%d", &moves) != 1) {
		(void) fprintf(stderr,
			"corrupt record, expecting an integer for moves\n");
		return;
	}
	if (!scanStartPosition(fp, w))
		return;
	setPuzzle(w, ACTION_RESTORE);
	setPuzzle(w, ACTION_RESTORE);
#ifdef WINVER
	setStartPosition(w);
#endif
	if (!scanMoves(fp, w, moves))
		return;
	(void) printf("%s: sizeX %d, sizeY %d, sizeZ %d, orient %s, practice %s, moves %d\n",
		name, sizeX, sizeY, sizeZ,
		BOOL_STRING(orient), BOOL_STRING(practice), moves);
}

static void
getPieces(RubikWidget w)
{
	FILE *fp;
	char *buf1 = NULL, *buf2 = NULL;
	char *fname, *lname, *name;

	stringCat(&buf1, CURRENTDELIM, LOGFILE);
	lname = buf1;
	stringCat(&buf1, LOGPATH, FINALDELIM);
	stringCat(&buf2, buf1, LOGFILE);
	free(buf1);
	fname = buf2;
	/* Try current directory first. */
	name = lname;
	if ((fp = fopen(name, "r")) == NULL) {
		name = fname;
		if ((fp = fopen(name, "r")) == NULL) {
			stringCat(&buf1, "Cannot read ", lname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Cannot read ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			return;
		}
#endif
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	readFile(w, fp, name);
	(void) fclose(fp);
	free(lname);
	free(fname);
	w->rubik.cheat = True; /* Assume the worst. */
	setPuzzle(w, ACTION_CHEAT);
}

static void
writePieces(RubikWidget w)
{
	FILE *fp;
	char *buf1 = NULL, *buf2 = NULL;
	char *fname, *lname, *name;

	stringCat(&buf1, CURRENTDELIM, LOGFILE);
	lname = buf1;
	stringCat(&buf1, LOGPATH, FINALDELIM);
	stringCat(&buf2, buf1, LOGFILE);
	free(buf1);
	fname = buf2;
	/* Try current directory first. */
	name = lname;
	if ((fp = fopen(name, "w")) == NULL) {
		name = fname;
		if ((fp = fopen(name, "w")) == NULL) {
			stringCat(&buf1, "Cannot write to ", lname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Cannot write to ", lname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, fname);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
			return;
		}
#endif
	}
	(void) fprintf(fp, "sizeX%c %d\n", SYMBOL, w->rubik.sizeX);
	(void) fprintf(fp, "sizeY%c %d\n", SYMBOL, w->rubik.sizeY);
	(void) fprintf(fp, "sizeZ%c %d\n", SYMBOL, w->rubik.sizeZ);
	(void) fprintf(fp, "orient%c %d\n", SYMBOL,
		(w->rubik.orient) ? 1 : 0);
	(void) fprintf(fp, "practice%c %d\n", SYMBOL,
		(w->rubik.practice) ? 1 : 0);
	(void) fprintf(fp, "moves%c %d\n", SYMBOL,
		numMoves(&undo));
	printStartPosition(fp, w);
	printMoves(fp, &undo);
	(void) fclose(fp);
	(void) printf("Saved to %s\n", name);
	free(lname);
	free(fname);
}

static void
undoPieces(RubikWidget w)
{
	if (madeMoves(&undo)) {
		int face, position, direction, control;

		getMove(&undo, &direction, &control,
			&face, &position);
		setMove(&redo, direction, control,
			face, position);
		direction = (direction < MAX_ORIENT) ?
			(direction + MAX_ORIENT / 2) % MAX_ORIENT :
			3 * MAX_ORIENT - direction;
		setPuzzleMove(w, ACTION_UNDO, face, position, direction,
			moveAllPieces(w, face, position, direction, control,
			DOUBLE), DOUBLE);
		if (!control && checkSolved(w, w->rubik.orient)) {
			setPuzzle(w, ACTION_SOLVED);
		}
	}
}

static void
redoPieces(RubikWidget w)
{
	if (madeMoves(&redo)) {
		int face, position, direction, control;

		getMove(&redo, &direction, &control,
			&face, &position);
		setMove(&undo, direction, control,
			face, position);
		setPuzzleMove(w, ACTION_REDO, face, position, direction,
			moveAllPieces(w, face, position, direction, control,
			DOUBLE), DOUBLE);
		if (!control && checkSolved(w, w->rubik.orient)) {
			setPuzzle(w, ACTION_SOLVED);
		}
	}
}

static void
clearPieces(RubikWidget w)
{
	setPuzzle(w, ACTION_CLEAR);
}

static void
practicePieces(RubikWidget w)
{
	setPuzzle(w, ACTION_PRACTICE);
}

static void
randomizePieces(RubikWidget w)
{
	int face, position, direction;
	int big = w->rubik.sizeX * w->rubik.sizeY * w->rubik.sizeZ *
			3 + NRAND(2);

	w->rubik.cheat = False;
	setPuzzle(w, ACTION_CHEAT);
	if (w->rubik.practice)
		practicePieces(w);
	setPuzzle(w, ACTION_RESET);
	if (big > 100)
		big = 100;
#ifdef DEBUG
	big = 3;
#endif
	while (big--) {
		face = NRAND(MAX_FACES);
		position = NRAND(sizeFace(w, face));
		direction = NRAND(MAX_ORIENT);
		movePuzzle(w, face, position, direction, FALSE, INSTANT);
	}
	flushMoves(w, &undo, TRUE);
	flushMoves(w, &redo, FALSE);
	setPuzzle(w, ACTION_RANDOMIZE);
	if (checkSolved(w, w->rubik.orient)) {
		setPuzzle(w, ACTION_SOLVED);
	}
}

static void
printPieces(RubikWidget w)
{
	printSomePieces(w);
}

static void
solvePieces(RubikWidget w)
{
	if (checkSolved(w, w->rubik.orient))
		return;
	if (w->rubik.sizeX == w->rubik.sizeY && w->rubik.sizeX == w->rubik.sizeZ &&
			w->rubik.sizeX < 5)
		solveSomePieces(w);
	else {
		setPuzzle(w, ACTION_SOLVE_MESSAGE);
	}
}

static void
orientizePieces(RubikWidget w)
{
	setPuzzle(w, ACTION_ORIENTIZE);
}

static void
incrementPieces(RubikWidget w)
{
	setPuzzle(w, ACTION_INCX);
	setPuzzle(w, ACTION_INCY);
	setPuzzle(w, ACTION_INCZ);
}

static void
decrementPieces(RubikWidget w)
{
	if (w->rubik.sizeX > MIN_FACETS) {
		setPuzzle(w, ACTION_DECX);
	}
	if (w->rubik.sizeY > MIN_FACETS) {
		setPuzzle(w, ACTION_DECY);
	}
	if (w->rubik.sizeZ > MIN_FACETS) {
		setPuzzle(w, ACTION_DECZ);
	}
}

static void
viewPieces(RubikWidget w)
{
	setPuzzle(w, ACTION_VIEW);
}

static void
speedPieces(RubikWidget w)
{
	w->rubik.delay -= 5;
	if (w->rubik.delay < 0)
		w->rubik.delay = 0;
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	setPuzzle(w, ACTION_SPEED);
#endif
}

static void
slowPieces(RubikWidget w)
{
	w->rubik.delay += 5;
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	setPuzzle(w, ACTION_SPEED);
#endif
}

static void
soundPieces(RubikWidget w)
{
	w->rubik.sound = !w->rubik.sound;
	setPuzzle(w, ACTION_SOUND);
}

#ifdef WINVER
static void
setValuesPuzzle(RubikWidget w)
{
	struct tagColor {
		int red, green, blue;
	} color;
	char szBuf[80], buf[20], charbuf[2];
	int face;

	w->rubik.sizeX = (int) GetPrivateProfileInt(SECTION,
		"sizeX", DEFAULT_FACETS, INIFILE);
	w->rubik.sizeY = (int) GetPrivateProfileInt(SECTION,
		"sizeY", DEFAULT_FACETS, INIFILE);
	w->rubik.sizeZ = (int) GetPrivateProfileInt(SECTION,
		"sizeZ", DEFAULT_FACETS, INIFILE);
	w->rubik.orient = (BOOL) GetPrivateProfileInt(SECTION,
		"orient", DEFAULT_ORIENT, INIFILE);
	w->rubik.practice = (BOOL) GetPrivateProfileInt(SECTION,
		"practice", DEFAULT_PRACTICE, INIFILE);
#ifdef HAVE_OPENGL
	w->rubik.dim = (int) GetPrivateProfileInt(SECTION, "dim", 4, INIFILE);
#else
	w->rubik.dim = (int) GetPrivateProfileInt(SECTION, "dim", 3, INIFILE);
#endif
	w->rubik.view = (int) GetPrivateProfileInt(SECTION, "view", 1, INIFILE);
	w->rubik.mono = (BOOL) GetPrivateProfileInt(SECTION,
		"mono", DEFAULT_MONO, INIFILE);
	w->rubik.reverse = (BOOL) GetPrivateProfileInt(SECTION,
		"reverseVideo", DEFAULT_REVERSE, INIFILE);
	/* Cyan */
	(void) GetPrivateProfileString(SECTION,
		"frameColor", "0 255 255", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->rubik.frameGC = RGB(color.red, color.green, color.blue);
	/* gray25 */
	(void) GetPrivateProfileString(SECTION,
		"pieceBorder", "64 64 64", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->rubik.borderGC = RGB(color.red, color.green, color.blue);
	/* #AEB2C3 */
	(void) GetPrivateProfileString(SECTION,
		"background", "174 178 195", szBuf, sizeof (szBuf), INIFILE);
	(void) sscanf(szBuf, "%d %d %d",
		&(color.red), &(color.green), &(color.blue));
	w->rubik.inverseGC = RGB(color.red, color.green, color.blue);
	for (face = 0; face < MAX_FACES; face++) {
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 20, "faceColor%d", face);
#else
		(void) sprintf(buf, "faceColor%d", face);
#endif
		(void) GetPrivateProfileString(SECTION,
			buf, faceColorString[face],
			szBuf, sizeof (szBuf), INIFILE);
		(void) sscanf(szBuf, "%d %d %d",
			&(color.red), &(color.green), &(color.blue));
		w->rubik.faceGC[face] =
			RGB(color.red, color.green, color.blue);
#ifdef HAVE_SNPRINTF
		(void) snprintf(buf, 20, "faceChar%d", face);
#else
		(void) sprintf(buf, "faceChar%d", face);
#endif
		charbuf[0] = faceColorChar[face];
		charbuf[1] = '\0';
		(void) GetPrivateProfileString(SECTION,
			buf, charbuf, szBuf, sizeof (szBuf), INIFILE);
		w->rubik.faceChar[face] = szBuf[0];
	}
	w->rubik.delay = (int) GetPrivateProfileInt(SECTION,
		"delay", 10, INIFILE);
	w->rubik.sound = (BOOL) GetPrivateProfileInt(SECTION,
		"sound", FALSE, INIFILE);
	(void) GetPrivateProfileString(SECTION,
		"moveSound", MOVESOUND, szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->rubik.moveSound, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"userName", "guest", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->rubik.userName, szBuf, 80);
	(void) GetPrivateProfileString(SECTION,
		"scoreFile", "", szBuf, sizeof (szBuf), INIFILE);
	(void) strncpy(w->rubik.scoreFile, szBuf, 80);
}

void
destroyPuzzle(HBRUSH brush)
{
	deleteMoves(&undo);
	deleteMoves(&redo);
	(void) DeleteObject(brush);
	PostQuitMessage(0);
}

void
resizePuzzle(RubikWidget w)
{
	if (w->rubik.dim == 2)
		resizePuzzle2D((Rubik2DWidget) w);
	else if (w->rubik.dim == 3)
		resizePuzzle3D((Rubik3DWidget) w);
#ifdef HAVE_OPENGL
	else if (w->rubik.dim == 4)
		resizePuzzleGL((RubikGLWidget) w);
#endif
}

void
sizePuzzle(RubikWidget w)
{
	resetPieces(w);
	resizePuzzle(w);
}

void
exposePuzzle(RubikWidget w)
{
	if (w->rubik.dim == 2)
		exposePuzzle2D((Rubik2DWidget) w);
	else if (w->rubik.dim == 3)
		exposePuzzle3D((Rubik3DWidget) w);
#ifdef HAVE_OPENGL
	else if (w->rubik.dim == 4)
		exposePuzzleGL((RubikGLWidget) w);
#endif
}

#else
static void
getColor(RubikWidget w, int face)
{
	XGCValues values;
	XtGCMask valueMask;
	XColor colorCell, rgb;

	valueMask = GCForeground | GCBackground;
	if (w->rubik.reverse) {
		values.background = w->rubik.foreground;
	} else {
		values.background = w->rubik.background;
	}
	if (!w->rubik.mono || w->rubik.dim == 4) {
		if (!w->rubik.faceName[face]) {
			char *buf1;

			intCat(&buf1, "Color name null for face ", face);
			DISPLAY_WARNING(buf1);
			free(buf1);
		} else if (XAllocNamedColor(XtDisplay(w),
				DefaultColormapOfScreen(XtScreen(w)),
				w->rubik.faceName[face], &colorCell, &rgb)) {
			values.foreground = w->rubik.faceColor[face] =
				colorCell.pixel;
			if (w->rubik.faceGC[face])
				XtReleaseGC((Widget) w, w->rubik.faceGC[face]);
			w->rubik.faceGC[face] = XtGetGC((Widget) w, valueMask,
				&values);
			return;
		} else {
			char *buf1, *buf2;

			stringCat(&buf1, "Color name \"",
				w->rubik.faceName[face]);
			stringCat(&buf2, buf1, "\" is not defined for face ");
			free(buf1);
			intCat(&buf1, buf2, face);
			free(buf2);
			DISPLAY_WARNING(buf1);
			free(buf1);
		}
	}
	if (w->rubik.reverse) {
		values.background = w->rubik.foreground;
		values.foreground = w->rubik.background;
	} else {
		values.background = w->rubik.background;
		values.foreground = w->rubik.foreground;
	}
	if (w->rubik.faceGC[face])
		XtReleaseGC((Widget) w, w->rubik.faceGC[face]);
	w->rubik.faceGC[face] = XtGetGC((Widget) w, valueMask, &values);
}

void
setAllColors(RubikWidget w)
{
	XGCValues values;
	XtGCMask valueMask;
	int face;

	valueMask = GCForeground | GCBackground;

	if (w->rubik.reverse) {
		values.background = w->rubik.background;
		values.foreground = w->rubik.foreground;
	} else {
		values.foreground = w->rubik.background;
		values.background = w->rubik.foreground;
	}
	if (w->rubik.inverseGC)
		XtReleaseGC((Widget) w, w->rubik.inverseGC);
	w->rubik.inverseGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->rubik.mono) {
		if (w->rubik.reverse) {
			values.background = w->rubik.foreground;
			values.foreground = w->rubik.background;
		} else {
			values.foreground = w->rubik.foreground;
			values.background = w->rubik.background;
		}
	} else {
		values.foreground = w->rubik.frameColor;
		values.background = w->rubik.background;
	}
	if (w->rubik.frameGC)
		XtReleaseGC((Widget) w, w->rubik.frameGC);
	w->rubik.frameGC = XtGetGC((Widget) w, valueMask, &values);
	if (w->rubik.mono) {
		if (w->rubik.reverse) {
			values.background = w->rubik.foreground;
			values.foreground = w->rubik.background;
		} else {
			values.foreground = w->rubik.foreground;
			values.background = w->rubik.background;
		}
	} else {
		values.foreground = w->rubik.borderColor;
		values.background = w->rubik.background;
	}
	if (w->rubik.borderGC)
		XtReleaseGC((Widget) w, w->rubik.borderGC);
	w->rubik.borderGC = XtGetGC((Widget) w, valueMask, &values);
	for (face = 0; face < MAX_FACES; face++)
		getColor(w, face);
	if (w->rubik.fontInfo)
		XSetFont(XtDisplay(w), w->rubik.borderGC,
			w->rubik.fontInfo->fid);
}

static Boolean
setValuesPuzzle(Widget current, Widget request, Widget renew)
{
	RubikWidget c = (RubikWidget) current, w = (RubikWidget) renew;
	Boolean redraw = False, setColors = False;
	int face;

	checkPieces(w);
	for (face = 0; face < MAX_FACES; face++) {
		if (strcmp(w->rubik.faceName[face], c->rubik.faceName[face])) {
			setColors = True;
			break;
		}
	}
	if (w->rubik.font != c->rubik.font ||
			w->rubik.borderColor != c->rubik.borderColor ||
			w->rubik.reverse != c->rubik.reverse ||
			w->rubik.mono != c->rubik.mono) {
		loadFont(w);
		setAllColors(w);
		redraw = True;
	} else if (w->rubik.background != c->rubik.background ||
			w->rubik.foreground != c->rubik.foreground ||
			setColors) {
		setAllColors(w);
		redraw = True;
	}
	if (w->rubik.orient != c->rubik.orient) {
		resetPieces(w);
		redraw = True;
	} else if (w->rubik.practice != c->rubik.practice) {
		resetPieces(w);
		redraw = True;
	}
	if (w->rubik.menu != ACTION_IGNORE) {
		int menu = w->rubik.menu;

		w->rubik.menu = ACTION_IGNORE;
		switch (menu) {
		case ACTION_GET:
			getPieces(w);
			/* or could get out of sync */
			c->rubik.sizeX = w->rubik.sizeX;
			c->rubik.sizeY = w->rubik.sizeY;
			c->rubik.sizeZ = w->rubik.sizeZ;
			break;
		case ACTION_WRITE:
			writePieces(w);
			break;
		case ACTION_UNDO:
			undoPieces(w);
			break;
		case ACTION_REDO:
			redoPieces(w);
			break;
		case ACTION_CLEAR:
			clearPieces(w);
			break;
		case ACTION_PRACTICE:
			practicePieces(w);
			break;
		case ACTION_RANDOMIZE:
			randomizePieces(w);
			break;
		case ACTION_PRINT:
			printPieces(w);
			break;
		case ACTION_SOLVE:
			solvePieces(w);
			break;
		case ACTION_INCREMENT:
			incrementPieces(w);
			break;
		case ACTION_DECREMENT:
			(void) decrementPieces(w);
			break;
		case ACTION_ORIENTIZE:
			orientizePieces(w);
			break;
		case ACTION_SPEED:
			speedPieces(w);
			break;
		case ACTION_SLOW:
			slowPieces(w);
			break;
		case ACTION_SOUND:
			soundPieces(w);
			break;
		case ACTION_VIEW:
			viewPieces(w);
			break;
		default:
			break;
		}
	}
	if (w->rubik.currentDirection == RESTORE_DIR) {
		setStartPosition(w);
		w->rubik.currentDirection = IGNORE_DIR;
	} else if (w->rubik.currentDirection == CLEAR_DIR) {
		w->rubik.currentDirection = IGNORE_DIR;
		resetPieces(w);
		redraw = True;
		w->rubik.currentDirection = IGNORE_DIR;
	} else if (w->rubik.currentDirection > IGNORE_DIR) {
		int face = w->rubik.currentFace;
		int position = w->rubik.currentPosition;
		int direction = w->rubik.currentDirection;

		w->rubik.currentFace = IGNORE_DIR;
		w->rubik.currentPosition = IGNORE_DIR;
		w->rubik.currentDirection = IGNORE_DIR;
		(void) moveAllPieces(w, face, position, direction,
			w->rubik.currentControl, w->rubik.currentFast);
	}
	return redraw;
}

static void
destroyPuzzle(Widget old)
{
	RubikWidget w = (RubikWidget) old;
	Display *display = XtDisplay(w);
	int face;

#if defined( USE_SOUND ) && defined( USE_ESOUND )
	if (w->rubik.dim == 2)
		(void) shutdown_sound();
#endif
	for (face = 0; face < MAX_FACES; face++)
		XtReleaseGC(old, w->rubik.faceGC[face]);
	XtReleaseGC(old, w->rubik.borderGC);
	XtReleaseGC(old, w->rubik.frameGC);
	XtReleaseGC(old, w->rubik.inverseGC);
	XtRemoveCallbacks(old, XtNselectCallback, w->rubik.select);
	if (w->rubik.fontInfo) {
		XUnloadFont(display, w->rubik.fontInfo->fid);
		XFreeFont(display, w->rubik.fontInfo);
	}
	deleteMoves(&undo);
	deleteMoves(&redo);
}

void
quitPuzzle(RubikWidget w, XEvent *event, char **args, int nArgs)
{
	XtCloseDisplay(XtDisplay(w));
	exit(0);
}
#endif

#ifndef WINVER
static
#endif
void
initializePuzzle(
#ifdef WINVER
RubikWidget w, HBRUSH brush
#else
Widget request, Widget renew
#endif
)
{
	int face, orient;
#ifdef WINVER
	setValuesPuzzle(w);
#else
	RubikWidget w = (RubikWidget) renew;

	w->rubik.mono = (DefaultDepthOfScreen(XtScreen(w)) < 2 ||
		w->rubik.mono);
	w->rubik.fontInfo = NULL;
	for (face = 0; face < MAX_FACES; face++)
		w->rubik.faceGC[face] = NULL;
	w->rubik.borderGC = NULL;
	w->rubik.frameGC = NULL;
	w->rubik.inverseGC = NULL;
#endif
	w->rubik.focus = False;
	loadFont(w);
	for (face = 0; face < MAX_FACES; face++)
		w->rubik.cubeLoc[face] = NULL;
	for (orient = 0; orient < MAX_ORIENT; orient++)
		w->rubik.rowLoc[orient] = NULL;
	checkPieces(w);
	newMoves(&undo);
	newMoves(&redo);
	w->rubik.cheat = False;
	resetPieces(w);
#ifdef WINVER
	brush = CreateSolidBrush(w->rubik.inverseGC);
	SETBACK(w->core.hWnd, brush);
	(void) SRAND(time(NULL));
#else
	(void) SRAND(getpid());
#endif
#ifdef USE_SOUND
#ifdef USE_NAS
	dsp = XtDisplay(w);
#endif
#ifdef USE_ESOUND
	if (w->rubik.dim == 2)
		(void) init_sound();
#endif
#endif
}

void
hidePuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	setPuzzle(w, ACTION_HIDE);
}

void
selectPuzzle(RubikWidget w
#ifdef WINVER
, const int x, const int y, const int control
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int control = (int) (event->xkey.state & ControlMask);
#endif

	if (selectPieces(w, x, y,
		     &(w->rubik.currentFace), &(w->rubik.currentPosition))) {
		if (control || w->rubik.practice || !checkSolved(w, w->rubik.orient))
			drawSquareAll(w, w->rubik.currentFace, w->rubik.currentPosition,
				TRUE);
	} else {
		w->rubik.currentFace = IGNORE_DIR;
		w->rubik.currentDirection = IGNORE_DIR;
#ifdef HAVE_OPENGL
		if (w->rubik.dim == 4)
			drawAllPiecesGL((RubikGLWidget) w);
#endif
	}
}

void
releasePuzzle(RubikWidget w
#ifdef WINVER
, const int x, const int y, const int control
#else
, XEvent *event, char **args, int nArgs
#endif
)
{
	int face, position, count = -1, direction = -1;
#ifndef WINVER
	int x = event->xbutton.x, y = event->xbutton.y;
	int control = (int) (event->xkey.state & ControlMask);
#endif

	if (w->rubik.currentFace <= IGNORE_DIR)
		return;
	drawSquare(w, w->rubik.currentFace, w->rubik.currentPosition, FALSE);
	if (!control && !w->rubik.practice && checkSolved(w, w->rubik.orient))
		moveNoPieces(w);
	else if (selectPieces(w, x, y, &face, &position)) {
		if (face == w->rubik.currentFace) {
			if (position != w->rubik.currentPosition)
				count = checkMoveDir(w, face,
					w->rubik.currentPosition, position,
					&direction);
		} else {
			direction = facesToDirection[w->rubik.currentFace][face];
			count = (direction == AMBIGUOUS_DIR) ? 2 : 1;
			/* can look closer to minimize this, but then
			must also be able to handle double moves */
		}
		if (count == 1) {
			face = w->rubik.currentFace;
			position = w->rubik.currentPosition;
			w->rubik.currentFace = IGNORE_DIR;
			w->rubik.currentPosition = IGNORE_DIR;
			movePuzzle(w, face, position,
				direction, (control) ? 1 : 0, NORMAL);
			if (!control && checkSolved(w, w->rubik.orient)) {
				setPuzzle(w, ACTION_SOLVED);
			}
		} else if (count == 2) {
			setPuzzle(w, ACTION_AMBIGUOUS);
		} else if (count == 0) {
			moveNoPieces(w);
		}
	}
	w->rubik.currentFace = IGNORE_DIR;
	w->rubik.currentDirection = IGNORE_DIR;
#ifdef HAVE_OPENGL
	if (w->rubik.dim == 4 && count != 1) {
		drawAllPiecesGL((RubikGLWidget) w);
	}
#endif
}

#ifndef WINVER
void
practicePuzzleWithQuery(RubikWidget w
, XEvent *event, char **args, int nArgs
)
{
	if (!w->rubik.started)
		practicePieces(w);
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	else {
		setPuzzle(w, ACTION_PRACTICE_QUERY);
	}
#endif
}

void
practicePuzzleWithDoubleClick(RubikWidget w
, XEvent *event, char **args, int nArgs
)
{
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	if (!w->rubik.started)
#endif
		practicePieces(w);
}

void
randomizePuzzleWithQuery(RubikWidget w
, XEvent *event, char **args, int nArgs
)
{
	if (!w->rubik.started)
		randomizePieces(w);
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	else {
		setPuzzle(w, ACTION_RANDOMIZE_QUERY);
	}
#endif
}

void
randomizePuzzleWithDoubleClick(RubikWidget w
, XEvent *event, char **args, int nArgs
)
{
#if defined(HAVE_MOTIF) || defined(HAVE_ATHENA)
	if (!w->rubik.started)
#endif
		randomizePieces(w);
}
#endif

void
getPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	getPieces(w);
}

void
writePuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	writePieces(w);
}

void
undoPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	undoPieces(w);
}

void
redoPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	redoPieces(w);
}

void
clearPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	clearPieces(w);
}

void
randomizePuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	randomizePieces(w);
}

void
printPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	printPieces(w);
}

void
solvePuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	solvePieces(w);
}

void
practicePuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	practicePieces(w);
}

void
orientizePuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	orientizePieces(w);
}

void
decrementPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	decrementPieces(w);
}

void
incrementPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	incrementPieces(w);
}

void
viewPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	viewPieces(w);
}

void
speedUpPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	speedPieces(w);
}

void
slowDownPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	slowPieces(w);
}

void
toggleSoundPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	soundPieces(w);
}

void
enterPuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	w->rubik.focus = True;
	drawFrame(w, w->rubik.focus);
}

void
leavePuzzle(RubikWidget w
#ifndef WINVER
, XEvent *event, char **args, int nArgs
#endif
)
{
	w->rubik.focus = False;
	drawFrame(w, w->rubik.focus);
}

#ifdef WINVER
void
incrementAxisPuzzle(RubikWidget w, char axis)
{
	switch (axis) {
	case 'X':
		if (w->rubik.sizeX > MIN_FACETS)
			setPuzzle(w, ACTION_DECX);
		return;
	case 'x':
		setPuzzle(w, ACTION_INCX);
		return;
	case 'Y':
		if (w->rubik.sizeY > MIN_FACETS)
			setPuzzle(w, ACTION_DECY);
		return;
	case 'y':
		setPuzzle(w, ACTION_INCY);
		return;
	case 'Z':
		if (w->rubik.sizeZ > MIN_FACETS)
			setPuzzle(w, ACTION_DECZ);
		return;
	case 'z':
		setPuzzle(w, ACTION_INCZ);
		return;
	}
	return;
}

void
dimPuzzle(RubikWidget w)
{
	setPuzzle(w, ACTION_DIM);
}

#else
void
incrementXPuzzle(RubikWidget w, XEvent *event, char **args, int nArgs)
{
	if (event->xbutton.state & (ShiftMask | LockMask)) {
		if (w->rubik.sizeX <= MIN_FACETS)
			return;
		setPuzzle(w, ACTION_DECX);
	} else
		setPuzzle(w, ACTION_INCX);
}

void
incrementYPuzzle(RubikWidget w , XEvent *event, char **args, int nArgs)
{
	if (event->xbutton.state & (ShiftMask | LockMask)) {
		if (w->rubik.sizeY <= MIN_FACETS)
			return;
		setPuzzle(w, ACTION_DECY);
	} else
		setPuzzle(w, ACTION_INCY);
}

void
incrementZPuzzle(RubikWidget w , XEvent *event, char **args, int nArgs)
{
	if (event->xbutton.state & (ShiftMask | LockMask)) {
		if (w->rubik.sizeZ <= MIN_FACETS)
			return;
		setPuzzle(w, ACTION_DECZ);
	} else
		setPuzzle(w, ACTION_INCZ);
}

void
movePuzzleCcw(RubikWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, CCW,
		(int) (event->xbutton.state & ControlMask));
}

void
movePuzzleCw(RubikWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, CW,
		(int) (event->xkey.state & ControlMask));
}
#endif
