/*
 * @(#)Oct2d.c
 *
 * Copyright 1994 - 2024  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 Oct2d */

#include "OctP.h"
#include "Oct2dP.h"

#ifndef WINVER
static Boolean setValuesPuzzle2D(Widget current,
	Widget request, Widget renew);
static void resizePuzzle2D(Oct2DWidget w);
static void initializePuzzle2D(Widget request, Widget renew);
static void exposePuzzle2D(Widget renew,
	XEvent *event, Region region);
static void movePuzzleTl(OctWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTop(OctWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleTr(OctWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleLeft(OctWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleRight(OctWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBl(OctWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBottom(OctWidget w,
	XEvent *event, char **args, int nArgs);
static void movePuzzleBr(OctWidget w,
	XEvent *event, char **args, int nArgs);

static char translations2D[] =
"<KeyPress>q: Quit()\n\
 Ctrl<KeyPress>C: Quit()\n\
 <KeyPress>osfCancel: Hide()\n\
 <KeyPress>Escape: Hide()\n\
 <KeyPress>osfEscape: Hide()\n\
 Ctrl<KeyPress>[: Hide()\n\
 <KeyPress>0x1B: Hide()\n\
 <KeyPress>0x2E: Speed()\n\
 <KeyPress>0x3E: Speed()\n\
 <KeyPress>0x3C: Slow()\n\
 <KeyPress>0x2C: Slow()\n\
 Shift<KeyPress>2: Sound()\n\
 <KeyPress>F11: MoveCcw()\n\
 <KeyPress>KP_Divide: MoveCcw()\n\
 <KeyPress>R5: MoveCcw()\n\
 <KeyPress>Home: MoveTl()\n\
 <KeyPress>KP_7: MoveTl()\n\
 <KeyPress>R7: MoveTl()\n\
 <KeyPress>Up: MoveTop()\n\
 <KeyPress>osfUp: MoveTop()\n\
 <KeyPress>KP_Up: MoveTop()\n\
 <KeyPress>KP_8: MoveTop()\n\
 <KeyPress>R8: MoveTop()\n\
 <KeyPress>Prior: MoveTr()\n\
 <KeyPress>KP_9: MoveTr()\n\
 <KeyPress>R9: MoveTr()\n\
 <KeyPress>Left: MoveLeft()\n\
 <KeyPress>osfLeft: MoveLeft()\n\
 <KeyPress>KP_Left: MoveLeft()\n\
 <KeyPress>KP_4: MoveLeft()\n\
 <KeyPress>R10: MoveLeft()\n\
 <KeyPress>F12: MoveCw()\n\
 <KeyPress>Begin: MoveCw()\n\
 <KeyPress>KP_5: MoveCw()\n\
 <KeyPress>R11: MoveCw()\n\
 <KeyPress>Right: MoveRight()\n\
 <KeyPress>osfRight: MoveRight()\n\
 <KeyPress>KP_Right: MoveRight()\n\
 <KeyPress>KP_6: MoveRight()\n\
 <KeyPress>R12: MoveRight()\n\
 <KeyPress>End: MoveBl()\n\
 <KeyPress>KP_1: MoveBl()\n\
 <KeyPress>R13: MoveBl()\n\
 <KeyPress>Down: MoveBottom()\n\
 <KeyPress>osfDown: MoveBottom()\n\
 <KeyPress>KP_Down: MoveBottom()\n\
 <KeyPress>KP_2: MoveBottom()\n\
 <KeyPress>R14: MoveBottom()\n\
 <KeyPress>Next: MoveBr()\n\
 <KeyPress>KP_3: MoveBr()\n\
 <KeyPress>R15: MoveBr()\n\
 <Btn1Down>: Select()\n\
 <Btn1Up>: Release()\n\
 <Btn2Down>: PracticeMaybe()\n\
 <Btn2Down>(2+): Practice2()\n\
 <Btn3Down>: RandomizeMaybe()\n\
 <Btn3Down>(2+): Randomize2()\n\
 <Btn4Down>: MoveTop()\n\
 <Btn5Down>: MoveBottom()\n\
 <KeyPress>g: Get()\n\
 <KeyPress>w: Write()\n\
 <KeyPress>u: Undo()\n\
 <KeyPress>r: Redo()\n\
 <KeyPress>c: Clear()\n\
 <KeyPress>z: Randomize()\n\
 <KeyPress>s: Solve()\n\
 <KeyPress>f: Find()\n\
 <KeyPress>p: Practice()\n\
 <KeyPress>i: Increment()\n\
 <KeyPress>d: Decrement()\n\
 <KeyPress>o: Orientize()\n\
 <KeyPress>3: Period3()\n\
 <KeyPress>4: Period4()\n\
 <KeyPress>b: Both()\n\
 <KeyPress>y: Sticky()\n\
 <KeyPress>v: View()\n\
 <EnterWindow>: Enter()\n\
 <LeaveWindow>: Leave()";

static XtActionsRec actionsList2D[] =
{
	{(char *) "Quit", (XtActionProc) quitPuzzle},
	{(char *) "Hide", (XtActionProc) hidePuzzle},
	{(char *) "MoveCcw", (XtActionProc) movePuzzleCcw},
	{(char *) "MoveTl", (XtActionProc) movePuzzleTl},
	{(char *) "MoveTop", (XtActionProc) movePuzzleTop},
	{(char *) "MoveTr", (XtActionProc) movePuzzleTr},
	{(char *) "MoveLeft", (XtActionProc) movePuzzleLeft},
	{(char *) "MoveCw", (XtActionProc) movePuzzleCw},
	{(char *) "MoveRight", (XtActionProc) movePuzzleRight},
	{(char *) "MoveBl", (XtActionProc) movePuzzleBl},
	{(char *) "MoveBottom", (XtActionProc) movePuzzleBottom},
	{(char *) "MoveBr", (XtActionProc) movePuzzleBr},
	{(char *) "Select", (XtActionProc) selectPuzzle},
	{(char *) "Release", (XtActionProc) releasePuzzle},
	{(char *) "PracticeMaybe", (XtActionProc) practicePuzzleWithQuery},
	{(char *) "Practice2", (XtActionProc) practicePuzzleWithDoubleClick},
	{(char *) "RandomizeMaybe", (XtActionProc) randomizePuzzleWithQuery},
	{(char *) "Randomize2", (XtActionProc) randomizePuzzleWithDoubleClick},
	{(char *) "Get", (XtActionProc) getPuzzle},
	{(char *) "Write", (XtActionProc) writePuzzle},
	{(char *) "Undo", (XtActionProc) undoPuzzle},
	{(char *) "Redo", (XtActionProc) redoPuzzle},
	{(char *) "Clear", (XtActionProc) clearPuzzle},
	{(char *) "Randomize", (XtActionProc) randomizePuzzle},
	{(char *) "Solve", (XtActionProc) solvePuzzle},
	{(char *) "Find", (XtActionProc) findPuzzle},
	{(char *) "Practice", (XtActionProc) practicePuzzle},
	{(char *) "Increment", (XtActionProc) incrementPuzzle},
	{(char *) "Decrement", (XtActionProc) decrementPuzzle},
	{(char *) "Orientize", (XtActionProc) orientizePuzzle},
	{(char *) "Period3", (XtActionProc) period3ModePuzzle},
	{(char *) "Period4", (XtActionProc) period4ModePuzzle},
	{(char *) "Both", (XtActionProc) bothModePuzzle},
	{(char *) "Sticky", (XtActionProc) stickyModePuzzle},
	{(char *) "View", (XtActionProc) viewPuzzle},
	{(char *) "Speed", (XtActionProc) speedUpPuzzle},
	{(char *) "Slow", (XtActionProc) slowDownPuzzle},
	{(char *) "Sound", (XtActionProc) toggleSoundPuzzle},
	{(char *) "Enter", (XtActionProc) enterPuzzle},
	{(char *) "Leave", (XtActionProc) leavePuzzle}
};

static XtResource resources2D[] =
{
	{XtNwidth, XtCWidth, XtRDimension, sizeof (Dimension),
	 XtOffset(OctWidget, core.width),
	 XtRString, (caddr_t) "300"},
	{XtNheight, XtCHeight, XtRDimension, sizeof (Dimension),
	 XtOffset(OctWidget, core.height),
	 XtRString, (caddr_t) "600"},
	{XtNmono, XtCMono, XtRBoolean, sizeof (Boolean),
	 XtOffset(OctWidget, oct.mono),
	 XtRString, (caddr_t) "FALSE"},
	{XtNreverseVideo, XtCReverseVideo, XtRBoolean, sizeof (Boolean),
	 XtOffset(OctWidget, oct.reverse),
	 XtRString, (caddr_t) "FALSE"},
	{XtNforeground, XtCForeground, XtRPixel, sizeof (Pixel),
	 XtOffset(OctWidget, oct.foreground),
	 XtRString, (caddr_t) XtDefaultForeground},
	{XtNbackground, XtCBackground, XtRPixel, sizeof (Pixel),
	 XtOffset(OctWidget, oct.background),
	 XtRString, (caddr_t) "#AEB2C3" /*XtDefaultBackground*/},
	{XtNframeColor, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(OctWidget, oct.frameColor),
	 XtRString, (caddr_t) "Cyan" /*XtDefaultForeground*/},
	/* Beware color values are swapped */
	{XtNfaceColor0, XtCLabel, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.faceName[0]),
	 XtRString, (caddr_t) "Red"},
	{XtNfaceColor1, XtCLabel, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.faceName[1]),
	 XtRString, (caddr_t) "Blue"},
	{XtNfaceColor2, XtCLabel, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.faceName[2]),
	 XtRString, (caddr_t) "White"},
	{XtNfaceColor3, XtCLabel, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.faceName[3]),
	 XtRString, (caddr_t) "Magenta"},
	{XtNfaceColor4, XtCLabel, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.faceName[4]),
	 XtRString, (caddr_t) "Orange"},
	{XtNfaceColor5, XtCLabel, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.faceName[5]),
	 XtRString, (caddr_t) "Pink"},
	{XtNfaceColor6, XtCLabel, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.faceName[6]),
	 XtRString, (caddr_t) "Green"},
	{XtNfaceColor7, XtCLabel, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.faceName[7]),
	 XtRString, (caddr_t) "Yellow"},
	{XtNpieceBorder, XtCColor, XtRPixel, sizeof (Pixel),
	 XtOffset(OctWidget, oct.borderColor),
	 XtRString, (caddr_t) "gray25" /*XtDefaultForeground*/},
	{XtNdelay, XtCDelay, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.delay),
	 XtRString, (caddr_t) "10"}, /* DEFAULT_DELAY */
	{XtNsound, XtCSound, XtRBoolean, sizeof (Boolean),
	 XtOffset(OctWidget, oct.sound),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmoveSound, XtCMoveSound, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.moveSound),
	 XtRString, (caddr_t) MOVESOUND},
	{XtNfont, XtCFont, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.font),
	 XtRString, (caddr_t) "9x15bold"},
	{XtNsize, XtCSize, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.size),
	 XtRString, (caddr_t) "3"}, /*DEFAULT_FACETS */
	{XtNsticky, XtCSticky, XtRBoolean, sizeof (Boolean),
	 XtOffset(OctWidget, oct.sticky),
	 XtRString, (caddr_t) "FALSE"}, /* DEFAULT_STICKY */
	{XtNmode, XtCMode, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.mode),
	 XtRString, (caddr_t) "4"}, /*DEFAULT_MODE */
	{XtNorient, XtCOrient, XtRBoolean, sizeof (Boolean),
	 XtOffset(OctWidget, oct.orient),
	 XtRString, (caddr_t) "FALSE"}, /*DEFAULT_ORIENT */
	{XtNpractice, XtCPractice, XtRBoolean, sizeof (Boolean),
	 XtOffset(OctWidget, oct.practice),
	 XtRString, (caddr_t) "TRUE"}, /*DEFAULT_PRACTICE */
	{XtNuserName, XtCUserName, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.userName),
	 XtRString, (caddr_t) ""},
	{XtNscoreFile, XtCScoreFile, XtRString, sizeof (String),
	 XtOffset(OctWidget, oct.scoreFile),
	 XtRString, (caddr_t) ""},
	{XtNscoreOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(OctWidget, oct.scoreOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNversionOnly, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(OctWidget, oct.versionOnly),
	 XtRString, (caddr_t) "FALSE"},
	{XtNmenu, XtCMenu, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.menu),
	 XtRString, (caddr_t) "999"}, /* ACTION_IGNORE */
	{XtNstart, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(OctWidget, oct.started),
	 XtRString, (caddr_t) "FALSE"},
	{XtNcheat, XtCBoolean, XtRBoolean, sizeof (Boolean),
	 XtOffset(OctWidget, oct.cheat),
	 XtRString, (caddr_t) "FALSE"},
	{XtNface, XtCFace, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.currentFace),
	 XtRString, (caddr_t) "-1"},
	{XtNpos, XtCPos, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.currentPosition),
	 XtRString, (caddr_t) "-1"},
	{XtNdirection, XtCDirection, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.currentDirection),
	 XtRString, (caddr_t) "-1"},
	{XtNstyle, XtCStyle, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.currentStyle),
	 XtRString, (caddr_t) "-1"},
	{XtNcontrol, XtCControl, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.currentControl),
	 XtRString, (caddr_t) "0"},
	{XtNfast, XtCFast, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.currentFast),
	 XtRString, (caddr_t) "1"},
	{XtNpixmapSize, XtCPixmapSize, XtRInt, sizeof (int),
	 XtOffset(OctWidget, oct.pixmapSize),
	 XtRString, (caddr_t) "64"},
	{XtNselectCallback, XtCCallback, XtRCallback, sizeof (caddr_t),
	 XtOffset(OctWidget, oct.select),
	 XtRCallback, (caddr_t) NULL}
};

