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

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

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

static void
movePuzzlePiece(PyraminxWidget 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
rotateEdge(PyraminxWidget w, int face, int position, int dir)
{
	movePuzzlePiece(w, face, position, dir, PERIOD2, FALSE);
}

static void
rotateFace(PyraminxWidget w, int face, int position, int dir)
{
	movePuzzlePiece(w, face, position, dir, PERIOD3, FALSE);
}

static int
checkIfEqual(PyraminxWidget w, int face, int corner1, int corner2) {
	return (w->pyraminx.facetLoc[face][corner1].face ==
		w->pyraminx.facetLoc[face][corner2].face
#if 0
		&&
		w->pyraminx.facetLoc[face][corner1].rotation ==
		w->pyraminx.facetLoc[face][corner2].rotation
#endif
		) ? 1: 0;
}

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

static void
findMovesPeriod2x3(PyraminxWidget w, FILE *fp)
{
	int maxChanged = 3, maxChangedLog = 3, maxMovesCheck = 50; /*change to suit */
	int i, face, position, dir, changedTotal = 22;
	int temp, lastMoveDir = -1;
	int cornerCount = 0, edgeCount = 0, centerCount = 0, specialCount = 0;
	PyraminxStack log = {NULL, NULL, NULL, 0};

	newMoves(&log);
	for (i = 0; i < maxMovesCheck; i++) {
		do {
			face = NRAND(MAX_FACES);
			temp = NRAND(3);
			dir = (temp == 1) ? TR : (temp == 0) ? TOP : RIGHT;
			temp = NRAND(3);
			position = (temp == 0) ? 2 : (temp == 1) ? 5 : 7;
		} while (lastMoveDir == dir);
		setMove(&log, dir, 2, False, face, position);
		rotateEdge(w, face, position, dir);
		centerCount = -1; /* do not care about centers as will solve later */
		if (lastMoveDir >= 0)
			changedTotal = checkClosePeriod2x3(w,
				&cornerCount, &edgeCount, &centerCount, &specialCount);
		lastMoveDir = dir;
		if (changedTotal == 0) {
			/*(void) printf("solved\n");*/
			break;
		} else if (changedTotal <= maxChanged) {
			/* weeding out moves already found */
			if (centerCount < 12 && centerCount != 10 && i >= 7) {
				(void) printf("center %d, moves %d\n", 12 - centerCount, i + 1);
				break;
			}
			if (edgeCount < 6 && edgeCount == 4 && i >= 5 && specialCount > 2) {
				(void) printf("edge %d, moves %d\n", 6 - edgeCount, i + 1);
				break;
			}
			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 Boolean
checkClosePeriod3x4(PyraminxWidget w, int *cornerCount, int *edgeCount, int *centerCount, int *edgeLRCount, int *exactCenterCount)
{
	int face, position, count, total = 30;
	*cornerCount = 0;
	*edgeCount = 0;
	*centerCount = 0;
	*edgeLRCount = 0;
	*exactCenterCount = 0;
	for (face = 0; face < MAX_FACES; face++) {
		for (position = 0; position < 16; position++) {
			switch (position) {
			case 0:
			case 9:
			case 15:
				*cornerCount += checkIfEqual(w, face, 0, position);
				break;
			case 5:
			case 7:
			case 12:
				*edgeCount += checkIfEqual(w, face, 0, position);
				break;
			case 2:
			case 10:
			case 14:
				*centerCount += checkIfEqual(w, face, 0, position);
				break;
			case 1:
			case 3:
			case 4:
			case 8:
			case 11:
			case 13:
				*edgeLRCount += checkIfEqual(w, face, 0, position);
				break;
			case 6:
				*exactCenterCount += checkIfEqual(w, face, 0, position);
				break;
			}
		}
	}
	*cornerCount /= 3;
	*edgeCount /= 2;
	*centerCount /= 3;
	*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);*/
	return count;
}

static void
findMovesPeriod3x4(PyraminxWidget w, FILE *fp)
{
	int maxChanged = 4, maxChangedLog = 5, maxMovesCheck = 23; /*change to suit */
	int i, face, position, dir, changedTotal = 30;
	int lastMoveFace = -1, lastMoveDir = -1;
	int cornerCount = 0, edgeCount = 0, centerCount = 0,
		edgeLRCount = 0, exactCenterCount = 0;
	PyraminxStack log = {NULL, NULL, NULL, 0};

	newMoves(&log);
	for (i = 0; i < maxMovesCheck; i++) {
		do {
#if 1
			face = NRAND(MAX_FACES);
			if (face != 1)
				face = 0;
			if (face == 1)
				dir = (NRAND(2) == 1) ? TR : BL;
			else
				dir = NRAND(6);
#endif
#if 0
			int temp = NRAND(4);
			face = 0;
			if (temp >= 2)
				dir = (temp == 2) ? TOP : BOTTOM;
			else
				dir = (temp == 2) ? LEFT : RIGHT;
#endif
#if 0
			face = 0;
			dir = NRAND(6);
#endif
			position = 6;
		} while ((face == 1 && lastMoveFace == face) ||
			(face == 0 && lastMoveFace == 0 && (lastMoveDir % 3) == (dir % 3)));
		setMove(&log, dir, 3, False, face, position);
		rotateFace(w, face, position, dir);
		if (lastMoveDir >= 0)
			changedTotal = checkClosePeriod3x4(w,
				&cornerCount, &edgeCount, &centerCount,
				&edgeLRCount, &exactCenterCount);
		lastMoveFace = face;
		lastMoveDir = dir;
		if (changedTotal == 0) {
			/*(void) printf("solved\n");*/
			break;
		} else if (changedTotal <= maxChanged) {
			if (exactCenterCount > 3)
				continue;
			printMoves(fp, &log);
			(void) fprintf(fp,
				"moves: %d, changed: total=%d corner=%d edge=%d center=%d edgeLR=%d exactCenter=%d\n",
				i + 1, changedTotal, cornerCount, edgeCount, centerCount,
				edgeLRCount, exactCenterCount);
			(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(PyraminxWidget w)
{
	FILE *fp;
	char * fileName = (char *) "pyraminx.txt";

	if (w->pyraminx.size == 3)
		fileName = (char *) "pyraminx3.txt";
	else if (w->pyraminx.size == 4)
		fileName = (char *) "pyraminx4.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->pyraminx.size == 3)
					findMovesPeriod2x3(w, fp);
				if (w->pyraminx.size == 4)
					findMovesPeriod3x4(w, fp);
				usleep(1);
			}
		}
	}
#ifdef JMP
	abortFindingFlag = False;
#endif
	fclose(fp);
	findingFlag = False;
	w->pyraminx.cheat = True; /* Assume the worst. */
	setPuzzle(w, ACTION_COMPUTED);
}
