/*
 * @(#)DinoS.c
 *
 * Break ability taken from the X puzzle by Don Bennett, HP Labs
 *
 * Copyright 2010 - 2013  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.
 */

/* Solver file for Dino */

/* This puzzle is relatively easy as an edge cannot be flipped upsidedown. */
/* There are 2 different orientations possible when solved, red, yellow, */
/* blue in clockwise or counterclockwise order, in orientation mode, the */
/* order of the colors is clockwise. */

/* To swap from one orientation to other
1.  turn 4 non-overlapping corners clockwise (or counterclockwise),
2.  turn the other non-overlapping corners clockwise (or counterclockwise),
3.  repeat steps 1 and 2 now turning counterclockwise (clockwise) */

#include "rngs.h"
#define JMP
#ifdef JMP
#include <setjmp.h> /* longjmp ... interrupt */
#endif
#include "DinoP.h"

static Boolean solvingFlag = False;
#ifdef JMP
static Boolean abortSolvingFlag = False;
static jmp_buf solve_env;

static void
abortSolving(void)
{
	if (solvingFlag)
		abortSolvingFlag = True;
}

#ifdef WINVER
static Boolean
processMessage(UINT msg)
{
	switch (msg) {
	case WM_KEYDOWN:
	case WM_CLOSE:
	case WM_LBUTTONDOWN:
	case WM_RBUTTONDOWN:
		abortSolving();
		return True;
	default:
		return False;
	}
}
#else
static void
processButton(void /*XButtonEvent *event*/)
{
	abortSolving();
}

static void
processVisibility(XVisibilityEvent *event)
{
	if (event->state != VisibilityUnobscured)
		abortSolving();
}

static void
getNextEvent(DinoWidget w, XEvent *event)
{
	if (!XCheckMaskEvent(XtDisplay(w), VisibilityChangeMask, event))
		(void) XNextEvent(XtDisplay(w), event);
}

static void
processEvent(XEvent *event)
{
	switch(event->type) {
	case KeyPress:
	case ButtonPress:
		processButton(/*&event->xbutton*/);
		break;
	case VisibilityNotify:
		processVisibility(&event->xvisibility);
		break;
	default:
		break;
	}
}

static void
processEvents(DinoWidget w)
{
	XEvent event;

	while (XPending(XtDisplay(w))) {
		getNextEvent(w, &event);
		processEvent(&event);
	}
}
#endif
#endif

static void
movePuzzlePiece(DinoWidget w, int face, int position,
	int direction, int control)
{
#ifdef JMP
#ifdef WINVER
	MSG msg;

	if (PeekMessage(&msg, NULL, 0, 0, 0)) {
		if (!processMessage(msg.message)) {
			if (GetMessage(&msg, NULL, 0, 0))
				DispatchMessage(&msg);
		}
	}
#else
	processEvents(w);
#endif
	if (solvingFlag && abortSolvingFlag)
		longjmp(solve_env, 1);
#endif
	movePuzzleDelay(w, face, position, direction, CORNER, control);
}

#define MAX_CORNERS 8
#define MAX_EDGES 12
static int dinoCorner[MAX_CORNERS][3] =
{
	{1, 4, 5},
	{1, 2, 4},
	{0, 1, 5},
	{0, 2, 1},
	{0, 5, 3},
	{3, 5, 4},
	{0, 3, 2},
	{2, 3, 4}
};

#if 0
static int dinoCornerOrient[MAX_CORNERS][3] =
{
	{2, 2, 3},
	{1, 2, 3},
	{3, 3, 2},
	{2, 3, 0},
	{1, 0, 1},
	{1, 2, 0},
	{0, 1, 0},
	{1, 3, 0}
};
#endif

static int dinoEdge[MAX_EDGES][2] =
{
	{4, 5}, {2, 4}, {5, 0}, {0, 2},
	{1, 5}, {1, 2}, {3, 5}, {3, 2},
	{1, 4}, {1, 0}, {3, 4}, {3, 0}
};

static int dinoEdgeOrient[MAX_EDGES][2] =
{
	{2, 0}, {2, 0}, {2, 0}, {2, 0},
	{3, 3}, {1, 3}, {1, 1}, {3, 1},
	{2, 3}, {0, 3}, {2, 1}, {0, 1}
};

/* Corner layout, bits correspond to position
   2 6
 2 3 7 6
 0 1 5 4
   0 4
   2 6
  Edge layout
     2
   9  11
     3
4  5  10  6
     1
   8   7
     0
   4   6
     2
 */