Oct2DClassRec oct2dClassRec =
{
	{
		(WidgetClass) & octClassRec,		/* superclass */
		(char *) "Oct2D",		/* class name */
		sizeof (OctRec),	/* widget size */
		NULL,		/* class initialize */
		NULL,		/* class part initialize */
		FALSE,		/* class inited */
		(XtInitProc) initializePuzzle2D,	/* initialize */
		NULL,		/* initialize hook */
		XtInheritRealize,	/* realize */
		actionsList2D,	/* actions */
		XtNumber(actionsList2D),	/* num actions */
		resources2D,	/* resources */
		XtNumber(resources2D),	/* num resources */
		NULLQUARK,	/* xrm class */
		TRUE,		/* compress motion */
		TRUE,		/* compress exposure */
		TRUE,		/* compress enterleave */
		TRUE,		/* visible interest */
		NULL,		/* destroy */
		(XtWidgetProc) resizePuzzle2D,	/* resize */
		(XtExposeProc) exposePuzzle2D,	/* expose */
		(XtSetValuesFunc) setValuesPuzzle2D,	/* set values */
		NULL,		/* set values hook */
		XtInheritSetValuesAlmost,	/* set values almost */
		NULL,		/* get values hook */
		XtInheritAcceptFocus,		/* accept focus */
		XtVersion,	/* version */
		NULL,		/* callback private */
		translations2D,	/* tm table */
		NULL,		/* query geometry */
		NULL,		/* display accelerator */
		NULL		/* extension */
	},
	{
		0		/* ignore */
	},
	{
		0		/* ignore */
	}
};

