/*
 * @(#)BevelS.c
 *
 * Copyright 2023  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 Bevel */

#include "rngs.h"
#define JMP
#ifdef JMP
#include <setjmp.h> /* longjmp ... interrupt */
#endif
#include "BevelP.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(BevelWidget 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(BevelWidget w)
{
	XEvent event;

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

static void
movePuzzlePiece(BevelWidget 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, control);
}

#define MAX_CORNERS 8
#define MAX_INNER_PIECES 24
static int bevelCorner[MAX_CORNERS][3] =
{
	{1, 4, 5},
	{1, 2, 4},
	{0, 1, 5},
	{0, 2, 1},
	{3, 5, 4},
	{2, 3, 4},
	{0, 5, 3},
	{0, 3, 2}
};

static int bevelCornerOrient[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}
};

static int cornerColor(BevelWidget w, int corner, int n)
{
	return w->bevel.cubeLoc[bevelCorner[corner][n]][bevelCornerOrient[corner][n]].face;
}

static int
findOtherCorner(BevelWidget w, int corner, int color0, int color1)
{
	int i, j, color, count;

	for (i = 0; i < MAX_CORNERS; i++) {
		if (i == corner)
			continue;
		count = 0;
		for (j = 0; j < 3; j++) {
			color = cornerColor(w, i, j);
			if (color == color0 || color == color1)
				count++;
			if (count == 2)
				return i;
		}
	}
	return MAX_CORNERS;
}

#if 0
static int
findCorner(BevelWidget w, int corner)
{
	/* -0-, _3_, 5, 6 or 1, 2, -4-, _7_ */
	int i, j, t, count;
	int c[3];

	c[0] = bevelCorner[corner][0];
	c[1] = bevelCorner[corner][1];
	c[2] = bevelCorner[corner][2];
	for (i = 0; i < MAX_CORNERS; i++) {
		count = 0;
		for (j = 0; j < 3; j++) {
			t = cornerColor(w, i, j);
			if (c[0] == t || c[1] == t || c[2] == t)
				count++;
		}
		if (count == 3) /* Got all 3 */
			return i;
	}
	return MAX_CORNERS;
}
#endif

static void
setSecondCorner(BevelWidget w)
{
	int fixedCorner = 3;
	int color0 = cornerColor(w, fixedCorner, 0);
	int color1 = cornerColor(w, fixedCorner, 1);
	int foundCorner = findOtherCorner(w, fixedCorner, color0, color1);
#ifdef DEBUG
	int targetCorner = 7;
	(void) printf("secondCorner %d\n", targetCorner);
	(void) printf("foundCorner %d\n", foundCorner);
#endif
	switch (foundCorner) {
	case 0:	
		movePuzzlePiece(w, 4, 1, LEFT, FALSE);
		movePuzzlePiece(w, 4, 1, TOP, FALSE);
		movePuzzlePiece(w, 2, 1, TOP, FALSE);
		break;
	case 1:	
		movePuzzlePiece(w, 2, 1, LEFT, FALSE);
		movePuzzlePiece(w, 2, 1, TOP, FALSE);
		break;
	case 2:	
		movePuzzlePiece(w, 0, 0, LEFT, FALSE);
		movePuzzlePiece(w, 0, 1, BOTTOM, FALSE);
		break;
	case 4:	
		movePuzzlePiece(w, 4, 1, TOP, FALSE);
		movePuzzlePiece(w, 2, 1, TOP, FALSE);
		break;
	case 5:	
		movePuzzlePiece(w, 2, 1, TOP, FALSE);
		break;
	case 6:	
		movePuzzlePiece(w, 0, 1, BOTTOM, FALSE);
		break;
	}
	/* rotate "turnstile" if needed */
	if (color0 != w->bevel.cubeLoc[0][1].face) {
		if (color0 == w->bevel.cubeLoc[2][0].face) {
			movePuzzlePiece(w, 3, 3, RIGHT, FALSE);
			movePuzzlePiece(w, 3, 0, BOTTOM, FALSE);
			movePuzzlePiece(w, 3, 1, LEFT, FALSE);
			movePuzzlePiece(w, 3, 2, TOP, FALSE);
		} else {
			movePuzzlePiece(w, 3, 3, BOTTOM, FALSE);
			movePuzzlePiece(w, 3, 2, RIGHT, FALSE);
			movePuzzlePiece(w, 3, 1, TOP, FALSE);
			movePuzzlePiece(w, 3, 0, LEFT, FALSE);
		}
	}
}