static int edgeColor(DinoWidget w, int edge, int n)
{
	return w->dino.cubeLoc[dinoEdge[edge][n]][dinoEdgeOrient[edge][n]].face;
}

static int
findCorner(Boolean reverse, int colorFace1, int colorFace2)
{
	int corner, face, i, j;

	if (reverse) {
		i = 2;
		j = 1;
	} else {
		i = 1;
		j = 2;
	}
	for (corner = 0; corner < MAX_CORNERS; corner++) {
		for (face = 0; face < 3; face++) {
			if (dinoCorner[corner][face] == colorFace1 &&
					dinoCorner[corner][(face + i) % 3] == colorFace2) {
				return dinoCorner[corner][(face + j) % 3];
			}
		}
	}
	return -1;
}

static int
getEdgeIndex(int color0, int color1)
{
	int i;

	for (i = 0; i < MAX_EDGES; i++) {
		if ((dinoEdge[i][0] == color0 && dinoEdge[i][1] == color1) ||
				(dinoEdge[i][1] == color0 && dinoEdge[i][0] == color1))
			return i;
	}
	return MAX_EDGES;
}

static int
findEdge(DinoWidget w, int edge)
{
	int i, j, t, count;
	int c[2];

	c[0] = dinoEdge[edge][0];
	c[1] = dinoEdge[edge][1];
	for (i = 0; i < MAX_EDGES; i++) {
		count = 0;
		for (j = 0; j < 2; j++) {
			t = edgeColor(w, i, j);
			if (c[0] == t || c[1] == t)
				count++;
		}
		if (count == 2)
			return i;
	}
	return MAX_EDGES;
}

static void
setFirstEdge(DinoWidget w, int i)
{
	int e = findEdge(w, i);

#if DEBUG
	(void) printf("nextEdge %d, (%d)\n", e, i);
#endif
	switch (e) {
	case 0:
		movePuzzlePiece(w, 5, 0, BL, FALSE);
		break;
	case 1:
		movePuzzlePiece(w, 4, 0, BL, FALSE);
		movePuzzlePiece(w, 4, 2, TL, FALSE);
		break;
	case 3:
		movePuzzlePiece(w, 2, 0, BL, FALSE);
		movePuzzlePiece(w, 1, 1, BL, FALSE);
		movePuzzlePiece(w, 4, 2, TL, FALSE);
		break;
	case 4:
		break;
	case 5:
		movePuzzlePiece(w, 1, 1, BL, FALSE);
		movePuzzlePiece(w, 4, 2, TL, FALSE);
		break;
	case 6:
		movePuzzlePiece(w, 5, 1, TL, FALSE);
		movePuzzlePiece(w, 5, 0, BL, FALSE);
		break;
	case 7:
		movePuzzlePiece(w, 2, 1, BL, FALSE);
		movePuzzlePiece(w, 4, 0, BL, FALSE);
		movePuzzlePiece(w, 4, 2, TL, FALSE);
		break;
	case 8:
		movePuzzlePiece(w, 4, 2, TL, FALSE);
		break;
	case 9:
		movePuzzlePiece(w, 1, 0, BR, FALSE);
		movePuzzlePiece(w, 1, 1, BL, FALSE);
		movePuzzlePiece(w, 4, 2, TL, FALSE);
		break;
	case 10:
		movePuzzlePiece(w, 4, 1, BL, FALSE);
		movePuzzlePiece(w, 5, 0, BL, FALSE);
		break;
	case 11:
		movePuzzlePiece(w, 5, 2, TR, FALSE);
		movePuzzlePiece(w, 5, 2, TL, FALSE);
		movePuzzlePiece(w, 5, 1, BL, FALSE);
		break;
	case 2:
	default:
		(void) printf("Wrong edge %d\n", e);
		return;
	}
}