WidgetClass oct2dWidgetClass = (WidgetClass) & oct2dClassRec;
#endif

static Point triangleUnit[MAX_SIDES][4] =
{
	{
		{0, 0},
		{1, 1},
		{-2, 0},
		{1, -1}
	},
	{
		{0, 0},
		{-1, 1},
		{0, -2},
		{1, 1}
	},
	{
		{0, 0},
		{-1, -1},
		{2, 0},
		{-1, 1}
	},
	{
		{0, 0},
		{1, -1},
		{0, 2},
		{-1, -1}
	}
};
static Point triangleList[MAX_SIDES][4], letterList[MAX_SIDES];

static Pixmap dr = 0; /* dummy for future double buffering */

static void
drawOrientLine2D(Oct2DWidget w, int orient, int face, int side, int dx, int dy,
		GC borderGC)
{
	int x_1 = 0, x_2 = 0, y_1 = 0, y_2 = 0;
	int temp1 = w->oct.facetLength + 1;
	int temp2 = w->oct.facetLength / 2 + 1;
	int temp3 = w->oct.orientLineLength / 3;

	/* clock positions */
	switch ((side == face) ? orient :
			(orient + MAX_ORIENT / 2) % MAX_ORIENT) {
	case 0:
		x_2 = x_1 = dx;
		y_1 = dy + temp1 - 2;
		y_2 = y_1 - w->oct.orientLineLength;
		break;
	case 1:
		x_1 = dx + temp2;
		y_1 = dy + temp2;
		x_2 = x_1 + temp3;
		y_2 = y_1 - w->oct.orientLineLength;
		break;
	case 2:
		x_1 = dx - temp2;
		y_1 = dy - temp2;
		x_2 = x_1 + w->oct.orientLineLength;
		y_2 = y_1 - temp3;
		break;
	case 3:
		x_1 = dx - temp1 + 2;
		x_2 = x_1 + w->oct.orientLineLength;
		y_2 = y_1 = dy;
		break;
	case 4:
		x_1 = dx - temp2;
		y_1 = dy + temp2;
		x_2 = x_1 + w->oct.orientLineLength;
		y_2 = y_1 + temp3;
		break;
	case 5:
		x_1 = dx + temp2;
		y_1 = dy - temp2;
		x_2 = x_1 + temp3;
		y_2 = y_1 + w->oct.orientLineLength;
		break;
	case 6:
		x_2 = x_1 = dx;
		y_1 = dy - temp1 + 2;
		y_2 = y_1 + w->oct.orientLineLength;
		break;
	case 7:
		x_1 = dx - temp2;
		y_1 = dy - temp2;
		x_2 = x_1 - temp3;
		y_2 = y_1 + w->oct.orientLineLength;
		break;
	case 8:
		x_1 = dx + temp2;
		y_1 = dy + temp2;
		x_2 = x_1 - w->oct.orientLineLength;
		y_2 = y_1 + temp3;
		break;
	case 9:
		x_1 = dx + temp1 - 2;
		x_2 = x_1 - w->oct.orientLineLength;
		y_2 = y_1 = dy;
		break;
	case 10:
		x_1 = dx + temp2;
		y_1 = dy - temp2;
		x_2 = x_1 - w->oct.orientLineLength;
		y_2 = y_1 - temp3;
		break;
	case 11:
		x_1 = dx - temp2;
		y_1 = dy + temp2;
		x_2 = x_1 - temp3;
		y_2 = y_1 - w->oct.orientLineLength;
		break;
	default:
		{
			char *buf;

			intCat(&buf, "drawOrientLine: orient ",
				orient);
			DISPLAY_WARNING(buf);
			free(buf);
		}
	}
	DRAWLINE(w, dr, borderGC, x_1, y_1, x_2, y_2);
}

