/*
 * @(#)xskewb.c
 *
 * Copyright 1993 - 2010  David A. Bagley, bagleyd@tux.org
 *
 * 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.
 */

/* Driver file for Skewb */

#ifndef WINVER
#include "version.h"
static const char aboutHelp[] = {
"Skewb Version " VERSION "\n"
"Send bugs (reports or fixes) to the author: "
"David Bagley <bagleyd@tux.org>\n"
"The latest version is at: "
"http://www.tux.org/~bagleyd/puzzles.html"
};

static const char optionsHelp[] = {
"[-geometry [{width}][x{height}][{+-}{xoff}[{+-}{yoff}]]]\n"
"[-display [{host}]:[{vs}]] [-[no]mono] [-[no]{reverse|rv}]\n"
"[-{foreground|fg} {color}] [-{background|bg} {color}]\n"
"[-face{0|1|2|3|4|5} {color}] [-{border|bd} {color}]\n"
"[-delay msecs] [-[no]sound] [-moveSound {filename}]\n"
"[-{font|fn} {fontname}] [-view {int}] [-[no]orient]\n"
"[-[no]practice] [-userName {string}]\n"
"[-scoreFile {filename}] [-scores] [-version]"
};
#endif

#if defined(HAVE_MOTIF) || defined(WINVER)
static const char descriptionHelp[] = {
"The original puzzle has each face cut by a diamond, so "
"that there are 5 pieces,  4 corner pieces and\n"
"one diamond piece in the center.  This was designed by "
"Uwe Meffert and called the Pyraminx Cube.  Douglas\n"
"Hofstadter later coined it a Skewb and it stuck.  The "
"puzzle has period 3 turning (i.e. each half turns\n"
"with 120 degree intervals).  The Skewb has  2^5*3^8*6!/2^6 "
"or 3,149,280  different combinations (with\n"
"centers oriented 2^5*3^8*6!/2 or 100,766,960 different "
"combinations).\n"
"More recently, Disney released Mickey's Challenge, its "
"a spherical skewb with a pretty good internal\n"
"mechanism.  Mickey's challenge has 2^5*3^8*6!/36 "
"5,598,720 visually different combinations).  It also\n"
"comes with a pretty neat book.  Also released "
"is the Creative Puzzle Ball or Meffert's Challenge which\n"
"has 4 rings in different colors.\n"
"Mach Balls of the Hungarian Gyula Mach are similar "
"but they do not have a ratchet mechanism and do not\n"
"turn as easily or smoothly.  One must match the 12 "
"different symbols of 4 each  at the 12 intersection\n"
"points.\n"
};

static const char featuresHelp[] = {
"Press \"mouse-left\" button to move a piece.  Release "
"\"mouse-left\" button on a piece  on the same face.\n"
"(Clicks on diamonds are ignored).  The pieces will then "
"turn towards where the mouse button was released.\n"
"\n"
"Click \"mouse-center\" button, or press \"P\" or \"p\" "
"keys, to toggle the practice mode  (in practice mode the\n"
"the record should say \"practice\").  This is good for "
"learning moves and experimenting.\n"
"\n"
"Click \"mouse-right\" button,  or press \"Z\" or \"z\" "
"keys,  to randomize the puzzle  (this must be done first\n"
"to set a new record).\n"
"\n"
"Press \"G\" or \"g\" keys to get a saved puzzle.\n"
"\n"
"Press \"W\" or \"w\" keys to save (write) a puzzle.\n"
"\n"
"Press \"U\" or \"u\" keys to undo a move.\n"
"\n"
"Press \"R\" or \"r\" keys to redo a move.\n"
"\n"
"Press \"C\" or \"c\" keys to clear the puzzle.\n"
"\n"
"\"S\" or \"s\" keys to start auto-solver.\n"
"\n"
"Press \"O\" or \"o\" keys to toggle  the orient mode.  One "
"has  to orient the faces  in orient mode,  besides\n"
"getting  all the faces to be the  same color.  To do this "
"one has to get the lines to be  oriented in the\n"
"same direction,  this only matters with  center diamond "
"piece.  This does add complexity  so there are 2\n"
"sets of records.\n"
"\n"
"Press \"V\" or \"v\" keys to change the view of the cube.\n"
"\n"
"Press \">\" or \".\" keys to speed up the movement of pieces.\n"
"\n"
"Press \"<\" or \",\" keys to slow down the movement of pieces.\n"
"\n"
"Press \"@\" key to toggle the sound.\n"
"\n"
"Press \"Esc\" key to hide program.\n"
"\n"
"Press \"Q\", \"q\", or \"CTRL-C\" keys to kill program.\n"
"\n"
"Use the key pad or arrow keys to move without the mouse.\n"
"Key pad is defined for Skewb2d as:\n"
"  /    Counterclockwise\n"
"7 8 9  Upper Left, Up, Upper Right\n"
"  ^\n"
"4<5>6  Left, Clockwise, Right\n"
"  v\n"
"1 2 3  Lower Left, Down, Lower Right\n"
"\n"
"If the mouse is on a diamond, the above keys will not "
"move cube because the move is ambiguous.  Also\n"
"if the mouse is on a triangle, not all the keys will "
"function because the puzzle will only rotate on the\n"
"cuts, i.e. a triangle with a Upper Left - Lower Right "
"cut will rotate only Upper Left & Lower Right, a\n"
"triangle with a Upper Right - Lower Left cut will rotate "
"only Upper Right & Lower Left.  Therefore, a\n"
"triangle can only move tangential to the center of "
"the face. No doubt this is confusing, but the physical\n"
"skewb is the same way. In fact, that is part of its appeal.\n"
"Key pad for Skewb3d, use must use your intuition (is this "
"a cop out or what?).  The key pad is defined\n"
"differently depending on which side of the cube your mouse "
"is pointing at.  One thing that stays the\n"
"same is \"5\" is Clockwise and \"/\" is Counterclockwise.\n"
"\n"
"Use the control key and the left mouse button, keypad, or "
"arrow keys to move the whole cube.  Its also possible to\n"
"rotate whole cube with an axis through a face with alt key "
"is pressed (Tab key when using Windows).This is not\n"
"recorded as a turn.\n"
};

static const char referencesHelp[] = {
"Beyond Rubik's Cube: spheres, pyramids, dodecahedrons and "
"God knows what else by Douglas\n"
"R. Hofstadter, Scientific American, July 1982, pp 16-31.\n"
"Mickey's Challenge by Christoph Bandelow.\n"
"Magic Cubes 1996 Catalog of Dr. Christoph Bandelow."
};
#endif

static const char solveHelp[] = {
"Auto-solver: sorry, not implemented."
};

#include "file.h"
#ifdef WINVER
#include "SkewbP.h"
#define TITLE "wskewb"

static SkewbRec widget;

#ifndef SCOREPATH
#ifdef UNIXDELIM
#define SCOREPATH "c:/WINDOWS"
#else
#define SCOREPATH "c:\\WINDOWS"
#endif
#endif
#define PRINT_MESSAGE(b) (void) MessageBox(widget.core.hWnd, (LPCSTR) b, "Warning", MB_OK);
#define SET_STARTED(w,b) w->skewb.started = b
#else
#include "xwin.h"
#include <X11/Shell.h>
#include <X11/cursorfont.h>
#ifdef HAVE_MOTIF
#include <Xm/PanedW.h>
#include <Xm/RowColumn.h>
#include <Xm/Label.h>
#include <Xm/LabelG.h>
#include <Xm/MessageB.h>
#include <Xm/PushBG.h>
#include <Xm/CascadeB.h>
#include <Xm/Scale.h>
#include <Xm/ToggleB.h>
#ifdef MOUSEBITMAPS
#include "pixmaps/mouse-l.xbm"
#include "pixmaps/mouse-r.xbm"
#endif
#define PRINT_MESSAGE(b) printState(message, b)
#else
#define PRINT_MESSAGE(b) XtWarning(b)
#endif
#define SET_STARTED(w,b) XtVaSetValues(w, XtNstart, b, NULL)
#include "Skewb.h"
#include "Skewb2d.h"
#ifdef HAVE_OPENGL
#include "SkewbGL.h"
#else
#include "Skewb3d.h"
#endif
#ifdef HAVE_XPM
#include <X11/xpm.h>
#ifdef CONSTPIXMAPS
#include "skewb.t.xpm"
#include "skewb.p.xpm"
#include "skewb.s.xpm"
#include "skewb.m.xpm"
#include "skewb.l.xpm"
#include "skewb.xpm"
#else
#include "pixmaps/skewb.t.xpm"
#include "pixmaps/skewb.p.xpm"
#include "pixmaps/skewb.s.xpm"
#include "pixmaps/skewb.m.xpm"
#include "pixmaps/skewb.l.xpm"
#include "pixmaps/skewb.xpm"
#endif
#define RESIZE_XPM(s) ((char **) (((s)<=32)?\
(((s)<=22)?(((s)<=16)?skewb_t_xpm:skewb_p_xpm):\
(((s)<=24)?skewb_s_xpm:skewb_m_xpm)):\
(((s)<=48)?skewb_l_xpm:skewb_xpm)))
#endif
#include "pixmaps/skewb.xbm"
#define DEFINE_XBM (char *) skewb_bits, skewb_width, skewb_height
#ifndef SCOREPATH
#ifdef VMS
#define SCOREPATH "SYS$LOGIN:"
#else
#define SCOREPATH "/var/games/xpuzzles"
#endif
#endif
#endif

