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

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

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

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

static void
rotateEdge(RubikWidget w, int face, int position, int dir)
{
	movePuzzlePiece(w, face, position, dir, FALSE);
}


static int
checkIfEqual(RubikWidget w, int face, int x1, int y1, int x2, int y2) {
	return (w->rubik.cubeLoc[face][x1 + y1 * w->rubik.sizeX].face ==
		w->rubik.cubeLoc[face][x2 + y2 * w->rubik.sizeX].face &&
		w->rubik.cubeLoc[face][x1 + y1 * w->rubik.sizeX].rotation ==
		w->rubik.cubeLoc[face][x1 + y2 * w->rubik.sizeX].rotation) ? 1: 0;
}

static int face2ClockFace[MAX_FACES] = {1, 0, 0, 0, 1, 0};
static int face2ClockPosition[MAX_FACES] = {0, 0, 3, 3, 3, 0};
static int face2ClockDir[MAX_FACES] = {LEFT, BOTTOM, RIGHT, TOP, TOP, LEFT};

static Boolean
checkClose2(RubikWidget w, int *cornerCount)
{
	int face, count, total = 8;
	*cornerCount = 0;
	for (face = 0; face < MAX_FACES; face++) {
		*cornerCount += checkIfEqual(w, face, 0, 0, 1, 0);
		*cornerCount += checkIfEqual(w, face, 0, 0, 0, 1);
		*cornerCount += checkIfEqual(w, face, 0, 0, 1, 1);
	}
	*cornerCount /= 3;
	count = total - *cornerCount;
	/*(void) printf("count = %d %d %d\n",
		count, *cornerCount);*/
	return count;
}

static void
findMoves2(RubikWidget w, FILE *fp)
{
	int maxChanged = 2, maxChangedLog = 4, maxMovesCheck = 18; /*change to suit */
	int i, face, position, dir, changedTotal = 8;
	int clockFace, clockDir;
	int lastMoveFace = -1, lastMoveDir = -1;
	int cornerCount = 0;
	RubikStack log = {NULL, NULL, NULL, 0};

	newMoves(&log);
	for (i = 0; i < maxMovesCheck; i++) {
		do {
#if 0
			face = NRAND(4);
			face = NRAND(3);
			face = NRAND(2);
			position = (NRAND(2)) ? ((dir + 1) % 4) :
				((dir + 3) % 4);
#endif
			face = NRAND(3);
			dir = (NRAND(2)) ? CCW : CW;
		} while (lastMoveFace == face && lastMoveDir == dir);
		clockFace = face2ClockFace[face];
		position = face2ClockPosition[face];
		clockDir = face2ClockDir[face];
		if (dir == CCW)
			clockDir = (clockDir + 2) % 4;
		setMove(&log, clockDir, False, clockFace, position);
		rotateEdge(w, clockFace, position, clockDir);
		if (lastMoveDir >= 0)
			changedTotal = checkClose2(w,
				&cornerCount);
		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\n",
				i + 1, changedTotal, cornerCount);
			(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
checkClose3(RubikWidget w, int *cornerCount, int *edgeCount, int *centerCount)
{
	int face, count, total = 26;
	*centerCount = 0;
	*edgeCount = 0;
	*cornerCount = 0;
	for (face = 0; face < MAX_FACES; face++) {
		*cornerCount += checkIfEqual(w, face, 0, 0, 2, 0);
		*cornerCount += checkIfEqual(w, face, 0, 0, 0, 2);
		*cornerCount += checkIfEqual(w, face, 0, 0, 2, 2);
		*edgeCount += checkIfEqual(w, face, 0, 0, 0, 1);
		*edgeCount += checkIfEqual(w, face, 0, 0, 1, 0);
		*edgeCount += checkIfEqual(w, face, 0, 0, 1, 2);
		*edgeCount += checkIfEqual(w, face, 0, 0, 2, 1);
		*centerCount += checkIfEqual(w, face, 0, 0, 1, 1);
	}
	*cornerCount /= 3;
	*edgeCount /= 2;
	count = total - *cornerCount - *edgeCount - *centerCount;
	/*(void) printf("count = %d %d %d\n",
		count, *cornerCount, *edgeCount, *centerCount);*/
	return count;
}

static void
findMoves3(RubikWidget w, FILE *fp)
{
	int maxChanged = 6, maxChangedLog = 8, maxMovesCheck = 18; /*change to suit */
	int i, face, position, dir, changedTotal = 26;
	int clockFace, clockDir;
	int lastMoveFace = -1, lastMoveDir = -1;
	int cornerCount = 0, edgeCount = 0, centerCount = 0;
	RubikStack log = {NULL, NULL, NULL, 0};

	newMoves(&log);
	for (i = 0; i < maxMovesCheck; i++) {
		do {
#if 0
			face = NRAND(4);
			face = NRAND(3);
			face = NRAND(2);
			position = (NRAND(2)) ? ((dir + 1) % 4) :
				((dir + 3) % 4);
#endif
			face = NRAND(2);
			dir = (NRAND(2)) ? CCW : CW;
		} while (lastMoveFace == face && lastMoveDir == dir);
		clockFace = face2ClockFace[face];
		position = face2ClockPosition[face];
		if (position == 3)
			position = w->rubik.sizeX * w->rubik.sizeX - 1;
		clockDir = face2ClockDir[face];
		if (dir == CCW)
			clockDir = (clockDir + 2) % 4;
		setMove(&log, clockDir, False, clockFace, position);
		rotateEdge(w, clockFace, position, clockDir);
		if (lastMoveDir >= 0)
			changedTotal = checkClose3(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);
}

/* This procedure coordinates the search process. */
void
findSomeMoves(RubikWidget w)
{
	FILE *fp;
	char * fileName = (char *) "rubik.txt";
	if ((fp = fopen(fileName, "a")) == NULL) {
		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, w->rubik.orient)) {
			for (;;) {
				if (w->rubik.sizeX == 2)
					findMoves2(w, fp);
				else
					findMoves3(w, fp);
				usleep(1);
			}
		}
	}
#ifdef JMP
	abortFindingFlag = False;
#endif
	fclose(fp);
	findingFlag = False;
	w->rubik.cheat = True; /* Assume the worst. */
	setPuzzle(w, ACTION_COMPUTED);
}