void
drawTriangle2D(Oct2DWidget w, int face, int position, int offset)
{
/*#ifndef FIND*/
	GC faceGC, borderGC;
	int dx = 0, dy = 0;
	int side, row = SQRT(position);
	int base = row * row;
	int g = position - base;
	int s = g % 2;
	int pos = row * (w->oct.facetLength + w->oct.delta);
	int orient, view, faceOnView;

	if (position < 0) {
		(void) printf("drawTriangle face %d, position = %d\n",
			face, position);
		return;
	}
	view = (face / MAX_SIDES == UP) ? DOWN : UP;
	faceOnView = face - MAX_SIDES * (!view);
	orient = (!w->oct.vertical && view == DOWN) ?
		(faceOnView + MAX_SIDES / 2) % MAX_SIDES : faceOnView;
	switch (orient) {
	case 0:
		dy = w->oct.viewMiddle - w->oct.delta - 2 - pos;
		dx = w->oct.viewMiddle + pos - 1 -
			g * (w->oct.facetLength + w->oct.delta);
		break;
	case 1:
		dx = w->oct.viewMiddle + w->oct.delta + pos;
		dy = w->oct.viewMiddle + pos - 1 -
			g * (w->oct.facetLength + w->oct.delta);
		break;
	case 2:
		dy = w->oct.viewMiddle + w->oct.delta + pos;
		dx = w->oct.viewMiddle - pos - 1 +
			g * (w->oct.facetLength + w->oct.delta);
		break;
	case 3:
		dx = w->oct.viewMiddle - w->oct.delta - 2 - pos;
		dy = w->oct.viewMiddle - pos - 1 +
			g * (w->oct.facetLength + w->oct.delta);
		break;
	default:
		{
			char *buf;

			intCat(&buf, "drawTriangle: orient ", orient);
			DISPLAY_WARNING(buf);
			free(buf);
		}
	}
	if ((faceOnView & 1) == 1)
		side = ((faceOnView == 1) ? !s : s) * 2 + 1;
	else	/* faceOnView == (RIGHT / 2) || faceOnView == (LEFT / 2) */
		side = ((faceOnView) ? s : !s) * 2;
	side = (!w->oct.vertical && view == DOWN) ?
		(side + MAX_SIDES / 2) % MAX_SIDES : side;
	if (s == OPPOSITE)
		switch (side) {
		case 0:
			dy -= w->oct.facetLength;
			break;
		case 1:
			dx += w->oct.facetLength;
			break;
		case 2:
			dy += w->oct.facetLength;
			break;
		case 3:
			dx -= w->oct.facetLength;
			break;
		default:
			{
				char *buf;

				intCat(&buf, "drawTriangle: side ",
					side);
				DISPLAY_WARNING(buf);
				free(buf);
			}
		}
	dx += w->oct.puzzleOffset.x;
	dy += w->oct.puzzleOffset.y;
	if (view == DOWN) {
		if (w->oct.vertical)
			dy += w->oct.viewLength - w->oct.delta - 1;
		else
			dx += w->oct.viewLength - w->oct.delta - 1;
	}
	triangleList[side][0].x = dx;
	triangleList[side][0].y = dy;
	if (offset) {
		borderGC = w->oct.faceGC[w->oct.facetLoc[face][position].face];
		if (w->oct.mono) {
			faceGC = w->oct.inverseGC;
		} else {
			faceGC = w->oct.borderGC;
		}
	} else {
		faceGC = w->oct.faceGC[w->oct.facetLoc[face][position].face];
		borderGC = w->oct.borderGC;
	}
	POLYGON(w, dr, faceGC, borderGC, triangleList[side], 3, True, False);
	if (w->oct.mono) {
		int letterX, letterY;
		char buf[2];

		buf[0] =
#ifdef WINVER
			w->oct.faceChar[w->oct.facetLoc
				[face][position].face];
#else
			w->oct.faceName[w->oct.facetLoc
				[face][position].face][0];
#endif
		buf[1] = '\0';
		letterX = dx + letterList[side].x;
		letterY = dy + letterList[side].y;
		if (offset) {
			borderGC = w->oct.borderGC;
		} else {
			borderGC = w->oct.inverseGC;
		}
		DRAWTEXT(w, dr, borderGC, letterX, letterY, buf, 1);
	}
	if (w->oct.orient)
		drawOrientLine2D(w, w->oct.facetLoc[face][position].rotation,
			faceOnView, side, dx, dy, borderGC);
/*endif*/
}