#ifndef SCOREFILE
#define SCOREFILE "skewb.scores"
#endif

#define NEVER (-1)
#define FILE_NAME_LENGTH 1024
#define USER_NAME_LENGTH 120
#define MESSAGE_LENGTH (USER_NAME_LENGTH+64)
#define TITLE_LENGTH 2048
#define NOACCESS "noaccess"
#define NOBODY "nobody"

typedef struct {
	int score;
	char name[USER_NAME_LENGTH];
} PuzzleRecord;

static PuzzleRecord puzzleRecord[2];
static int movesDsp = 0;
static char messageDsp[MESSAGE_LENGTH] = "Welcome";
static char recordDsp[MESSAGE_LENGTH] = "NOT RECORDED";
#ifndef HAVE_MOTIF
static char titleDsp[TITLE_LENGTH] = "";
#endif
static char scoreFileName[FILE_NAME_LENGTH] = SCOREFILE;
static char fileName[FILE_NAME_LENGTH];
static Boolean randomized = False;
#ifdef WINVER
#define PROGRAM_NAME_LENGTH 80
static char progDsp[PROGRAM_NAME_LENGTH] = TITLE;
static char userNameDsp[USER_NAME_LENGTH] = "Guest";
#else
#ifdef HAVE_MOTIF
#define MIN_SPEED 1
#define MAX_SPEED 50
static Widget movesText, recordText, message;
static Widget speedSlider, orientizeSwitch, practiceSwitch;
static char buff[21];
static Widget descriptionDialog, featuresDialog;
static Widget optionsDialog, referencesDialog, aboutDialog;
static Widget solveDialog, practiceDialog, randomizeDialog;
static Arg arg[3];
#else
static Widget shell;
#endif
static Pixmap pixmap = None;
static Widget topLevel, puzzle2d, puzzle3d;
static char *progDsp;
static char userNameDsp[USER_NAME_LENGTH] = "";

#ifdef HAVE_MOTIF
static void
printState(Widget w, char *msg)
{
	XmString xmstr;

	if (!XtIsSubclass(w, xmLabelWidgetClass))
		XtError("printState() requires a Label Widget");
	xmstr = XmStringCreateLtoR(msg, XmSTRING_DEFAULT_CHARSET);
	XtVaSetValues(w, XmNlabelString, xmstr, NULL);
}
#endif

static void
printRecords(void)
{
	int orient;

	(void) printf("    SKEWB  HALL OF FAME\n\n");
	(void) printf("ORIENT USER            MOVES\n");
	for (orient = 0; orient < 2; orient++) {
		if (puzzleRecord[orient].score > 0)
			(void) printf("%6d %-12s%9d\n",
				orient,
				puzzleRecord[orient].name,
				puzzleRecord[orient].score);
	}
}
#endif

static void
initRecords(void)
{
	int orient;

	for (orient = 0; orient < 2; orient++) {
		puzzleRecord[orient].score = NEVER;
		(void) strncpy(puzzleRecord[orient].name, NOACCESS, USER_NAME_LENGTH);
	}
}

static void
readRecords(void)
{
	FILE *fp;
	int n, orient;
	char userName[USER_NAME_LENGTH];
	char *buf1 = NULL, *buf2 = NULL;
	char *fname, *lname;

	stringCat(&buf1, CURRENTDELIM, scoreFileName);
	lname = buf1;
	stringCat(&buf1, SCOREPATH, FINALDELIM);
	stringCat(&buf2, buf1, SCOREFILE);
	free(buf1);
	fname = buf2;
	(void) strncpy(fileName, lname, USER_NAME_LENGTH);
	if ((fp = fopen(fileName, "r")) == NULL) {
		(void) strncpy(fileName, fname, USER_NAME_LENGTH);
		/* Try installed directory. */
		if ((fp = fopen(fileName, "r")) == NULL) {
			stringCat(&buf1, "Can not read ", fname);
			stringCat(&buf2, buf1, " or ");
			free(buf1);
			stringCat(&buf1, buf2, lname);
			free(buf2);
			PRINT_MESSAGE(buf1);
			free(buf1);
			free(lname);
			free(fname);
			return;
		}
/* Probably annoying */
#if 0
		else {
			stringCat(&buf1, "Can not read ", fname);
			stringCat(&buf2, buf1, ", falling back to ");
			free(buf1);
			stringCat(&buf1, buf2, lname);
			free(buf2);
			PRINT_MESSAGE(buf1);
			free(buf1);
		}
#endif
	}
	free(lname);
	free(fname);
	for (orient = 0; orient < 2; orient++) {
		(void) fscanf(fp, "%d %s\n", &n, userName);
		if (n <= puzzleRecord[orient].score ||
				puzzleRecord[orient].score <= NEVER) {
			puzzleRecord[orient].score = n;
			(void) strncpy(puzzleRecord[orient].name, userName,
				USER_NAME_LENGTH);
		}
	}
	(void) fclose(fp);
}

static void
writeRecords(void)
{
	FILE *fp;
	int orient;
	char *buf1 = NULL;

	if ((fp = fopen(fileName, "w")) == NULL) {
		stringCat(&buf1, "Can not write to ", fileName);
		PRINT_MESSAGE(buf1);
		free(buf1);
		return;
	}
	{
#if HAVE_FCNTL_H
		int lfd;
		char lockFile[FILE_NAME_LENGTH];

		(void) strncpy(lockFile, fileName, FILE_NAME_LENGTH - 6);
		lockFile[FILE_NAME_LENGTH - 6] = '\0';
		(void) strcat(lockFile, ".lock");
		while (((lfd = open(lockFile, O_CREAT | O_EXCL, 0644)) < 0) &&
				errno == EEXIST)
			(void) sleep(1);
		if (lfd < 0) {
#if 1
			(void) fprintf(stderr,
				"Lock file exists... guessing its an old one.\n");
#else
			(void) fprintf(stderr,
				"Lock file exists... score not recorded - sorry.\n");
			return;
#endif
		}
#endif
		for (orient = 0; orient < 2; orient++)
			(void) fprintf(fp, "%d %s\n",
			puzzleRecord[orient].score, puzzleRecord[orient].name);
#if HAVE_FCNTL_H
		(void) close(lfd);
		(void) unlink(lockFile);
#endif
		(void) fclose(fp);
	}
}

static void
printRecord(Boolean orient, Boolean practice)
{
	int i = (orient) ? 1 : 0;

	if (practice) {
		(void) strncpy(recordDsp, "practice", MESSAGE_LENGTH);
	} else if (puzzleRecord[i].score <= NEVER) {
		(void) sprintf(recordDsp, "NEVER %s", NOACCESS);
	} else {
		(void) sprintf(recordDsp, "%d %s",
			puzzleRecord[i].score, puzzleRecord[i].name);
	}
#ifdef HAVE_MOTIF
	printState(recordText, recordDsp);
#endif
}