static void
setSecondEdge(DinoWidget w, int i)
{
	int e = findEdge(w, i);

#if DEBUG
	(void) printf("nextEdge %d, (%d)\n", e, i);
#endif
	switch (e) {
	case 0:
		movePuzzlePiece(w, 1, 0, BL, FALSE);
		movePuzzlePiece(w, 1, 3, BR, FALSE);
		movePuzzlePiece(w, 1, 3, TR, FALSE);
		break;
	case 1:
		movePuzzlePiece(w, 2, 2, TL, FALSE);
		movePuzzlePiece(w, 1, 1, TL, FALSE);
		break;
	case 3:
		movePuzzlePiece(w, 0, 2, TL, FALSE);
		break;
	case 5:
		movePuzzlePiece(w, 1, 1, TL, FALSE);
		break;
	case 6:
		movePuzzlePiece(w, 0, 3, TR, FALSE);
		movePuzzlePiece(w, 5, 1, BL, FALSE);
		movePuzzlePiece(w, 0, 0, BL, FALSE);
		break;
	case 7:
		movePuzzlePiece(w, 2, 1, TL, FALSE);
		movePuzzlePiece(w, 0, 2, TL, FALSE);
		break;
	case 8:
		movePuzzlePiece(w, 1, 2, TR, FALSE);
		movePuzzlePiece(w, 1, 1, TL, FALSE);
		break;
	case 9:
		break;
	case 10:
		if (NRAND(2) == 0) {
			movePuzzlePiece(w, 4, 1, TL, FALSE);
			movePuzzlePiece(w, 2, 2, TL, FALSE);
			movePuzzlePiece(w, 1, 1, TL, FALSE);
		} else {
			movePuzzlePiece(w, 3, 2, TL, FALSE);
			movePuzzlePiece(w, 2, 1, TL, FALSE);
			movePuzzlePiece(w, 0, 2, TL, FALSE);
		}
		break;
	case 11:
		movePuzzlePiece(w, 0, 1, BL, FALSE);
		movePuzzlePiece(w, 0, 2, TL, FALSE);
		break;
	case 2:
	case 4:
	default:
		(void) printf("Wrong edge %d\n", e);
		return;
	}
}

static void
setThirdEdge(DinoWidget w, int i)
{
	int e = findEdge(w, i);

#if DEBUG
	(void) printf("nextEdge %d, (%d)\n", e, i);
#endif
	switch (e) {
	case 0:
		movePuzzlePiece(w, 5, 1, BL, FALSE);
		movePuzzlePiece(w, 5, 0, BR, FALSE);
		movePuzzlePiece(w, 5, 2, TR, FALSE);
		break;
	case 1:
		movePuzzlePiece(w, 2, 2, TR, FALSE);
		movePuzzlePiece(w, 3, 3, TR, FALSE);
		break;
	case 3:
		movePuzzlePiece(w, 0, 2, TR, FALSE);
		break;
	case 5:
		movePuzzlePiece(w, 2, 3, BR, FALSE);
		movePuzzlePiece(w, 2, 2, TR, FALSE);
		movePuzzlePiece(w, 3, 3, TR, FALSE);
		break;
	case 6:
		movePuzzlePiece(w, 3, 1, BL, FALSE);
		movePuzzlePiece(w, 3, 2, TL, FALSE);
		movePuzzlePiece(w, 3, 3, TR, FALSE);
		break;
	case 7:
		movePuzzlePiece(w, 3, 3, TR, FALSE);
		break;
	case 8:
		movePuzzlePiece(w, 4, 3, TR, FALSE);
		movePuzzlePiece(w, 2, 2, TR, FALSE);
		movePuzzlePiece(w, 3, 3, TR, FALSE);
		break;
	case 10:
		movePuzzlePiece(w, 3, 2, TL, FALSE);
		movePuzzlePiece(w, 3, 3, TR, FALSE);
		break;
	case 11:
		break;
	case 2:
	case 4:
	case 9:
	default:
		(void) printf("Wrong edge %d\n", e);
		return;
	}
}

static void
setForthEdge(DinoWidget w, int i)
{
	int e = findEdge(w, i);

#if DEBUG
	(void) printf("nextEdge %d, (%d)\n", e, i);
#endif
	switch (e) {
	case 0:
		movePuzzlePiece(w, 4, 2, TR, FALSE);
		movePuzzlePiece(w, 2, 0, BR, FALSE);
		movePuzzlePiece(w, 3, 2, TL, FALSE);
		movePuzzlePiece(w, 2, 1, TL, FALSE);
		break;
	case 1:
		movePuzzlePiece(w, 2, 0, BR, FALSE);
		movePuzzlePiece(w, 2, 2, TR, FALSE);
		movePuzzlePiece(w, 2, 1, TL, FALSE);
		break;
	case 3:
		break;
	case 5:
		movePuzzlePiece(w, 2, 3, BR, FALSE);
		movePuzzlePiece(w, 2, 0, BL, FALSE);
		movePuzzlePiece(w, 2, 2, TL, FALSE);
		movePuzzlePiece(w, 2, 3, TR, FALSE);
		break;
	case 6:
		movePuzzlePiece(w, 3, 1, BL, FALSE);
		movePuzzlePiece(w, 2, 0, BR, FALSE);
		movePuzzlePiece(w, 3, 2, TL, FALSE);
		movePuzzlePiece(w, 2, 1, TL, FALSE);
		break;
	case 7:
		movePuzzlePiece(w, 2, 1, BL, FALSE);
		movePuzzlePiece(w, 2, 0, BR, FALSE);
		movePuzzlePiece(w, 2, 2, TR, FALSE);
		movePuzzlePiece(w, 2, 1, TL, FALSE);
		break;
	case 8:
		movePuzzlePiece(w, 2, 0, BL, FALSE);
		movePuzzlePiece(w, 1, 2, TR, FALSE);
		movePuzzlePiece(w, 2, 3, TR, FALSE);
		break;
	case 10:
		movePuzzlePiece(w, 2, 0, BR, FALSE);
		movePuzzlePiece(w, 3, 2, TL, FALSE);
		movePuzzlePiece(w, 2, 1, TL, FALSE);
		break;
	case 2:
	case 4:
	case 9:
	case 11:
	default:
		(void) printf("Wrong edge %d\n", e);
	}
}