void
eraseFrame2D(const Oct2DWidget w)
{
	FILLRECTANGLE(w, dr, w->oct.inverseGC,
		0, 0, w->core.width, w->core.height);
}

void
drawFrame2D(Oct2DWidget w, Boolean focus)
{
	int startx, starty, lengthx, lengthy, longlength;

	GC gc = (focus) ? w->oct.frameGC : w->oct.borderGC;
	startx = 1 + w->oct.puzzleOffset.x;
	starty = 1 + w->oct.puzzleOffset.y;
	lengthx = w->oct.viewLength - w->oct.delta + w->oct.puzzleOffset.x;
	lengthy = w->oct.viewLength - w->oct.delta + w->oct.puzzleOffset.y;
	DRAWLINE(w, dr, gc, startx, starty, lengthx, starty);
	DRAWLINE(w, dr, gc, startx, starty, startx, lengthy);
	DRAWLINE(w, dr, gc, startx, lengthy, lengthx, starty);
	DRAWLINE(w, dr, gc, startx, starty, lengthx, lengthy);
	if (w->oct.vertical) {
		longlength = 2 * w->oct.viewLength - 2 * w->oct.delta - 1 +
			w->oct.puzzleOffset.y;
		DRAWLINE(w, dr, gc,
			startx, lengthy, startx, longlength);
		DRAWLINE(w, dr, gc,
			lengthx, lengthy, lengthx, longlength);
		DRAWLINE(w, dr, gc,
			startx, longlength, lengthx, longlength);
		DRAWLINE(w, dr, gc,
			startx, longlength, lengthx, lengthy);
		DRAWLINE(w, dr, gc,
			startx, lengthy, lengthx, longlength);
		DRAWLINE(w, dr, gc,
			lengthx, starty, lengthx, lengthy);
		DRAWLINE(w, dr, w->oct.frameGC,
			0, lengthy, (int) w->core.width, lengthy);
	} else {
		longlength = 2 * w->oct.viewLength - 2 * w->oct.delta - 1 +
			w->oct.puzzleOffset.x;
		DRAWLINE(w, dr, gc,
			lengthx, starty, longlength, starty);
		DRAWLINE(w, dr, gc,
			lengthx, lengthy, longlength, lengthy);
		DRAWLINE(w, dr, gc,
			longlength, starty, longlength, lengthy);
		DRAWLINE(w, dr, gc,
			longlength, starty, lengthx, lengthy);
		DRAWLINE(w, dr, gc,
			lengthx, starty, longlength, lengthy);
		DRAWLINE(w, dr, gc,
			startx, lengthy, lengthx, lengthy);
		DRAWLINE(w, dr, w->oct.frameGC,
			lengthx, 0, lengthx, (int) w->core.height);
	}
}

