/*
 * @(#)OctF.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.
 */

/* Find moves file for Oct */

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

static Boolean findingFlag = False;
#ifdef JMP
static Boolean abortFindingFlag = False;
static jmp_buf find_env;

static void
abortFinding(void)
{
	if (findingFlag)
		abortFindingFlag = True;
}

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

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

static void
getNextEvent(OctWidget 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(OctWidget w)
{
	XEvent event;

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

static void
movePuzzlePiece(OctWidget w, int face, int position,
	int direction, int style, 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 (findingFlag && abortFindingFlag)
		longjmp(find_env, 1);
#endif
	movePuzzleDelay(w, face, position, direction, style, control);
}

static void
rotateCenterByFace(OctWidget w, int face, int dir)
{
	int position = (w->oct.size == 2) ? 2 : 6;
	movePuzzlePiece(w, face, position, dir, PERIOD3, FALSE);
}

static void
rotateCenterByCorner(OctWidget w, int face, int dir)
{
	int position = (w->oct.size == 2) ? 2 : 6;
	movePuzzlePiece(w, face, position, dir, PERIOD4, FALSE);
}

/*
static int checkFace[4] = {0, 2, 5, 7};
static int oppFace[8] = {4, 7, 6, 5, 0, 3, 2, 1};
*/

static int
checkIfEqual(OctWidget w, int face, int corner1, int corner2) {
	return (w->oct.facetLoc[face][corner1].face ==
		w->oct.facetLoc[face][corner2].face &&
		w->oct.facetLoc[face][corner1].rotation ==
		w->oct.facetLoc[face][corner2].rotation) ? 1: 0;
}

static int
checkIfEqualFace(OctWidget w, int face, int corner1, int corner2) {
	return (w->oct.facetLoc[face][corner1].face ==
		w->oct.facetLoc[face][corner2].face) ? 1 : 0;
}

static int
checkJustRotated(OctWidget w, int face, int corner1, int corner2) {
	return (w->oct.facetLoc[face][corner1].face ==
		w->oct.facetLoc[face][corner2].face &&
		w->oct.facetLoc[face][corner1].rotation !=
		w->oct.facetLoc[face][corner2].rotation) ? 1: 0;
}

static Boolean
checkClosePeriod3x2(OctWidget w, int *cornerCount, int *centerCount)
{
	int face, position, count, total = 14;
	*cornerCount = 0;
	*centerCount = 0;
	for (face = 0; face < MAX_FACES; face++) {
		for (position = 0; position < 4; position++) {
			switch (position) {
			case 0:
			case 1:
			case 3:
				*cornerCount += checkIfEqual(w, face, 0, position);
				break;
			case 2:
				*centerCount += checkIfEqual(w, face, 0, position);
				break;
			}
		}
	}
	*cornerCount /= 4;
	count = total - *cornerCount - *centerCount;
	/*(void) printf("count = %d %d %d %d\n",
		count, *cornerCount, *edgeCount, *centerCount);*/
	return count;
}

static Boolean
checkClosePeriod3x3(OctWidget w, int *cornerCount, int *edgeCount, int *centerCount)
{
	int face, position, count, total = 72;
	*cornerCount = 0;
	*edgeCount = 0;
	*centerCount = 0;
	for (face = 0; face < MAX_FACES; face++) {
		for (position = 0; position < 9; position++) {
			switch (position) {
			case 0:
			case 4:
			case 8:
				*cornerCount += checkIfEqual(w, face, 0, position);
				break;
			case 1:
			case 3:
			case 6:
				*edgeCount += checkIfEqual(w, face, 0, position);
				break;
			case 2:
			case 5:
			case 7:
				*centerCount += checkIfEqual(w, face, 0, position);
				break;
			}
		}
	}
	*cornerCount /= 4;
	*edgeCount /= 2;
	count = total - *cornerCount - *edgeCount - *centerCount;
	/*(void) printf("count = %d %d %d %d\n",
		count, *cornerCount, *edgeCount, *centerCount);*/
	return count;
}

static Boolean
checkClosePeriod4x4(OctWidget w, int *cornerCount, int *edgeCount, int *centerCount, int *edgeLRCount, int *exactCenterCount, int *rotatedCount)
{
	int face, position, count, total = 56; /* 6 + 12 + 6 + 24 + 8 */
	if (*cornerCount != -1)
		*cornerCount = 0;
	if (*edgeCount != -1)
		*edgeCount = 0;
	if (*centerCount != -1)
		*centerCount = 0;
	if (*edgeLRCount != -1)
		*edgeLRCount = 0;
	if (*exactCenterCount != -1)
		*exactCenterCount = 0;
	if (*rotatedCount != -1)
		*rotatedCount = 0;
	for (face = 0; face < MAX_FACES; face++) {
		for (position = 0; position < 16; position++) {
			switch (position) {
			case 0:
			case 9:
			case 15:
				*cornerCount += checkIfEqualFace(w, face, 0, position);
				break;
			case 5:
			case 7:
			case 12:
				*edgeCount += checkIfEqualFace(w, face, 0, position);
				break;
			case 2:
			case 10:
			case 14:
				*centerCount += checkIfEqualFace(w, face, 0, position);
				break;
			case 1:
			case 3:
			case 4:
			case 8:
			case 11:
			case 13:
				*edgeLRCount += checkIfEqualFace(w, face, 0, position);
				break;
			case 6:
				*exactCenterCount += checkIfEqualFace(w, face, 0, position);
				*rotatedCount += checkJustRotated(w, face, 0, position);
				break;
			}
		}
	}
	*cornerCount /= 4;
	*edgeCount /= 2;
	*centerCount /= 4;
	*edgeLRCount /= 2;
	count = total - *cornerCount - *edgeCount - *centerCount
		- *edgeLRCount - *exactCenterCount;
	/*(void) printf("count = %d %d %d %d %d %d\n",
		count, *cornerCount, *edgeCount, *centerCount,
		*edgeLRCount, *exactCenterCount);*/
	if (count == 0)
		return count - *rotatedCount;
	else
		return count;
}

static int
oppDir(int dir)
{
	switch(dir) {
	case TOP:
		return BOTTOM;
	case TR:
		return BL;
	case RIGHT:
		return LEFT;
	case BR:
		return TL;
	case BOTTOM:
		return TOP;
	case BL:
		return BR;
	case LEFT:
		return RIGHT;
	case TL:
		return BR;
	case CW:
		return CCW;
	case CCW:
		return CW;
	}
	return -1;
}

static void
findMovesPeriod3x2(OctWidget w, FILE *fp) {
	int maxChanged = 4, maxChangedLog = 6, maxMovesCheck = 12; /*change to suit */
	int i, face, position, dir, changedTotal = 14;
	int lastMoveFace = -1, lastMoveDir = -1;
	int cornerCount = 0, centerCount = 0;
	OctStack log = {NULL, NULL, NULL, 0};

	newMoves(&log);
	for (i = 0; i < maxMovesCheck; i++) {
		do {
#if 0
			/*face = NRAND(MAX_FACES >> 1);*/
			face = NRAND(3);
#endif
			face = NRAND(4);
			dir = (NRAND(2)) ? CCW : CW;
			position = 2;
		} while (lastMoveFace == face);
		setMove(&log, dir, 3, False, face, position);
		rotateCenterByFace(w, face, /*position, */ dir);
		if (lastMoveDir >= 0)
			changedTotal = checkClosePeriod3x2(w,
				&cornerCount, &centerCount);
		lastMoveFace = face;
		if (changedTotal == 0) {
			(void) printf("solved\n");
			break;
		} else if (changedTotal <= maxChanged) {
			printMoves(fp, &log);
			(void) fprintf(fp,
				"moves: %d, changed: total=%d corner=%d center=%d\n",
				i + 1, changedTotal, cornerCount, centerCount);
			(void) printf("%d in %d moves!\n", changedTotal, i + 1);
			break;
		}
		if (changedTotal < maxChangedLog)
			(void) printf("%d\n", changedTotal);
	}
	flushMoves(w, &log, False);
	clearPieces(w);
}

static void
findMovesPeriod3x3(OctWidget w, FILE *fp) {
	int maxChanged = 8, maxChangedLog = 12, maxMovesCheck = 18; /*change to suit */
	int i, face, position, dir, changedTotal = 72;
	int lastMoveFace = -1, lastMoveDir = -1;
	int cornerCount = 0, edgeCount = 0, centerCount = 0;
	OctStack log = {NULL, NULL, NULL, 0};

	newMoves(&log);
	for (i = 0; i < maxMovesCheck; i++) {
		do {
			/*face = NRAND(MAX_FACES >> 1);*/
			face = NRAND(3);
			if ((face & 1) == 1)
				dir = (NRAND(2)) ? TOP : BOTTOM;
			else
				dir = (NRAND(2)) ? LEFT : RIGHT;
			position = 2;
		} while (lastMoveFace == face && lastMoveDir == oppDir(dir));
		setMove(&log, dir, 3, False, face, position);
		rotateCenterByFace(w, face, dir);
		if (lastMoveDir >= 0)
			changedTotal = checkClosePeriod3x3(w,
				&cornerCount, &edgeCount, &centerCount);
		lastMoveFace = face;
		lastMoveDir = dir;
		if (changedTotal == 0) {
			(void) printf("solved\n");
			break;
		} else if (changedTotal <= maxChanged) {
			printMoves(fp, &log);
			(void) fprintf(fp,
				"moves: %d, changed: total=%d corner=%d edge=%d center=%d\n",
				i + 1, changedTotal, cornerCount, edgeCount, centerCount);
			(void) printf("%d in %d moves!\n", changedTotal, i + 1);
			break;
		}
		if (changedTotal < maxChangedLog)
			(void) printf("%d\n", changedTotal);
	}
	flushMoves(w, &log, False);
	clearPieces(w);
}

static void
findMovesPeriod4x4(OctWidget w, FILE *fp) {
	int maxChanged = 4, maxChangedLog = 5, maxMovesCheck = 18; /*change to suit */
	int i, face, position, dir, changedTotal = 128;
	int lastMoveFace = -1, lastMoveDir = -1;
	int cornerCount = 0, edgeCount = 0, centerCount = 0,
		edgeLRCount = 0, exactCenterCount = 0, rotatedCount = 0;
	OctStack log = {NULL, NULL, NULL, 0};

	newMoves(&log);
	for (i = 0; i < maxMovesCheck; i++) {
		do {
			/*face = NRAND(MAX_FACES >> 1);*/
			face = NRAND(2) ? 0 : 4;
			dir = NRAND(10);
			if (dir == 8)
				dir = CW;
			else if (dir == 9)
				dir = CCW;
			position = 6;
		} while (lastMoveFace == face && lastMoveDir == oppDir(dir));
		setMove(&log, dir, 4, False, face, position);
		rotateCenterByCorner(w, face, dir);
		if (lastMoveDir >= 0)
			changedTotal = checkClosePeriod4x4(w,
				&cornerCount, &edgeCount, &centerCount,
				&edgeLRCount, &exactCenterCount, &rotatedCount);
		lastMoveFace = face;
		lastMoveDir = dir;
		if (changedTotal == 0) {
			(void) printf("solved\n");
			break;
		} else if (changedTotal <= maxChanged) {
			/* weeding out moves already found */
			if (exactCenterCount > 3) {
				break;
			}
			printMoves(fp, &log);
			(void) fprintf(fp,
				"moves: %d, changed: total=%d corner=%d edge=%d center=%d edgeLR=%d exactCenter=%d, rotated=%d\n",
				i + 1, changedTotal, cornerCount, edgeCount, centerCount,
				edgeLRCount, exactCenterCount, rotatedCount);
			(void) printf("%d in %d moves!\n", changedTotal, i + 1);
			break;
		}
		if (changedTotal < maxChangedLog)
			(void) printf("%d\n", changedTotal);
	}
	flushMoves(w, &log, False);
	clearPieces(w);
}

/* This procedure coordinates the search process. */
void
findSomeMoves(OctWidget w)
{
	FILE *fp;
	char * fileName = (char *) "oct.txt";

	if (w->oct.size == 2)
		fileName = (char *) "oct2.txt";
	else if (w->oct.size == 3)
		fileName = (char *) "oct3.txt";
	else if (w->oct.size == 4)
		fileName = (char *) "oct4.txt";
	if ((fp = fopen(fileName, "a")) == NULL) {
		(void) printf("problem opening %s\n", fileName);
		return;
	}
#if !defined( __MSDOS__ ) && !defined( __CYGWIN__ )
	/* This gets rid of the unwanted buffering in UNIX */
	(void) setlinebuf(fp);
#endif
	setPuzzle(w, ACTION_RESET);
	if (findingFlag) {
		fclose(fp);
		return;
	}
#ifdef JMP
	if (!setjmp(find_env))
#endif
	{
		findingFlag = True;
		if (checkSolved(w)) {
			for (;;) {
				if (w->oct.size == 2)
					findMovesPeriod3x2(w, fp);
				else if (w->oct.size == 3)
					findMovesPeriod3x3(w, fp);
				else if (w->oct.size == 4)
					findMovesPeriod4x4(w, fp);
				usleep(1);
			}
		}
	}
#ifdef JMP
	abortFindingFlag = False;
#endif
	fclose(fp);
	findingFlag = False;
	w->oct.cheat = True; /* Assume the worst. */
	setPuzzle(w, ACTION_COMPUTED);
}