#ifndef WINVER
/* There is probably a better way to assure that they are the same
 * but I do not know it off hand. */
static void
makeEquivalent(String *userName, String *scoreFile,
		Boolean *orient, Boolean *practice, int *delay)
{
	Boolean mono, reverse;
	Boolean scoreOnly, versionOnly;
	Pixel foreground, background, pieceBorder;
	String faceColor[MAX_FACES];

	XtVaGetValues(puzzle2d,
		XtNuserName, userName,
		XtNscoreFile, scoreFile,
		XtNorient, orient,
		XtNpractice, practice,
		XtNmono, &mono,
		XtNreverseVideo, &reverse,
		XtNdelay, delay,
		XtNforeground, &foreground,
		XtNbackground, &background,
		XtNpieceBorder, &pieceBorder,
		XtNfaceColor0, &(faceColor[0]),
		XtNfaceColor1, &(faceColor[1]),
		XtNfaceColor2, &(faceColor[2]),
		XtNfaceColor3, &(faceColor[3]),
		XtNfaceColor4, &(faceColor[4]),
		XtNfaceColor5, &(faceColor[5]),
		XtNscoreOnly, &scoreOnly,
		XtNversionOnly, &versionOnly, NULL);
	if (versionOnly) {
		(void) printf("%s\n", aboutHelp);
		exit(0);
	}
	if (strcmp(*scoreFile, ""))
		(void) strncpy(scoreFileName, *scoreFile, FILE_NAME_LENGTH);
	if (scoreOnly) {
		initRecords();
		readRecords();
		printRecords();
		exit(0);
	}
	XtVaSetValues(puzzle2d,
		XtNdirection, IGNORE_DIR,
		XtNstart, False, NULL);
	XtVaSetValues(puzzle3d,
		XtNuserName, *userName,
		XtNorient, *orient,
		XtNpractice, *practice,
		XtNmono, mono,
		XtNreverseVideo, reverse,
		XtNdirection, IGNORE_DIR,
		XtNstart, False,
		XtNdelay, *delay,
		XtNforeground, foreground,
		XtNbackground, background,
		XtNpieceBorder, pieceBorder,
		XtNfaceColor0, faceColor[0],
		XtNfaceColor1, faceColor[1],
		XtNfaceColor2, faceColor[2],
		XtNfaceColor3, faceColor[3],
		XtNfaceColor4, faceColor[4],
		XtNfaceColor5, faceColor[5], NULL);
}
#endif

static void
printStatus(char *msg, int nMoves
#ifndef HAVE_MOTIF
		, int dim
#endif
		)
{
#ifdef HAVE_MOTIF
	printState(message, msg);
	(void) sprintf(buff, "%d", nMoves);
	printState(movesText, buff);
#else
#ifdef HAVE_OPENGL
	if (dim == 4)
		(void) sprintf(titleDsp, "%sGL: (%d/%s) - %s",
			progDsp, nMoves, recordDsp, msg);
	else
#endif
		(void) sprintf(titleDsp, "%s%dd: (%d/%s) - %s",
			progDsp, dim, nMoves, recordDsp, msg);
#ifdef WINVER
	SetWindowText(widget.core.hWnd, (LPSTR) titleDsp);
#else
	XtVaSetValues(XtParent((dim == 2) ? puzzle2d : puzzle3d),
		XtNtitle, titleDsp, NULL);
#endif
#endif
}

static Boolean
handleSolved(int counter, Boolean orient)
{
	int i = (orient) ? 1 : 0;

	if (counter < puzzleRecord[i].score || puzzleRecord[i].score <= NEVER) {
		readRecords();	/* Maybe its been updated by another */
		puzzleRecord[i].score = counter;
		(void) strncpy(puzzleRecord[i].name, userNameDsp, USER_NAME_LENGTH);
		if (orient && (counter < puzzleRecord[!i].score ||
				puzzleRecord[!i].score <= NEVER)) {
			puzzleRecord[!i].score = counter;
			(void) strncpy(puzzleRecord[!i].name, userNameDsp,
				USER_NAME_LENGTH);
		}
		writeRecords();
		printRecord(orient, False);
		return True;
	}
	return False;
}

static void
initialize(
#ifdef WINVER
SkewbWidget w, HBRUSH brush
#else
void
#endif
)
{
	Boolean orient, practice;
	char *userName, *scoreFile;
#ifdef WINVER
	int dim;

	initializePuzzle(w, brush);

	orient = w->skewb.orient;
	practice = w->skewb.practice;
	userName = w->skewb.userName;
	scoreFile = w->skewb.scoreFile;
	dim = w->skewb.dim;
	SET_STARTED(w, False);
	if (strcmp(scoreFile, ""))
		(void) strncpy(scoreFileName, scoreFile, FILE_NAME_LENGTH);
#else
	int delay;

	makeEquivalent(&userName, &scoreFile, &orient, &practice, &delay);
#ifdef HAVE_MOTIF
	XmToggleButtonSetState(orientizeSwitch, orient, True);
	XmToggleButtonSetState(practiceSwitch, practice, True);
#endif
#endif
	initRecords();
	readRecords();
#ifndef WINVER
	(void) strncpy(userNameDsp, userName, USER_NAME_LENGTH);
#endif
	if (!strcmp(userName, "") || !strcmp(userName, "(null)") ||
			!strcmp(userName, NOACCESS) ||
			!strcmp(userName, NOBODY)) {
#ifdef WINVER
		(void) strncpy(userNameDsp, userName, USER_NAME_LENGTH);
#else
		char *login = getlogin();

		if (login == NULL) {
			(void) strcpy(userNameDsp, "");
		} else {
			(void) sprintf(userNameDsp, "%s", login);
		}
		if (!strcmp(userNameDsp, "") ||
				!strcmp(userNameDsp, "(null)") ||
				!strcmp(userNameDsp, NOACCESS) ||
				!strcmp(userNameDsp, NOBODY))
			/* It really IS nobody */
			(void) sprintf(userNameDsp, "%s", "guest");
#endif
	}
	printRecord(orient, practice);
#ifdef WINVER
	printStatus(messageDsp, movesDsp, dim);
#else
#ifdef HAVE_MOTIF
	printStatus(messageDsp, movesDsp);
#else
	printStatus(messageDsp, movesDsp, 2);
#ifdef HAVE_OPENGL
	printStatus(messageDsp, movesDsp, 4);
#else
	printStatus(messageDsp, movesDsp, 3);
#endif
#endif
#endif
}