static void
resizePieces(Oct2DWidget w)
{
	int i, j;

	w->oct.facetLength = w->oct.faceLength / (2 * w->oct.size) -
		w->oct.delta - 1;
	for (i = 0; i <= 3; i++)
		for (j = 0; j < MAX_SIDES; j++) {
			triangleList[j][i].x = triangleUnit[j][i].x *
				w->oct.facetLength;
			triangleList[j][i].y = triangleUnit[j][i].y *
				w->oct.facetLength;
		}
	letterList[TOP / 2].x = 1 - w->oct.letterOffset.x;
	letterList[TOP / 2].y = 3 * w->oct.facetLength / 5 - 1 +
		w->oct.letterOffset.y;
	letterList[RIGHT / 2].x = -3 * w->oct.facetLength / 5 - 1 -
		w->oct.letterOffset.x;
	letterList[RIGHT / 2].y = -1 + w->oct.letterOffset.y;
	letterList[BOTTOM / 2].x = 1 - w->oct.letterOffset.x;
	letterList[BOTTOM / 2].y = -3 * w->oct.facetLength / 5 - 2 +
		w->oct.letterOffset.y;
	letterList[LEFT / 2].x = 3 * w->oct.facetLength / 5 + 1 -
		w->oct.letterOffset.x;
	letterList[LEFT / 2].y = -1 + w->oct.letterOffset.y;
	w->oct.orientLineLength = w->oct.facetLength / 4;
}

