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

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

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

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


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

static Boolean
checkClose(SkewbWidget w, int *cornerCount, int *centerCount)
{
	int face, position, count, total = 14;
	*centerCount = 0;
	*cornerCount = 0;
	for (face = 0; face < MAX_FACES; face++) {
		for (position = 0; position < 4; position++) {
			*cornerCount += checkIfEqual(w, face, 0, position);
		}
		*centerCount += checkIfEqual(w, face, 0, 4);
	}
	*cornerCount /= 3;
	count = total - *cornerCount - *centerCount;
	/*(void) printf("count = %d %d %d\n",
		count, *cornerCount, *centerCount);*/
	return count;
}

static void
findMoves(SkewbWidget w, FILE *fp)
{
	int maxChanged = 6, maxChangedLog = 8, maxMovesCheck = 18; /*change to suit */
	int i, face, position, dir, changedTotal = 14;
	int lastMoveFace = -1, lastMoveDir = -1;
	int cornerCount = 0, centerCount = 0;
	SkewbStack 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);
#endif
			face = NRAND(3);
			dir = NRAND(2);
			position = (NRAND(2)) ? ((dir + 1) % 4) :
				((dir + 3) % 4);
		} while (lastMoveFace == face && lastMoveDir == dir);
		setMove(&log, dir, False, face, position);
		rotateEdge(w, face, position, dir);
		if (lastMoveDir >= 0)
			changedTotal = checkClose(w,
				&cornerCount, &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 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);
}

/* This procedure coordinates the search process. */
void
findSomeMoves(SkewbWidget w)
{
	FILE *fp;
	char * fileName = (char *) "skewb.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)) {
			for (;;) {
				findMoves(w, fp);
				usleep(1);
			}
		}
	}
#ifdef JMP
	abortFindingFlag = False;
#endif
	fclose(fp);
	findingFlag = False;
	w->skewb.cheat = True; /* Assume the worst. */
	setPuzzle(w, ACTION_COMPUTED);
}