#ifdef WINVER
void
setPuzzle(SkewbWidget w, int reason)
#else
static void
puzzleListener(Widget w, caddr_t clientData, skewbCallbackStruct *callData)
#endif
{
	Boolean orient, practice, start, cheat;
#ifdef WINVER
	int dim = 0;
#else
	int reason = callData->reason;
	Widget otherw = (Widget) NULL;
#ifndef HAVE_MOTIF
	int dim = 0, otherdim = 0;
#endif

	if (w == puzzle2d) {
		otherw = puzzle3d;
#ifndef HAVE_MOTIF
		dim = 2;
#ifdef HAVE_OPENGL
		otherdim = 4;
#else
		otherdim = 3;
#endif
#endif
	} else if (w == puzzle3d) {
		otherw = puzzle2d;
#ifndef HAVE_MOTIF
		otherdim = 2;
#ifdef HAVE_OPENGL
		dim = 4;
#else
		dim = 3;
#endif
#endif
	}
#endif
	(void) strcpy(messageDsp, "");
#ifdef WINVER
	orient = w->skewb.orient;
	practice = w->skewb.practice;
	cheat = w->skewb.cheat;
	start = w->skewb.started;
	dim = w->skewb.dim;
#else
	XtVaGetValues(w,
		XtNorient, &orient,
		XtNpractice, &practice,
		XtNstart, &start,
		XtNcheat, &cheat, NULL);
#endif
	switch (reason) {
	case ACTION_HIDE:
#ifdef WINVER
		ShowWindow(w->core.hWnd, SW_SHOWMINIMIZED);
#else
		(void) XIconifyWindow(XtDisplay(topLevel),
			XtWindow(topLevel),
			XScreenNumberOfScreen(XtScreen(topLevel)));
#ifndef HAVE_MOTIF
		(void) XIconifyWindow(XtDisplay(shell),
			XtWindow(shell),
			XScreenNumberOfScreen(XtScreen(shell)));
#endif
#endif
		break;
#ifndef WINVER
	case ACTION_PRACTICE_QUERY:
#ifdef HAVE_MOTIF
		XtManageChild(practiceDialog);
#else
		XtVaSetValues(puzzle2d, XtNmenu, ACTION_PRACTICE, NULL);
#endif
		break;
	case ACTION_RANDOMIZE_QUERY:
#ifdef HAVE_MOTIF
		XtManageChild(randomizeDialog);
#else
		XtVaSetValues(puzzle2d, XtNmenu, ACTION_RANDOMIZE, NULL);
#endif
		break;
#endif
	case ACTION_RESTORE:
		if (practice) {
			(void) strncpy(recordDsp, "practice",
				MESSAGE_LENGTH);
#ifdef HAVE_MOTIF
			printState(recordText, recordDsp);
#endif
		}
		movesDsp = 0;
		randomized = False;
#ifndef WINVER
		XtVaSetValues(otherw,
			XtNdirection, RESTORE_DIR, NULL);
		XtVaSetValues(w,
			XtNdirection, RESTORE_DIR, NULL);
#endif
		break;
	case ACTION_CLEAR:
		movesDsp = 0;
		randomized = False;
#ifndef WINVER
		XtVaSetValues(otherw,
			XtNdirection, CLEAR_DIR, NULL);
		XtVaSetValues(w,
			XtNdirection, CLEAR_DIR, NULL);
#endif
		break;
	case ACTION_RESET:
		movesDsp = 0;
		randomized = False;
		break;
	case ACTION_ILLEGAL:
		if (practice || randomized)
			(void) strncpy(messageDsp, "Illegal move",
				MESSAGE_LENGTH);
		else
			(void) strncpy(messageDsp,
				"Randomize to start", MESSAGE_LENGTH);
		break;
#ifndef WINVER
	case ACTION_MOVED:
		movesDsp++;
		XtVaSetValues(otherw,
			XtNstart, True,
			XtNface, callData->face,
			XtNpos, callData->position,
			XtNdirection, callData->direction,
			XtNcontrol, callData->control,
			XtNfast, callData->fast, NULL);
		SET_STARTED(w, True);
		break;
	case ACTION_CONTROL:
		XtVaSetValues(otherw,
			XtNface, callData->face,
			XtNpos, callData->position,
			XtNdirection, callData->direction,
			XtNcontrol, callData->control,
			XtNfast, callData->fast, NULL);
		break;
#endif
	case ACTION_SOLVED:
		if (practice)
			movesDsp = 0;
		else if (cheat)
			(void) sprintf(messageDsp,
				"No cheating %s!!", userNameDsp);
		else if (handleSolved(movesDsp, orient))
			(void) sprintf(messageDsp,
				"Congratulations %s!!", userNameDsp);
		else
			(void) strncpy(messageDsp, "Solved!",
				MESSAGE_LENGTH);
		SET_STARTED(w, False);
#ifndef WINVER
		SET_STARTED(otherw, False);
#endif
		randomized = False;
		break;
	case ACTION_COMPUTED:
		SET_STARTED(w, False);
#ifndef WINVER
		SET_STARTED(otherw, False);
#endif
		break;
	case ACTION_PRACTICE:
		movesDsp = 0;
		randomized = False;
		practice = !practice;
		if (!practice)
			(void) strncpy(messageDsp, "Randomize to start",
				MESSAGE_LENGTH);
		printRecord(orient, practice);
#ifdef WINVER
		w->skewb.practice = practice;
		w->skewb.started = False;
#else
		XtVaSetValues(w,
			XtNpractice, practice,
			XtNstart, False, NULL);
		XtVaSetValues(otherw,
			XtNpractice, practice,
			XtNstart, False, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(practiceSwitch, practice, True);
#endif
#endif
		break;
	case ACTION_RANDOMIZE:
		movesDsp = 0;
		randomized = True;
#ifdef WINVER
		w->skewb.practice = False;
		w->skewb.started = False;
#else
		XtVaSetValues(w,
			XtNpractice, False,
			XtNstart, False, NULL);
		XtVaSetValues(otherw,
			XtNpractice, False,
			XtNstart, False, NULL);
#endif
		break;
#ifdef HAVE_OPENGL
	case ACTION_VIEW:
		{
			int view;

#ifdef WINVER
			view = w->skewb.view;
#else
			XtVaGetValues(puzzle3d,
				XtNview, &view, NULL);
#endif
			view = (view + 1) % (2 * MAX_VIEWS);
#ifdef WINVER
			w->skewb.view = view;
#else
			XtVaSetValues(puzzle3d,
				XtNview, view, NULL);
#endif
		}
		break;
#endif
	case ACTION_ORIENTIZE:
		movesDsp = 0;
		orient = !orient;
		printRecord(orient, practice);
#ifdef WINVER
		w->skewb.orient = orient;
#else
		XtVaSetValues(w,
			XtNorient, orient, NULL);
		XtVaSetValues(otherw,
			XtNorient, orient, NULL);
#ifdef HAVE_MOTIF
		XmToggleButtonSetState(orientizeSwitch, orient, True);
#endif
#endif
		break;
#ifndef WINVER
	case ACTION_UNDO:
		movesDsp--;
		XtVaSetValues(otherw,
			XtNstart, True,
			XtNface, callData->face,
			XtNpos, callData->position,
			XtNdirection, callData->direction,
			XtNcontrol, callData->control,
			XtNfast, callData->fast, NULL);
		SET_STARTED(w, True);
		break;
#endif
#ifndef WINVER
	case ACTION_REDO:
		movesDsp++;
		XtVaSetValues(otherw,
			XtNstart, True,
			XtNface, callData->face,
			XtNpos, callData->position,
			XtNdirection, callData->direction,
			XtNcontrol, callData->control,
			XtNfast, callData->fast, NULL);
		SET_STARTED(w, True);
		break;
#endif
#ifdef WINVER
	case ACTION_DIM:
		dim++;
#ifdef HAVE_OPENGL
		if (dim == 3)
			dim = 4;
		else if (dim > 4)
#else
		if (dim > 3)
#endif
			dim = 2;
		w->skewb.dim = dim;
		break;
#endif
#ifndef WINVER
	case ACTION_CHEAT:
		XtVaSetValues(w,
			XtNcheat, cheat, NULL);
		XtVaSetValues(otherw,
			XtNcheat, cheat, NULL);
		break;
#endif
	}
#ifdef WINVER
	printStatus(messageDsp, movesDsp, dim);
#else
#ifdef HAVE_MOTIF
	printStatus(messageDsp, movesDsp);
#else
	printStatus(messageDsp, movesDsp, dim);
	printStatus(messageDsp, movesDsp, otherdim);
#endif
#endif
}

#ifdef WINVER
void
setPuzzleMove(SkewbWidget w, int reason, int face, int position,
		int direction, int control, int fast)
{
	/* Boolean orient, practice, cheat; */
	int dim;

	(void) strcpy(messageDsp, "");
#if 0
	orient = w->skewb.orient;
	practice = w->skewb.practice;
	cheat = w->skewb.cheat;
#endif
	dim = w->skewb.dim;
	switch (reason) {
	case ACTION_MOVED:
		movesDsp++;
		SET_STARTED(w, True);
		break;
	case ACTION_CONTROL:
		break;
	case ACTION_UNDO:
		movesDsp--;
		SET_STARTED(w, True);
		break;
	case ACTION_REDO:
		movesDsp++;
		SET_STARTED(w, True);
		break;
	}
	printStatus(messageDsp, movesDsp, dim);
}

static LRESULT CALLBACK
about(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
	if (message == WM_COMMAND && LOWORD(wParam) == IDOK) {
		(void) EndDialog(hDlg, TRUE);
		return TRUE;
	}
	return FALSE;
}

static LRESULT CALLBACK
WindowProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	HBRUSH brush = (HBRUSH) NULL;
	PAINTSTRUCT paint;

	widget.core.hWnd = hWnd;
	if (GetFocus()) {
		if (!widget.skewb.focus) {
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_BRUSH));
			enterPuzzle(&widget);
			(void) EndPaint(hWnd, &paint);
		}
	} else {
		if (widget.skewb.focus) {
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_BRUSH));
			leavePuzzle(&widget);
			(void) EndPaint(hWnd, &paint);
		}
	}
	switch (message) {
	case WM_CREATE:
		initialize(&widget, brush);
		break;
	case WM_DESTROY:
		destroyPuzzle(brush);
		break;
	case WM_SIZE:
		resizePuzzle(&widget);
		(void) InvalidateRect(hWnd, NULL, TRUE);
		break;
	case WM_PAINT:
		widget.core.hDC = BeginPaint(hWnd, &paint);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		exposePuzzle(&widget);
		(void) EndPaint(hWnd, &paint);
		break;
	case WM_RBUTTONDOWN:
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		randomizePuzzle(&widget);
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
	case WM_LBUTTONDOWN:
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		selectPuzzle(&widget, LOWORD(lParam), HIWORD(lParam),
			(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0,
			(GetKeyState(VK_TAB) >> 1) ? 1 : 0);
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
	case WM_LBUTTONUP:
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		releasePuzzle(&widget, LOWORD(lParam), HIWORD(lParam),
			(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0,
			(GetKeyState(VK_TAB) >> 1) ? 1 : 0);
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
#if (_WIN32_WINNT >= 0x0400) || (_WIN32_WINDOWS > 0x0400)
	case WM_MOUSEWHEEL:
		widget.core.hDC = GetDC(hWnd);
		(void) SelectObject(widget.core.hDC,
			GetStockObject(NULL_PEN));
		{
			int zDelta = ((short) HIWORD(wParam));
			POINT cursor, origin;

			origin.x = 0, origin.y = 0;
			ClientToScreen(hWnd, &origin);
			(void) GetCursorPos(&cursor);
			if (zDelta > (WHEEL_DELTA >> 1)) {
				movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					TOP,
					(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0,
					(GetKeyState(VK_TAB) >> 1) ? 1 : 0);
			} else if (zDelta < -(WHEEL_DELTA >> 1)) {
				movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					BOTTOM,
					(GetKeyState(VK_CONTROL) >> 1) ? 1 : 0,
					(GetKeyState(VK_TAB) >> 1) ? 1 : 0);
			}
		}
		(void) ReleaseDC(hWnd, widget.core.hDC);
		break;
#endif
	case WM_COMMAND:
		switch (LOWORD(wParam)) {
		case ACTION_GET:
			getPuzzle(&widget);
			resizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_WRITE:
			writePuzzle(&widget);
			break;
		case ACTION_EXIT:
			destroyPuzzle(brush);
			break;
		case ACTION_HIDE:
			hidePuzzle(&widget);
			break;
		case ACTION_UNDO:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			undoPuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_REDO:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			redoPuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_CLEAR:
			clearPuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_RANDOMIZE:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			randomizePuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_PRACTICE:
			practicePuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_SOLVE:
			widget.core.hDC = GetDC(hWnd);
			(void) SelectObject(widget.core.hDC,
				GetStockObject(NULL_PEN));
			solvePuzzle(&widget);
			(void) ReleaseDC(hWnd, widget.core.hDC);
			break;
		case ACTION_ORIENTIZE:
			orientizePuzzle(&widget);
			sizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_DIM:
			(void) dimPuzzle(&widget);
			resizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_VIEW:
			(void) viewPuzzle(&widget);
			resizePuzzle(&widget);
			(void) InvalidateRect(hWnd, NULL, TRUE);
			break;
		case ACTION_TR:
		case ACTION_BR:
		case ACTION_BL:
		case ACTION_TL:
		case ACTION_CW:
		case ACTION_CCW:
		case ACTION_TOP:
		case ACTION_RIGHT:
		case ACTION_BOTTOM:
		case ACTION_LEFT:
			{
				POINT cursor, origin;

				widget.core.hDC = GetDC(hWnd);
				(void) SelectObject(widget.core.hDC,
					GetStockObject(NULL_PEN));
				origin.x = 0, origin.y = 0;
				ClientToScreen(hWnd, &origin);
				(void) GetCursorPos(&cursor);
				(void) movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					(int) LOWORD(wParam) - ACTION_TR,
					FALSE, FALSE);
				(void) ReleaseDC(hWnd, widget.core.hDC);
			}
			break;
		case ACTION_CONTROL_TR:
		case ACTION_CONTROL_BR:
		case ACTION_CONTROL_BL:
		case ACTION_CONTROL_TL:
		case ACTION_CONTROL_CW:
		case ACTION_CONTROL_CCW:
		case ACTION_CONTROL_TOP:
		case ACTION_CONTROL_RIGHT:
		case ACTION_CONTROL_BOTTOM:
		case ACTION_CONTROL_LEFT:
			{
				POINT cursor, origin;

				widget.core.hDC = GetDC(hWnd);
				(void) SelectObject(widget.core.hDC,
					GetStockObject(NULL_PEN));
				origin.x = 0, origin.y = 0;
				ClientToScreen(hWnd, &origin);
				(void) GetCursorPos(&cursor);
				(void) movePuzzleInput(&widget,
					cursor.x - origin.x,
					cursor.y - origin.y,
					(int) LOWORD(wParam) - ACTION_CONTROL_TR,
					TRUE, FALSE);
				(void) ReleaseDC(hWnd, widget.core.hDC);
			}
			break;
		case ACTION_SPEED:
			speedUpPuzzle(&widget);
			break;
		case ACTION_SLOW:
			slowDownPuzzle(&widget);
			break;
		case ACTION_SOUND:
			toggleSoundPuzzle(&widget);
			break;
		case ACTION_DESCRIPTION:
			(void) MessageBox(hWnd, descriptionHelp,
				"Description", MB_OK);
			break;
		case ACTION_FEATURES:
			(void) MessageBox(hWnd, featuresHelp,
				"Features", MB_OK);
			break;
		case ACTION_REFERENCES:
			(void) MessageBox(hWnd, referencesHelp,
				"References", MB_OK);
			break;
		case ACTION_ABOUT:
			(void) DialogBox(widget.core.hInstance,
				"About", hWnd, (DLGPROC) about);
			break;
		}
		break;
	default:
		return (DefWindowProc(hWnd, message, wParam, lParam));
	}
	return FALSE;
}