Boolean
selectPieces2D(Oct2DWidget w, int positionX, int positionY,
		int *face, int *position)
{
	RTT rtt;
	int x = positionX, y = positionY;
	int view;

	x -= w->oct.puzzleOffset.x;
	y -= w->oct.puzzleOffset.y;
	if (w->oct.vertical && y > w->oct.viewLength - 1) {
		y -= (w->oct.viewLength - 1);
		view = DOWN;
	} else if (!w->oct.vertical && x > w->oct.viewLength - 1) {
		x -= (w->oct.viewLength - 1);
		view = DOWN;
	} else
		view = UP;
	if (x <= 0 || y <= 0 ||
			x >= w->oct.faceLength + w->oct.delta ||
			y >= w->oct.faceLength + w->oct.delta)
		return False;
	else if (x + y > w->oct.faceLength) {
		if (x > y)
			*face = 1;
		else if (x < y)
			*face = 2;
		else
			return False;
	} else {
		if (x > y)
			*face = 0;
		else if (x < y)
			*face = 3;
		else
			return False;
	}
	rtt.row = 0;
	while ((x <= (w->oct.size - (rtt.row + 1)) *
			(w->oct.facetLength + w->oct.delta) ||
			x >= w->oct.viewMiddle + (rtt.row + 1) *
			(w->oct.facetLength + w->oct.delta) + 1 ||
			y <= (w->oct.size - (rtt.row + 1)) *
			(w->oct.facetLength + w->oct.delta) ||
			y >= w->oct.viewMiddle + (rtt.row + 1) *
			(w->oct.facetLength + w->oct.delta) + 1) &&
			rtt.row < w->oct.size)
		rtt.row++;
	rtt.trbl = 0;
	while ((x + y) / 2 <= (w->oct.size - (rtt.trbl + 1)) *
			(w->oct.facetLength + w->oct.delta) + 2 * w->oct.delta ||
			(x + y) / 2 >= w->oct.viewMiddle + (rtt.trbl + 1) *
			(w->oct.facetLength + w->oct.delta))
		rtt.trbl++;
	rtt.tlbr = 0;
	while (x <= y - 2 * (rtt.tlbr + 1) *
			(w->oct.facetLength + w->oct.delta) - 2 ||
			y <= x - 2 * (rtt.tlbr + 1) *
			(w->oct.facetLength + w->oct.delta) - 2)
		rtt.tlbr++;
	if (!w->oct.vertical && view == DOWN)
		*face = (*face + MAX_SIDES / 2) % MAX_SIDES;
	*face += MAX_SIDES * (!view);
	if ((*face & 1) == 1) {
		view = rtt.tlbr;
		rtt.tlbr = rtt.trbl;
		rtt.trbl = view;
	}
	*position = toPosition(rtt);
	return True;
}