static void
setFifthEdge(DinoWidget w, int i)
{
	int e = findEdge(w, i);

#if DEBUG
	(void) printf("nextEdge %d, (%d)\n", e, i);
#endif
	switch (e) {
	case 0:
		movePuzzlePiece(w, 4, 2, TR, FALSE);
		movePuzzlePiece(w, 3, 2, TL, FALSE);
		break;
	case 1:
		movePuzzlePiece(w, 2, 2, TR, FALSE);
		break;
	case 5:
		movePuzzlePiece(w, 2, 3, BR, FALSE);
		movePuzzlePiece(w, 2, 2, TR, FALSE);
		break;
	case 6:
		movePuzzlePiece(w, 3, 1, BL, FALSE);
		movePuzzlePiece(w, 3, 2, TL, FALSE);
		break;
	case 7:
		break;
	case 8:
		movePuzzlePiece(w, 4, 3, TR, FALSE);
		movePuzzlePiece(w, 2, 2, TR, FALSE);
		break;
	case 10:
		movePuzzlePiece(w, 3, 2, TL, FALSE);
		break;
	case 2:
	case 3:
	case 4:
	case 9:
	case 11:
	default:
		(void) printf("Wrong edge %d\n", e);
	}
}

static void
setSixthEdge(DinoWidget w, int i)
{
	int e = findEdge(w, i);

#if DEBUG
	(void) printf("nextEdge %d, (%d)\n", e, i);
#endif
	switch (e) {
	case 0:
		movePuzzlePiece(w, 4, 2, TL, FALSE);
		movePuzzlePiece(w, 4, 0, BL, FALSE);
		movePuzzlePiece(w, 4, 3, BR, FALSE);
		break;
	case 1:
		movePuzzlePiece(w, 2, 2, TL, FALSE);
		break;
	case 5:
		break;
	case 6:
		movePuzzlePiece(w, 3, 1, BL, FALSE);
		movePuzzlePiece(w, 4, 1, TL, FALSE);
		movePuzzlePiece(w, 2, 2, TL, FALSE);
		movePuzzlePiece(w, 4, 0, BR, FALSE);
		break;
	case 8:
		movePuzzlePiece(w, 4, 0, BL, FALSE);
		break;
	case 10:
		movePuzzlePiece(w, 4, 1, TL, FALSE);
		movePuzzlePiece(w, 2, 2, TL, FALSE);
		movePuzzlePiece(w, 4, 0, BR, FALSE);
		break;
	case 2:
	case 3:
	case 4:
	case 7:
	case 9:
	case 11:
	default:
		(void) printf("Wrong edge %d\n", e);
	}
}

static void
setSeventhEdge(DinoWidget w, int i)
{
	int e = findEdge(w, i);

#if DEBUG
	(void) printf("nextEdge %d, (%d)\n", e, i);
#endif
	switch (e) {
	case 0:
		movePuzzlePiece(w, 4, 2, TR, FALSE);
		movePuzzlePiece(w, 4, 3, BR, FALSE);
		movePuzzlePiece(w, 4, 1, BL, FALSE);
		movePuzzlePiece(w, 4, 2, TL, FALSE);
		break;
	case 1:
		movePuzzlePiece(w, 4, 1, TL, FALSE);
		movePuzzlePiece(w, 4, 3, TR, FALSE);
		movePuzzlePiece(w, 4, 0, BR, FALSE);
		movePuzzlePiece(w, 4, 0, BL, FALSE);
		break;
	case 6:
		movePuzzlePiece(w, 5, 0, BL, FALSE);
		movePuzzlePiece(w, 5, 1, TL, FALSE);
		movePuzzlePiece(w, 5, 3, TR, FALSE);
		break;
	case 8:
		break;
	case 10:
		movePuzzlePiece(w, 4, 3, BR, FALSE);
		movePuzzlePiece(w, 4, 1, BL, FALSE);
		movePuzzlePiece(w, 4, 2, TL, FALSE);
		break;
	case 2:
	case 3:
	case 4:
	case 5:
	case 7:
	case 9:
	case 11:
	default:
		(void) printf("Wrong edge %d\n", e);
	}
}