int WINAPI
WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine,
		int numCmdShow)
{
	HWND hWnd;
	MSG msg;
	WNDCLASS wc;
	HACCEL hAccel;

	if (!hPrevInstance) {
		wc.style = CS_HREDRAW | CS_VREDRAW;
		wc.lpfnWndProc = WindowProc;
		wc.cbClsExtra = 0;
		wc.cbWndExtra = 0;
		wc.hInstance = hInstance;
		wc.hIcon = LoadIcon(hInstance, TITLE);
		wc.hCursor = LoadCursor((HINSTANCE) NULL, IDC_ARROW);
		wc.hbrBackground = (HBRUSH) GetStockObject(GRAY_BRUSH);
		wc.lpszMenuName = TITLE;
		wc.lpszClassName = TITLE;
		if (!RegisterClass(&wc))
			return FALSE;
	}
	widget.core.hInstance = hInstance;
	hWnd = CreateWindow(TITLE,
		TITLE,
		WS_OVERLAPPEDWINDOW,
		(signed) CW_USEDEFAULT,
		(signed) CW_USEDEFAULT,
		(signed) CW_USEDEFAULT,
		(signed) CW_USEDEFAULT,
		HWND_DESKTOP,
		(HMENU) NULL,
		hInstance,
		(void *) NULL);
	if (!hWnd)
		return FALSE;
	hAccel = (HACCEL) LoadAccelerators(hInstance, TITLE);
	(void) ShowWindow(hWnd, numCmdShow);
	(void) UpdateWindow(hWnd);
	while (GetMessage(&msg, (HWND) NULL, 0, 0))
		if (!TranslateAccelerator(hWnd, hAccel, &msg)) {
			(void) TranslateMessage(&msg);
			(void) DispatchMessage(&msg);
		}
	return (msg.wParam);
}