Boolean
narrowSelection2D(Oct2DWidget w, int style,
		int *face, int *position, int *direction)
{
	int side;

	side = *face % MAX_SIDES;
	if (((*direction & 1) == 0) && ((*direction / 2 + side) & 1) == 0)
		return False;
	if (style == PERIOD3 && (*direction == CW || *direction == CCW)) {
		*face = (*face + 2) % 4 + ((*face < 4) ? 0 : 4);
		*position = 0;
		if (*direction == CW) {
			*direction = (*face * 2 + 2) % 8;
		} else {
			*direction = (*face * 2 + 6) % 8;
		}
	} else if (style == PERIOD4 && (*direction & 1) == 0) {
		if (*direction == (2 * side + 2) % MAX_FACES)
			*direction = CW;
		else	/* *direction == (2 * side + 6) % MAX_FACES */
			*direction = CCW;
	}
#if 0
	if (style == PERIOD4) {
		RTT rtt;
		if (*direction > TL)
			h = rtt.row;
		else if (((*direction / 2) & 1) == 0)
			h = rtt.trbl;
		else /* ((*direction / 2) & 1) == 1*/
			h = rtt.tlbr;
		*position = toPosition(rtt);
	}
#endif
	return True;
}


#ifndef WINVER
static Boolean
setValuesPuzzle2D(Widget current, Widget request, Widget renew)
{
	Oct2DWidget c = (Oct2DWidget) current, w = (Oct2DWidget) renew;
	Boolean redraw = False;
	if (w->oct.size != c->oct.size ||
			w->oct.sticky != c->oct.sticky) {
		resetPieces((OctWidget) w);
		resizePuzzle2D(w);
		redraw = True;
	}
	if (w->oct.facetLength != c->oct.facetLength) {
		resizePuzzle2D(w);
		redraw = True;
	}
	return (redraw);
}
#endif

#ifndef WINVER
static
#endif
void
resizePuzzle2D(Oct2DWidget w)
{
	int tempLength;
#ifdef WINVER
	RECT rect;

	/* Determine size of client area */
	(void) GetClientRect(w->core.hWnd, &rect);
	w->core.width = rect.right;
	w->core.height = rect.bottom;
#endif

	w->oct.delta = 4;
	w->oct.vertical = (w->core.height >= w->core.width);
	if (w->oct.vertical)
		tempLength = MIN(w->core.height / 2, w->core.width);
	else
		tempLength = MIN(w->core.height, w->core.width / 2);
	w->oct.facetLength = MAX((tempLength - w->oct.delta + 1) / w->oct.size, 0);
	w->oct.faceLength = w->oct.size * w->oct.facetLength;
	w->oct.viewLength = w->oct.faceLength + w->oct.delta + 3;
	w->oct.viewMiddle = w->oct.viewLength / 2;
	if (w->oct.vertical) {
		w->oct.puzzleSize.x = w->oct.viewLength - 1;
		w->oct.puzzleSize.y = 2 * w->oct.viewLength - w->oct.delta - 2;
	} else {
		w->oct.puzzleSize.x = 2 * w->oct.viewLength - w->oct.delta - 2;
		w->oct.puzzleSize.y = w->oct.viewLength - 1;
	}
	w->oct.puzzleOffset.x = ((int) w->core.width -
		w->oct.puzzleSize.x) / 2;
	w->oct.puzzleOffset.y = ((int) w->core.height -
		w->oct.puzzleSize.y) / 2;
	resizePieces(w);
}

#ifndef WINVER
static
#endif
void
initializePuzzle2D(
#ifdef WINVER
Oct2DWidget w
#else
Widget request, Widget renew
#endif
)
{
#ifndef WINVER
	Oct2DWidget w = (Oct2DWidget) renew;

	setAllColors((OctWidget) w);
#endif
	w->oct.dim = 2;
	resizePuzzle2D(w);
}

#ifndef WINVER
static
#endif
void
exposePuzzle2D(
#ifdef WINVER
Oct2DWidget w
#else
Widget renew, XEvent *event, Region region
#endif
)
{
#ifndef WINVER
	Oct2DWidget w = (Oct2DWidget) renew;

	if (!w->core.visible)
		return;
#endif
	eraseFrame2D(w);
	drawFrame2D(w, w->oct.focus);
	drawAllPieces((OctWidget) w);
}

#ifndef WINVER
static void
movePuzzleTl(OctWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, TL,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleTop(OctWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, TOP,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleTr(OctWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, TR,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleLeft(OctWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, LEFT,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleRight(OctWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, RIGHT,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBl(OctWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, BL,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBottom(OctWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, BOTTOM,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xkey.state & ControlMask));
}

static void
movePuzzleBr(OctWidget w, XEvent *event, char **args, int nArgs)
{
	movePuzzleInput(w, event->xbutton.x, event->xbutton.y, BR,
		(int) (event->xkey.state & (ShiftMask | LockMask)),
		(int) (event->xkey.state & ControlMask));
}
#endif