static void
setThirdCorner(BevelWidget w)
{
	int fixedCorner = 3;
	int color0 = cornerColor(w, fixedCorner, 1);
	int color1 = cornerColor(w, fixedCorner, 2);
	int foundCorner = findOtherCorner(w, fixedCorner, color0, color1);
#ifdef DEBUG
	int targetCorner = 1;
	(void) printf("thirdCorner %d\n", targetCorner);
	(void) printf("foundCorner %d\n", foundCorner);
#endif
	switch (foundCorner) {
	case 0:	
		movePuzzlePiece(w, 4, 2, TOP, FALSE);
		break;
	case 2:	
		movePuzzlePiece(w, 5, 2, TOP, FALSE);
		movePuzzlePiece(w, 4, 2, TOP, FALSE);
		break;
	case 4:	
		movePuzzlePiece(w, 4, 1, TOP, FALSE);
		movePuzzlePiece(w, 2, 1, LEFT, FALSE);
		break;
	case 5:	
		movePuzzlePiece(w, 2, 1, LEFT, FALSE);
		break;
	case 6:	
		movePuzzlePiece(w, 5, 1, TOP, FALSE);
		movePuzzlePiece(w, 4, 1, TOP, FALSE);
		movePuzzlePiece(w, 2, 1, LEFT, FALSE);
		break;
	}
	/* rotate "turnstile" if needed */
	if (color0 != w->bevel.cubeLoc[2][2].face) {
		if (color0 == w->bevel.cubeLoc[1][1].face) {
			movePuzzlePiece(w, 4, 3, RIGHT, FALSE);
			movePuzzlePiece(w, 4, 0, BOTTOM, FALSE);
			movePuzzlePiece(w, 4, 1, LEFT, FALSE);
			movePuzzlePiece(w, 4, 2, TOP, FALSE);
		} else {
			movePuzzlePiece(w, 4, 3, BOTTOM, FALSE);
			movePuzzlePiece(w, 4, 2, RIGHT, FALSE);
			movePuzzlePiece(w, 4, 1, TOP, FALSE);
			movePuzzlePiece(w, 4, 0, LEFT, FALSE);
		}
	}
}

static void
setForthCorner(BevelWidget w)
{
	int fixedCorner = 1;
	int color0 = cornerColor(w, fixedCorner, 1);
	int color1 = cornerColor(w, fixedCorner, 2);
	int foundCorner = findOtherCorner(w, fixedCorner, color0, color1);
#ifdef DEBUG
	int targetCorner = 5;
	(void) printf("forthCorner %d\n", targetCorner);
	(void) printf("foundCorner %d\n", foundCorner);
#endif
	switch (foundCorner) {
	case 0:	
		movePuzzlePiece(w, 4, 1, RIGHT, FALSE);
		movePuzzlePiece(w, 4, 1, TOP, FALSE);
		break;
	case 2:	
		movePuzzlePiece(w, 5, 1, RIGHT, FALSE);
		movePuzzlePiece(w, 5, 1, TOP, FALSE);
		movePuzzlePiece(w, 4, 1, TOP, FALSE);
		break;
	case 4:	
		movePuzzlePiece(w, 4, 1, TOP, FALSE);
		break;
	case 6:	
		movePuzzlePiece(w, 5, 1, TOP, FALSE);
		movePuzzlePiece(w, 4, 1, TOP, FALSE);
		break;
	}
	/* rotate "turnstile" if needed */
	if (color0 != w->bevel.cubeLoc[2][1].face) {
		if (color0 == w->bevel.cubeLoc[4][0].face) {
			movePuzzlePiece(w, 4, 1, TOP, FALSE);
			movePuzzlePiece(w, 5, 0, BOTTOM, FALSE);
			movePuzzlePiece(w, 5, 1, LEFT, FALSE);
			movePuzzlePiece(w, 5, 2, TOP, FALSE);
			movePuzzlePiece(w, 5, 3, RIGHT, FALSE);
			movePuzzlePiece(w, 4, 1, TOP, FALSE);
		} else {
			movePuzzlePiece(w, 4, 1, TOP, FALSE);
			movePuzzlePiece(w, 5, 0, LEFT, FALSE);
			movePuzzlePiece(w, 5, 3, BOTTOM, FALSE);
			movePuzzlePiece(w, 5, 2, RIGHT, FALSE);
			movePuzzlePiece(w, 5, 1, TOP, FALSE);
			movePuzzlePiece(w, 4, 1, TOP, FALSE);
		}
	}
}
/* 2 6
 2 3 7 6
 0 1 5 4
   0 4
   2 6*/

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