static void
setEighthEdge(DinoWidget w, int i)
{
	int e = findEdge(w, i);

#if DEBUG
	(void) printf("nextEdge %d, (%d)\n", e, i);
#endif
	switch (e) {
	case 0:
		movePuzzlePiece(w, 4, 0, BR, FALSE);
		movePuzzlePiece(w, 4, 2, TR, FALSE);
		movePuzzlePiece(w, 4, 1, TL, FALSE);
		break;
	case 1:
		break;
	case 6:
		movePuzzlePiece(w, 4, 0, BR, FALSE);
		movePuzzlePiece(w, 4, 1, BL, FALSE);
		movePuzzlePiece(w, 4, 1, TL, FALSE);
		break;
	case 10:
		movePuzzlePiece(w, 4, 1, BL, FALSE);
		movePuzzlePiece(w, 4, 0, BR, FALSE);
		movePuzzlePiece(w, 4, 2, TR, FALSE);
		movePuzzlePiece(w, 4, 1, TL, FALSE);
		break;
	case 2:
	case 3:
	case 4:
	case 5:
	case 7:
	case 8:
	case 9:
	case 11:
	default:
		(void) printf("Wrong edge %d\n", e);
	}
}

static void
setLastEdges(DinoWidget w)
{
	int f;

	movePuzzlePiece(w, 0, 1, TL, TRUE);
	f = w->dino.cubeLoc[0][0].face;
	if (w->dino.cubeLoc[0][1].face == f) {
		return;
	} else if (w->dino.cubeLoc[2][0].face == f) {
		movePuzzlePiece(w, 2, 1, TL, FALSE);
	} else {
		movePuzzlePiece(w, 0, 1, BL, FALSE);
	}
}

/* This solves red, yellow, blue in clockwise order unless reverse. */
static void
solveEdges(DinoWidget w)
{
	int faces[6];
	Boolean reverse = (w->dino.orient) ? False : (NRAND(2) == 0);

	faces[5] = w->dino.cubeLoc[5][2].face;
	faces[0] = w->dino.cubeLoc[0][0].face;
	faces[1] = findCorner(reverse, faces[5], faces[0]);
#ifdef DEBUG
	(void) printf("zeroethEdge %d %d %d\n", faces[5], faces[0], faces[1]);
#endif
	if (faces[1] < 0) {
		(void) printf("Wrong face %d\n", faces[1]);
		return;
	}
	setFirstEdge(w, getEdgeIndex(faces[1], faces[5]));
	setSecondEdge(w, getEdgeIndex(faces[0], faces[1]));
	faces[3] = findCorner(reverse, faces[0], faces[5]);
	if (faces[3] < 0) {
		(void) printf("Wrong face %d\n", faces[3]);
		return;
	}
	setThirdEdge(w, getEdgeIndex(faces[0], faces[3]));
	faces[2] = findCorner(reverse, faces[1], faces[0]);
	if (faces[2] < 0) {
		(void) printf("Wrong face %d\n", faces[2]);
		return;
	}
	setForthEdge(w, getEdgeIndex(faces[0], faces[2]));
	setFifthEdge(w, getEdgeIndex(faces[2], faces[3]));
	setSixthEdge(w, getEdgeIndex(faces[1], faces[2]));
	faces[4] = findCorner(reverse, faces[1], faces[2]);
	if (faces[4] < 0) {
		(void) printf("Wrong face %d\n", faces[4]);
		return;
	}
	setSeventhEdge(w, getEdgeIndex(faces[1], faces[4]));
	setEighthEdge(w, getEdgeIndex(faces[2], faces[4]));
	setLastEdges(w);
}

/* This procedure coordinates the solution process. */
void
solveSomePieces(DinoWidget w)
{
	setPuzzle(w, ACTION_RESET);
	if (solvingFlag)
		return;
#ifdef JMP
	if (!setjmp(solve_env))
#endif
	{
		solvingFlag = True;
		solveEdges(w);
	}
#ifdef JMP
	abortSolvingFlag = False;
#endif
	solvingFlag = False;
	w->dino.cheat = True; /* Assume the worst. */
	setPuzzle(w, ACTION_CHEAT);
	setPuzzle(w, ACTION_COMPUTED);
}