#else
static void
usage(char *programName)
{
	unsigned int i;

	(void) fprintf(stderr, "usage: %s\n", programName);
	for (i = 0; i < strlen(optionsHelp); i++) {
		if (i == 0 || optionsHelp[i - 1] == '\n')
			(void) fprintf(stderr, "\t");
		(void) fprintf(stderr, "%c", (optionsHelp[i]));
	}
	(void) fprintf(stderr, "\n");
	exit(1);
}

static XrmOptionDescRec options[] =
{
	{(char *) "-mono", (char *) "*skewb.mono", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nomono", (char *) "*skewb.mono", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-rv", (char *) "*skewb.reverseVideo", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-reverse", (char *) "*skewb.reverseVideo", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-norv", (char *) "*skewb.reverseVideo", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-noreverse", (char *) "*skewb.reverseVideo", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-fg", (char *) "*skewb.Foreground", XrmoptionSepArg, NULL},
	{(char *) "-foreground", (char *) "*skewb.Foreground", XrmoptionSepArg, NULL},
	{(char *) "-bg", (char *) "*Background", XrmoptionSepArg, NULL},
	{(char *) "-background", (char *) "*Background", XrmoptionSepArg, NULL},
	{(char *) "-bd", (char *) "*skewb.pieceBorder", XrmoptionSepArg, NULL},
	{(char *) "-border", (char *) "*skewb.pieceBorder", XrmoptionSepArg, NULL},
	{(char *) "-face0", (char *) "*skewb.faceColor0", XrmoptionSepArg, NULL},
	{(char *) "-face1", (char *) "*skewb.faceColor1", XrmoptionSepArg, NULL},
	{(char *) "-face2", (char *) "*skewb.faceColor2", XrmoptionSepArg, NULL},
	{(char *) "-face3", (char *) "*skewb.faceColor3", XrmoptionSepArg, NULL},
	{(char *) "-face4", (char *) "*skewb.faceColor4", XrmoptionSepArg, NULL},
	{(char *) "-face5", (char *) "*skewb.faceColor5", XrmoptionSepArg, NULL},
	{(char *) "-delay", (char *) "*skewb.delay", XrmoptionSepArg, NULL},
	{(char *) "-sound", (char *) "*skewb.sound", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nosound", (char *) "*skewb.sound", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-fn", (char *) "*skewb.font", XrmoptionSepArg, NULL},
	{(char *) "-font", (char *) "*skewb.font", XrmoptionSepArg, NULL},
	{(char *) "-view", (char *) "*skewb.view", XrmoptionSepArg, NULL},
	{(char *) "-orient", (char *) "*skewb.orient", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-noorient", (char *) "*skewb.orient", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-practice", (char *) "*skewb.practice", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-nopractice", (char *) "*skewb.practice", XrmoptionNoArg, (char *) "FALSE"},
	{(char *) "-userName", (char *) "*skewb.userName", XrmoptionSepArg, NULL},
	{(char *) "-scoreFile", (char *) "*skewb.scoreFile", XrmoptionSepArg, NULL},
	{(char *) "-scores", (char *) "*skewb.scoreOnly", XrmoptionNoArg, (char *) "TRUE"},
	{(char *) "-version", (char *) "*skewb.versionOnly", XrmoptionNoArg, (char *) "TRUE"}
};

#ifdef HAVE_MOTIF
static void
puzzlePracticeListener(Widget w, XtPointer clientData,
		XmAnyCallbackStruct *cbs)
{
	if (cbs->reason == XmCR_OK) {
		XtVaSetValues(puzzle2d, XtNmenu, ACTION_PRACTICE, NULL);
	}
}

static void
puzzleRandomizeListener(Widget w, XtPointer clientData,
		XmAnyCallbackStruct *cbs)
{
	if (cbs->reason == XmCR_OK) {
		XtVaSetValues(puzzle2d, XtNmenu, ACTION_RANDOMIZE, NULL);
	}
}

static void
orientToggle(Widget w, XtPointer clientData,
		XmToggleButtonCallbackStruct *cbs)
{
	Boolean orient = cbs->set, practice;

	XtVaGetValues(puzzle2d,
		XtNpractice, &practice, NULL);
	XtVaSetValues(puzzle2d,
		XtNorient, orient, NULL);
	XtVaSetValues(puzzle3d,
		XtNorient, orient, NULL);
	movesDsp = 0;
	(void) sprintf(buff, "%d", movesDsp);
	printState(movesText, buff);
	printRecord(orient, practice);
	(void) strcpy(messageDsp, "");
	printState(message, messageDsp);
}

static void
practiceToggle(Widget w, XtPointer clientData,
		XmToggleButtonCallbackStruct *cbs)
{
	Boolean orient, practice = cbs->set;

	XtVaSetValues(puzzle2d,
		XtNpractice, practice,
		XtNstart, False, NULL);
	XtVaSetValues(puzzle3d,
		XtNpractice, practice,
		XtNstart, False, NULL);
	XtVaGetValues(puzzle2d,
		XtNpractice, &orient, NULL);
	movesDsp = 0;
	(void) sprintf(buff, "%d", movesDsp);
	printState(movesText, buff);
	printRecord(orient, practice);
	if (practice)
		(void) strcpy(messageDsp, "");
	else
		(void) strncpy(messageDsp, "Randomize to start", MESSAGE_LENGTH);
	printState(message, messageDsp);
}

static void
speedChangeListener(Widget w, XtPointer clientData,
		XmScaleCallbackStruct *cbs)
{
        int delay = MAX_SPEED + MIN_SPEED - cbs->value - 1, oldDelay;

	XtVaGetValues(puzzle2d,
		XtNdelay, &oldDelay, NULL);
	if (oldDelay != delay) {
		XtVaSetValues(puzzle3d, XtNdelay, delay, NULL);
		XtVaSetValues(puzzle2d, XtNdelay, delay, NULL);
	}
}

static void
fileMenuListener(Widget w, void *value, void *clientData)
{
	int val = (int) value + ACTION_GET;

	if (val == ACTION_EXIT)
		exit(0);
	XtVaSetValues(puzzle2d, XtNmenu, val, NULL);
}

static void
playCB(Widget w, void *value, void *clientData)
{
	int val = (int) value + ACTION_UNDO;

	XtVaSetValues(puzzle2d, XtNmenu, val, NULL);
}

static Widget
createQuery(Widget w, char *text, char *title, XtCallbackProc callback)
{
	Widget button, messageBox;
	char titleDsp[FILE_NAME_LENGTH + 8];
	XmString titleString = NULL, messageString = NULL;
	static XmStringCharSet charSet =
		(XmStringCharSet) XmSTRING_DEFAULT_CHARSET;

	messageString = XmStringCreateLtoR(text, charSet);
	(void) sprintf(titleDsp, "%s: %s", progDsp, title);
	titleString = XmStringCreateSimple((char *) titleDsp);
	XtSetArg(arg[0], XmNdialogTitle, titleString);
	XtSetArg(arg[1], XmNmessageString, messageString);
	messageBox = XmCreateWarningDialog(w, (char *) "queryBox",
		arg, 2);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(button);
	XmStringFree(titleString);
	XmStringFree(messageString);
	XtAddCallback(messageBox, XmNokCallback, callback, (XtPointer) NULL);
	XtAddCallback(messageBox, XmNcancelCallback, callback,
		(XtPointer) NULL);
	return messageBox;
}

static Widget
createHelp(Widget w, char *text, char *title)
{
	Widget button, messageBox;
	char titleDsp[FILE_NAME_LENGTH + 8];
	XmString titleString = NULL, messageString = NULL, buttonString = NULL;
	static XmStringCharSet charSet =
		(XmStringCharSet) XmSTRING_DEFAULT_CHARSET;

	messageString = XmStringCreateLtoR(text, charSet);
	(void) sprintf(titleDsp, "%s: %s", progDsp, title);
	titleString = XmStringCreateSimple((char *) titleDsp);
	buttonString = XmStringCreateSimple((char *) "OK");
	XtSetArg(arg[0], XmNdialogTitle, titleString);
	XtSetArg(arg[1], XmNokLabelString, buttonString);
	XtSetArg(arg[2], XmNmessageString, messageString);
	messageBox = XmCreateInformationDialog(w, (char *) "helpBox",
		arg, 3);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_CANCEL_BUTTON);
	XtUnmanageChild(button);
	button = XmMessageBoxGetChild(messageBox, XmDIALOG_HELP_BUTTON);
	XtUnmanageChild(button);
	XmStringFree(titleString);
	XmStringFree(buttonString);
	XmStringFree(messageString);
	return messageBox;
}

static void
helpMenuListener(Widget w, XtPointer value, XtPointer clientData)
{
	int val = (int) value;

	switch (val) {
	case 0:
		XtManageChild(descriptionDialog);
		break;
	case 1:
		XtManageChild(featuresDialog);
		break;
	case 2:
		XtManageChild(optionsDialog);
		break;
	case 3:
		XtManageChild(referencesDialog);
		break;
	case 4:
		XtManageChild(aboutDialog);
		break;
	default:
		{
			char *buf;

			intCat(&buf, "helpMenuListener: %d", val);
			XtWarning(buf);
			free(buf);
		}
	}
}
#endif

int
main(int argc, char **argv)
{
	int pixmapSize;
#ifdef HAVE_MOTIF
	Widget menuBar, pullDownMenu, widget;
	Widget menuBarPanel, mainPanel, controlPanel;
	Widget movesRowCol, switchRowCol, messageRowCol;
	XmString fileString, playString;
	XmString getString, writeString, quitString;
	XmString undoString, redoString;
	XmString clearString, practiceString, randomizeString, solveString;
	XmString orientizeString;
	XmString speedString, slowString, soundString, viewString;
#endif

	progDsp = argv[0];
	topLevel = XtInitialize(argv[0], "Skewb",
		options, XtNumber(options), &argc, argv);
	if (argc != 1)
		usage(argv[0]);

#ifdef HAVE_MOTIF
	menuBarPanel = XtVaCreateManagedWidget("menuBarPanel",
		xmPanedWindowWidgetClass, topLevel,
		XmNseparatorOn, False,
		XmNsashWidth, 1,
		XmNsashHeight, 1, NULL);
	fileString = XmStringCreateSimple((char *) "File");
	playString = XmStringCreateSimple((char *) "Play");
	menuBar = XmVaCreateSimpleMenuBar(menuBarPanel, (char *) "menuBar",
		XmVaCASCADEBUTTON, fileString, 'F',
		XmVaCASCADEBUTTON, playString, 'P',
		NULL);
	XmStringFree(fileString);
	XmStringFree(playString);
	getString = XmStringCreateSimple((char *) "Get");
	writeString = XmStringCreateSimple((char *) "Write");
	quitString = XmStringCreateSimple((char *) "Exit");
	(void) XmVaCreateSimplePulldownMenu(menuBar, (char *) "file_menu",
		0, fileMenuListener,
		XmVaPUSHBUTTON, getString, 'G', NULL, NULL,
		XmVaPUSHBUTTON, writeString, 'W', NULL, NULL,
		XmVaSEPARATOR,
		XmVaPUSHBUTTON, quitString, 'x', NULL, NULL,
		NULL);
	XmStringFree(getString);
	XmStringFree(writeString);
	XmStringFree(quitString);
	undoString = XmStringCreateSimple((char *) "Undo");
	redoString = XmStringCreateSimple((char *) "Redo");
	clearString = XmStringCreateSimple((char *) "Clear");
	practiceString = XmStringCreateSimple((char *) "Practice");
	randomizeString = XmStringCreateSimple((char *) "Randomize");
	solveString = XmStringCreateSimple((char *) "Auto-solve");
	orientizeString = XmStringCreateSimple((char *) "Orientize");
	speedString = XmStringCreateSimple((char *) "Speed >");
	slowString = XmStringCreateSimple((char *) "Slow <");
	soundString = XmStringCreateSimple((char *) "Sound @");
	viewString = XmStringCreateSimple((char *) "View");
	(void) XmVaCreateSimplePulldownMenu(menuBar, (char *) "play_menu",
		1, playCB,
		XmVaPUSHBUTTON, undoString, 'U', NULL, NULL,
		XmVaPUSHBUTTON, redoString, 'R', NULL, NULL,
		XmVaPUSHBUTTON, clearString, 'C', NULL, NULL,
		XmVaPUSHBUTTON, practiceString, 'P', NULL, NULL,
		XmVaPUSHBUTTON, randomizeString, 'z', NULL, NULL,
		XmVaPUSHBUTTON, solveString, 's', NULL, NULL,
		XmVaPUSHBUTTON, orientizeString, 'O', NULL, NULL,
		XmVaPUSHBUTTON, speedString, '>', NULL, NULL,
		XmVaPUSHBUTTON, slowString, '<', NULL, NULL,
		XmVaPUSHBUTTON, soundString, '@', NULL, NULL,
		XmVaPUSHBUTTON, viewString, 'V', NULL, NULL,
		NULL);
	XmStringFree(undoString);
	XmStringFree(redoString);
	XmStringFree(clearString);
	XmStringFree(practiceString);
	XmStringFree(randomizeString);
	XmStringFree(solveString);
	XmStringFree(orientizeString);
	XmStringFree(speedString);
	XmStringFree(slowString);
	XmStringFree(soundString);
	XmStringFree(viewString);
	pullDownMenu = XmCreatePulldownMenu(menuBar,
		(char *) "helpPullDown", NULL, 0);
	widget = XtVaCreateManagedWidget("Help",
		xmCascadeButtonWidgetClass, menuBar,
		XmNsubMenuId, pullDownMenu,
		XmNmnemonic, 'H', NULL);
	XtVaSetValues(menuBar, XmNmenuHelpWidget, widget, NULL);
	widget = XtVaCreateManagedWidget("Description",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'D', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 0);
	widget = XtVaCreateManagedWidget("Features",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'F', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 1);
	widget = XtVaCreateManagedWidget("Options",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'O', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 2);
	widget = XtVaCreateManagedWidget("References",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'R', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 3);
	widget = XtVaCreateManagedWidget("About",
		xmPushButtonGadgetClass, pullDownMenu,
		XmNmnemonic, 'A', NULL);
	XtAddCallback(widget, XmNactivateCallback, helpMenuListener, (char *) 4);
	XtManageChild(menuBar);
	descriptionDialog = createHelp(menuBar, (char *) descriptionHelp,
		(char *) "Description");
	featuresDialog = createHelp(menuBar, (char *) featuresHelp,
		(char *) "Features");
	optionsDialog = createHelp(menuBar, (char *) optionsHelp,
		(char *) "Options");
	referencesDialog = createHelp(menuBar, (char *) referencesHelp,
		(char *) "References");
	aboutDialog = createHelp(menuBar, (char *) aboutHelp,
		(char *) "About");
	solveDialog = createHelp(menuBar, (char *) solveHelp,
		(char *) "Solve");
	practiceDialog = createQuery(topLevel,
		(char *) "Are you sure you want to toggle the practice mode?",
		(char *) "Practice Query",
		(XtCallbackProc) puzzlePracticeListener);
	randomizeDialog = createQuery(topLevel,
		(char *) "Are you sure you want to randomize?",
		(char *) "Randomize Query",
		(XtCallbackProc) puzzleRandomizeListener);
	mainPanel = XtCreateManagedWidget("mainPanel",
		xmPanedWindowWidgetClass, menuBarPanel,
		NULL, 0);
	controlPanel = XtVaCreateManagedWidget("controlPanel",
		xmPanedWindowWidgetClass, mainPanel,
		XmNseparatorOn, False,
		XmNsashWidth, 1,
		XmNsashHeight, 1, NULL);
	movesRowCol = XtVaCreateManagedWidget("movesRowcol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 2,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_COLUMN, NULL);
#ifdef MOUSEBITMAPS
	{
		/* Takes up valuable real estate. */
		Pixmap mouseLeftCursor, mouseRightCursor;
		Pixel fg, bg;

		(void) XtVaGetValues(movesRowCol,
			XmNforeground, &fg,
			XmNbackground, &bg, NULL);
		mouseLeftCursor = XCreatePixmapFromBitmapData(
			XtDisplay(movesRowCol),
			RootWindowOfScreen(XtScreen(movesRowCol)),
			(char *) mouse_left_bits,
			mouse_left_width, mouse_left_height, fg, bg,
			DefaultDepthOfScreen(XtScreen(movesRowCol)));
		mouseRightCursor = XCreatePixmapFromBitmapData(
			XtDisplay(movesRowCol),
			RootWindowOfScreen(XtScreen(movesRowCol)),
			(char *) mouse_right_bits,
			mouse_right_width, mouse_right_height, fg, bg,
			DefaultDepthOfScreen(XtScreen(movesRowCol)));
		(void) XtVaCreateManagedWidget("mouseLeftText",
			xmLabelGadgetClass, movesRowCol,
			XtVaTypedArg, XmNlabelString,
			XmRString, "Move", 5, NULL);
		(void) XtVaCreateManagedWidget("mouseLeft",
			xmLabelGadgetClass, movesRowCol,
			XmNlabelType, XmPIXMAP,
			XmNlabelPixmap, mouseLeftCursor, NULL);
		(void) XtVaCreateManagedWidget("mouseRightText",
			xmLabelGadgetClass, movesRowCol,
			XtVaTypedArg, XmNlabelString,
			XmRString, "Randomize", 10, NULL);
		(void) XtVaCreateManagedWidget("mouseRight",
			xmLabelGadgetClass, movesRowCol,
			XmNlabelType, XmPIXMAP,
			XmNlabelPixmap, mouseRightCursor, NULL);
	}
#endif
	(void) XtVaCreateManagedWidget("movesText",
		xmLabelGadgetClass, movesRowCol,
		XtVaTypedArg, XmNlabelString, XmRString, "Moves", 6, NULL);
	movesText = XtVaCreateManagedWidget("0",
		xmLabelWidgetClass, movesRowCol, NULL);
	(void) XtVaCreateManagedWidget("recordText",
		xmLabelGadgetClass, movesRowCol,
		XtVaTypedArg, XmNlabelString, XmRString, "Record", 7, NULL);
	recordText = XtVaCreateManagedWidget("0",
		xmLabelWidgetClass, movesRowCol, NULL);

	switchRowCol = XtVaCreateManagedWidget("switchRowCol",
		xmRowColumnWidgetClass, controlPanel,
		XmNnumColumns, 1,
		XmNorientation, XmHORIZONTAL,
		XmNpacking, XmPACK_COLUMN, NULL);
	orientizeSwitch = XtVaCreateManagedWidget("Oriented",
		xmToggleButtonWidgetClass, switchRowCol,
		XmNset, DEFAULT_ORIENT, NULL);
	XtAddCallback(orientizeSwitch, XmNvalueChangedCallback,
		(XtCallbackProc) orientToggle, (XtPointer) NULL);
	practiceSwitch = XtVaCreateManagedWidget("Practice",
		xmToggleButtonWidgetClass, switchRowCol,
		XmNset, DEFAULT_PRACTICE, NULL);
	XtAddCallback(practiceSwitch, XmNvalueChangedCallback,
		(XtCallbackProc) practiceToggle, (XtPointer) NULL);
	speedSlider = XtVaCreateManagedWidget("speedSlider",
		xmScaleWidgetClass, switchRowCol,
		XtVaTypedArg, XmNtitleString, XmRString, "Animation Speed", 16,
		XmNminimum, MIN_SPEED,
		XmNmaximum, MAX_SPEED,
		XmNvalue, MAX_SPEED - 10,
		XmNshowValue, True,
		XmNorientation, XmHORIZONTAL, NULL);
	XtAddCallback(speedSlider, XmNvalueChangedCallback,
		(XtCallbackProc) speedChangeListener, (XtPointer) NULL);
	messageRowCol = XtVaCreateManagedWidget("messageRowCol",
		xmRowColumnWidgetClass, controlPanel, NULL);
	message = XtVaCreateManagedWidget("Play Skewb! (use mouse and keypad)",
		xmLabelWidgetClass, messageRowCol, NULL);
	puzzle2d = XtCreateManagedWidget("skewb",
		skewb2dWidgetClass, mainPanel, NULL, 0);
	XtVaSetValues(puzzle2d,
		XtNheight, 200, NULL);
	XtAddCallback(puzzle2d, XtNselectCallback,
		(XtCallbackProc) puzzleListener, (XtPointer) NULL);
#ifdef HAVE_OPENGL
	puzzle3d = XtCreateManagedWidget("skewb",
		skewbGLWidgetClass, mainPanel, NULL, 0);
#else
	puzzle3d = XtCreateManagedWidget("skewb",
		skewb3dWidgetClass, mainPanel, NULL, 0);
#endif
	XtVaSetValues(puzzle3d,
		XtNheight, 200, NULL);
#else
	shell = XtCreateApplicationShell(argv[0],
		topLevelShellWidgetClass, NULL, 0);
	puzzle2d = XtCreateManagedWidget("skewb",
		skewb2dWidgetClass, topLevel, NULL, 0);
	XtAddCallback(puzzle2d, XtNselectCallback,
		(XtCallbackProc) puzzleListener, (XtPointer) NULL);
#ifdef HAVE_OPENGL
	puzzle3d = XtCreateManagedWidget("skewb",
		skewbGLWidgetClass, shell, NULL, 0);
#else
	puzzle3d = XtCreateManagedWidget("skewb",
		skewb3dWidgetClass, shell, NULL, 0);
#endif
#endif
	XtVaGetValues(puzzle2d,
		XtNpixmapSize, &pixmapSize, NULL);
#ifdef HAVE_XPM
	{
		XpmAttributes xpmAttributes;
		XpmColorSymbol transparentColor[1] = {{NULL,
			(char *) "none", 0 }};
		Pixel bg;

		xpmAttributes.valuemask = XpmColorSymbols | XpmCloseness;
		xpmAttributes.colorsymbols = transparentColor;
		xpmAttributes.numsymbols = 1;
		xpmAttributes.closeness = 40000;
		XtVaGetValues(topLevel, XtNbackground, &bg, NULL);
		transparentColor[0].pixel = bg;
		(void) XpmCreatePixmapFromData(XtDisplay(topLevel),
			RootWindowOfScreen(XtScreen(topLevel)),
			RESIZE_XPM(pixmapSize), &pixmap, NULL,
			&xpmAttributes);
	}
	if (pixmap == (Pixmap) NULL)
#endif
		pixmap = XCreateBitmapFromData(XtDisplay(topLevel),
			RootWindowOfScreen(XtScreen(topLevel)),
			DEFINE_XBM);
#ifdef HAVE_MOTIF
	XtVaSetValues(topLevel,
		XmNkeyboardFocusPolicy, XmPOINTER, /* not XmEXPLICIT */
		XtNiconPixmap, pixmap, NULL);
#else
	XtVaSetValues(topLevel,
		XtNinput, True,
		XtNiconPixmap, pixmap, NULL);
	XtVaSetValues(shell,
		XtNinput, True,
		XtNiconPixmap, pixmap, NULL);
#endif
	XtAddCallback(puzzle3d, XtNselectCallback,
		(XtCallbackProc) puzzleListener, (XtPointer) NULL);
	initialize();
	XtRealizeWidget(topLevel);
#ifndef HAVE_MOTIF
	XtRealizeWidget(shell);
#endif
	XGrabButton(XtDisplay(puzzle2d), (unsigned int) AnyButton, AnyModifier,
		XtWindow(puzzle2d), TRUE, (unsigned int) (ButtonPressMask |
		ButtonMotionMask | ButtonReleaseMask),
		GrabModeAsync, GrabModeAsync, XtWindow(puzzle2d),
		XCreateFontCursor(XtDisplay(puzzle2d), XC_hand2));
	XGrabButton(XtDisplay(puzzle3d), (unsigned int) AnyButton, AnyModifier,
		XtWindow(puzzle3d), TRUE, (unsigned int) (ButtonPressMask |
		ButtonMotionMask | ButtonReleaseMask),
		GrabModeAsync, GrabModeAsync, XtWindow(puzzle3d),
		XCreateFontCursor(XtDisplay(puzzle3d), XC_hand2));
	XtMainLoop();

#ifdef VMS
	return 1;
#else
	return 0;
#endif
}
#endif
