/*-
# X-BASED BARREL(tm)
#
#  BarrelS.c
#
###
#
#  Taken from James G. Nourse's The Simple Solutions to Cubic Puzzles.
#  Break ability taken from the X puzzle by Don Bennett, HP Labs
#
#  Copyright (c) 2003 - 2005    David Albert Bagley, bagleyd@tux.org
#
#                   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 "playable",
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
*/

#ifndef WINVER
#define JMP
#ifdef JMP
#include <setjmp.h> /* longjmp ... interrupt */
#endif
#endif
#include "BarrelP.h"

#define LeftAway(w,r) MoveTilePiece(w,TOP,(r)?w->barrel.tiles-1:0,False) /* push top away */
#define LeftAway_2(w,r) MoveTilePiece(w,TOP,(r)?w->barrel.tiles-1:0,False);\
		MoveTilePiece(w,TOP,(r)?w->barrel.tiles-1:0,False)
#define LeftTowards(w,r) MoveTilePiece(w,BOTTOM,(r)?w->barrel.tiles-1:0,False)
#define LeftTowards_2(w,r) MoveTilePiece(w,BOTTOM,(r)?w->barrel.tiles-1:0,False);\
		MoveTilePiece(w,BOTTOM,(r)?w->barrel.tiles-1:0,False)
#define RightAway(w,r) MoveTilePiece(w,TOP,(r)?0:w->barrel.tiles-1,False)
#define RightAway_2(w,r) MoveTilePiece(w,TOP,(r)?0:w->barrel.tiles-1,False);\
		MoveTilePiece(w,TOP,(r)?0:w->barrel.tiles-1,False)
#define RightTowards(w,r) MoveTilePiece(w,BOTTOM,(r)?0:w->barrel.tiles-1,False)
#define RightTowards_2(w,r) MoveTilePiece(w,BOTTOM,(r)?0:w->barrel.tiles-1,False);\
		MoveTilePiece(w,BOTTOM,(r)?0:w->barrel.tiles-1,False)

#define LeftSlide(w,r) if(r) MoveTilePiece(w,RIGHT,1,False); \
else MoveTilePiece(w,LEFT,-1,False)

#define RightSlide(w,r) if(r) MoveTilePiece(w,LEFT,-1,False); \
else	MoveTilePiece(w,RIGHT,1,False)

static Boolean SolvingFlag = False;
/* TODO Interruptability for Windows */
#ifdef JMP
static Boolean AbortSolvingFlag = False;
static jmp_buf solve_env;

static void
AbortSolving(void)
{
	if (SolvingFlag)
		AbortSolvingFlag = True;
}

static void
ProcessButton(void /*XButtonEvent *event*/)
{
	AbortSolving();
}

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

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

	while(XPending(XtDisplay(w))) {
		GetNextEvent(w, &event);
		ProcessEvent(&event);
	}
}
#endif

static void
MoveTilePiece(BarrelWidget w,
        const int direction, const int tile,
        const Boolean all)
{
#ifdef JMP
	if (XPending(XtDisplay(w)))
                ProcessEvents(w);
        if (SolvingFlag && AbortSolvingFlag)
                longjmp(solve_env, 1);
#endif
        MoveBarrelDelay(w, direction, tile, all);
}

static int
findPiece(BarrelWidget w, int match)
{
	int tile, face = 0, pos = -1;

	for (tile = 0; tile < w->barrel.tiles; tile++) {
		for (face = 0; face < w->barrel.faces; face++) {
			if (w->barrel.tileOfPosition[face * w->barrel.faces + tile] - 1 == match) {
				pos = face * w->barrel.faces + tile;
				break;
			}
		}
		if (pos != -1)
			break;
	}
	return pos;
}

/* Page 51 */
static void
L(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftTowards(w, reverse);
	LeftSlide(w, reverse);
	LeftAway(w, reverse);
	RightSlide(w, reverse);
}

static void
R(BarrelWidget w)
{
	const Boolean reverse = False;

	RightTowards(w, reverse);
	LeftSlide(w, reverse);
	RightAway(w, reverse);
	RightSlide(w, reverse);
}

static void
L_1(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftSlide(w, reverse);
	LeftTowards(w, reverse);
	RightSlide(w, reverse);
	LeftAway(w, reverse);
}

static void
R_1(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftSlide(w, reverse);
	RightTowards(w, reverse);
	RightSlide(w, reverse);
	RightAway(w, reverse);
}

/* Page 27 */
static void
HS1(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftSlide(w, reverse);
	LeftAway_2(w, reverse);
	RightSlide(w, reverse);
	RightAway_2(w, reverse);
	LeftSlide(w, reverse);
	LeftTowards_2(w, reverse);
	RightSlide(w, reverse);
	RightTowards_2(w, reverse);
}

/* Page 28 */
static void
HS2(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftSlide(w, reverse);
	RightAway(w, reverse);
	RightSlide(w, reverse);
	RightTowards(w, reverse);
}

static void
HS3(BarrelWidget w)
{
	HS2(w);
	HS2(w);
}

static void
HS4(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftSlide(w, reverse);
	RightTowards(w, reverse);
	RightSlide(w, reverse);
	RightAway(w, reverse);
	LeftSlide(w, reverse);
	RightTowards(w, reverse);
	RightSlide(w, reverse);
	RightTowards(w, reverse);
}

static void
HS5(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftSlide(w, reverse);
	RightAway(w, reverse);
	RightSlide(w, reverse);
	RightTowards(w, reverse);
	LeftSlide(w, reverse);
	RightAway(w, reverse);
	RightSlide(w, reverse);
	RightAway(w, reverse);
}

/* Page 33 */
static void
R2B(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftSlide(w, reverse);
	LeftAway_2(w, reverse);
	RightSlide(w, reverse);
	RightAway_2(w, reverse);
	LeftSlide(w, reverse);
	RightTowards_2(w, reverse);
	RightSlide(w, reverse);
}

/* Page 29 */
static void
R1B(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftAway(w, reverse);
	RightAway(w, reverse);
	R2B(w);
}

/* Page 31 */
static void
R1E(BarrelWidget w)
{
	const Boolean reverse = False;

	RightAway(w, reverse);
	LeftSlide(w, reverse);
	LeftAway_2(w, reverse);
	RightSlide(w, reverse);
	RightAway_2(w, reverse);
	LeftSlide(w, reverse);
	LeftTowards_2(w, reverse);
	RightSlide(w, reverse);
}

/* Page 30 */
static void
R1C(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftTowards_2(w, reverse);
	R1E(w);
}


/* Page 31 */
static void
R1F(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftAway_2(w, reverse);
	R1E(w);
	LeftTowards_2(w, reverse);
	RightTowards_2(w, reverse);
}

/* Page 34 */
static void
R3C30(BarrelWidget w)
{
	const Boolean reverse = False;

	LeftAway_2(w, reverse);
	RightTowards(w, reverse);
	LeftSlide(w, reverse);
	RightAway(w, reverse);
	RightSlide(w, reverse);
	RightTowards_2(w, reverse);
	LeftSlide(w, reverse);
	RightTowards_2(w, reverse);
	RightSlide(w, reverse);
	RightAway_2(w, reverse);
	LeftSlide(w, reverse);
	RightTowards(w, reverse);
	RightSlide(w, reverse);
	RightTowards_2(w, reverse);
	LeftSlide(w, reverse);
	RightTowards_2(w, reverse);
	RightSlide(w, reverse);
	RightTowards_2(w, reverse);
	LeftTowards_2(w, reverse);
}

static void
SolveWhite(BarrelWidget w)
{
	Boolean reverse = False;
	int current, loc, pos;

	current = (w->barrel.faces - 1) * w->barrel.tiles;
	if (w->barrel.tileOfPosition[current] - 1 != current) {
		loc = findPiece(w, current);
		if (loc % w->barrel.tiles) { /* not on left side */
			if (w->barrel.spacePosition[0] / w->barrel.tiles !=
					loc / w->barrel.tiles) { /* not on same face */
				if (w->barrel.spacePosition[0] % w->barrel.tiles > 0) {
					RightSlide(w, reverse);
				}
				pos = ((loc / w->barrel.tiles -
					w->barrel.spacePosition[0] / w->barrel.tiles) +
					w->barrel.faces) % w->barrel.faces;
				if (pos == 1) {
					LeftTowards(w, reverse);
				} else if (pos == 2) {
					LeftAway_2(w, reverse);
				} else if (pos == 3) {
					LeftAway(w, reverse);
				}
			}
			if (w->barrel.spacePosition[0] % w->barrel.tiles < w->barrel.tiles - 1) {
				if (loc > w->barrel.spacePosition[0])
					loc--;
				LeftSlide(w, reverse);
			}
		}
		while (loc % w->barrel.tiles) {
			RightAway(w, reverse);
			RightSlide(w, reverse);
			LeftTowards(w, reverse);
			LeftSlide(w, reverse);
			loc--;
		}
		pos = (loc / w->barrel.tiles) % w->barrel.faces;
		if (pos == 0) {
			LeftAway(w, reverse);
		} else if (pos == 1) {
			LeftAway_2(w, reverse);
		} else if (pos == 2) {
			LeftTowards(w, reverse);
		}
	}
	if (w->barrel.spacePosition[0] % w->barrel.tiles < w->barrel.tiles - 1) {
		LeftSlide(w, reverse);
	}
	pos = (w->barrel.spacePosition[0] / w->barrel.tiles) % w->barrel.faces;
	if (pos == 0) {
		RightAway(w, reverse);
	} else if (pos == 1) {
		RightAway_2(w, reverse);
	} else if (pos == 2) {
		RightTowards(w, reverse);
	}

	current++;
	if (w->barrel.tileOfPosition[current] - 1 != current) {
		loc = findPiece(w, current);
		if (w->barrel.faces - 1 !=
				loc / w->barrel.tiles) { /* not same face */
			if ((loc + 1) % w->barrel.tiles) { /* not right side */
				int i;

				pos = loc / w->barrel.tiles;
				if (pos == 0) {
			  		RightTowards(w, reverse);
				} else if (pos == 1) {
					RightAway_2(w, reverse);
				} else if (pos == 2) {
			  		RightAway(w, reverse);
				}
				for (i = 0; i < w->barrel.tiles -  1 - (loc % w->barrel.tiles); i++) {
					RightSlide(w, reverse);
					LeftAway(w, reverse);
					LeftSlide(w, reverse);
					RightTowards(w, reverse);
					LeftTowards(w, reverse);
				}
				loc = (w->barrel.spacePosition[0] + w->barrel.tiles) % w->barrel.tileFaces;
			}
			pos = w->barrel.spacePosition[0] / w->barrel.tiles;
			if (pos == 0) {
		  		RightAway(w, reverse);
				loc = (loc + 3 * w->barrel.tiles) % w->barrel.tileFaces;
			} else if (pos == 1) {
				RightAway_2(w, reverse);
				loc = (loc + 2 * w->barrel.tiles) % w->barrel.tileFaces;
			} else if (pos == 2) {
		  		RightTowards(w, reverse);
				loc = (loc + w->barrel.tiles) % w->barrel.tileFaces;
			}
			RightSlide(w, reverse);
			pos = loc / w->barrel.tiles;
			if (pos == 0) {
		  		RightAway(w, reverse);
			} else if (pos == 1) {
				RightAway_2(w, reverse);
			} else if (pos == 2) {
		  		RightTowards(w, reverse);
			}
			LeftSlide(w, reverse);
		}
		RightAway(w, reverse);
		RightSlide(w, reverse);
		LeftTowards(w, reverse);
		LeftSlide(w, reverse);
		LeftAway(w, reverse);
	}

	current++;
	if (w->barrel.tileOfPosition[current] - 1 != current) {
		loc = findPiece(w, current);
		if ((loc + 1) % w->barrel.tiles) { /* not right side */
			int i;

			pos = loc / w->barrel.tiles;
			if (pos == 0) {
		  		RightTowards(w, reverse);
			} else if (pos == 1) {
				RightAway_2(w, reverse);
			} else if (pos == 2) {
		  		RightAway(w, reverse);
			}
			if (pos == 0) {
			for (i = 0; i < w->barrel.tiles -  1 - (loc % w->barrel.tiles); i++) {
				RightSlide(w, reverse);
				LeftTowards(w, reverse);
				LeftSlide(w, reverse);
				RightAway(w, reverse);
				LeftAway(w, reverse);
			}
			loc = (w->barrel.spacePosition[0] + 3 * w->barrel.tiles) % w->barrel.tileFaces;
			} else {
			for (i = 0; i < w->barrel.tiles -  1 - (loc % w->barrel.tiles); i++) {
				RightSlide(w, reverse);
				LeftAway(w, reverse);
				LeftSlide(w, reverse);
				RightTowards(w, reverse);
				LeftTowards(w, reverse);
			}
			loc = (w->barrel.spacePosition[0] + w->barrel.tiles) % w->barrel.tileFaces;
			}
		}
		pos = w->barrel.spacePosition[0] / w->barrel.tiles;
		if (pos == 0) {
	  		RightAway(w, reverse);
			loc = (loc + 3 * w->barrel.tiles) % w->barrel.tileFaces;
		} else if (pos == 1) {
			RightAway_2(w, reverse);
			loc = (loc + 2 * w->barrel.tiles) % w->barrel.tileFaces;
		} else if (pos == 2) {
	  		RightTowards(w, reverse);
			loc = (loc + w->barrel.tiles) % w->barrel.tileFaces;
		}
		RightSlide(w, reverse);
		pos = loc / w->barrel.tiles;
		if (pos == 0) {
	  		RightAway(w, reverse);
		} else if (pos == 1) {
			RightAway_2(w, reverse);
		} else if (pos == 2) {
	  		RightTowards(w, reverse);
		}
		LeftSlide(w, reverse);
	}
}

/* Page 6 - 14 */
static Boolean
SolveSpecialCases(BarrelWidget w, const int pos1, const int pos2)
{
	Boolean reverse = False;
	int x_1 = pos1 % w->barrel.tiles;
	int y_1 = pos1 / w->barrel.tiles;
	int x_2 = pos2 % w->barrel.tiles;
	int y_2 = pos2 / w->barrel.tiles;

#if 0
	printf("SolveSpecialCases pos %d, %d\n", pos1, pos2);
#endif
	if (x_1 >= w->barrel.tiles / 2) {
		x_1 = w->barrel.tiles - 1 - x_1;
		x_2 = w->barrel.tiles - 1 - x_2;
		reverse = !reverse;
	}
	if (x_1 > x_2 || (x_1 == x_2 && y_1 > y_2)) {
		int temp;

		temp = x_1;
		x_1 = x_2;
		x_2 = temp;
		temp = y_1;
		y_1 = y_2;
		y_2 = temp;
	}
#if 0
	if (x_1 == 0) {
		if (y_1 == 0) {
			if (x_2 == 0) {
				if (y_2 == 1) {
					Swap30_31(w, !reverse); /* 00_01 */
				} else if (y_2 == 2) {
					Swap32_30(w, !reverse); /* 00_02 */
				} else {
					return False;
				}
			} else if (x_2 == 1) {
				if (y_2 == 0) {
					Swap20_30(w, !reverse); /* 00_10 */
				} else if (y_2 == 1) {
					Swap30_21(w, !reverse); /* 00_11 */
				} else if (y_2 == 2) {
					Swap30_22(w, !reverse); /* 00_12 */
				} else {
					return False;
				}
			} else if (x_2 == 2) {
				if (y_2 == 1) {
					Swap00_21(w, reverse);
				} else {
					return False;
				}
			} else if (x_2 == 3) {
				if (y_2 == 0) {
					Swap00_30(w, reverse);
				} else {
					return False;
				}
			} else {
				return False;
			}
		} else if (y_1 == 1) {
			if (x_2 == 0) {
				if (y_2 == 2) {
					Swap32_31(w, !reverse); /* 01_02 */
				} else {
					return False;
				}
			} else if (x_2 == 1) {
				if (y_2 == 0) {
					Swap31_20(w, !reverse); /* 01_10 */
				} else if (y_2 == 1) {
					Swap21_31(w, !reverse); /* 01_11 */
				} else if (y_2 == 2) {
					Swap31_22(w, !reverse); /* 01_12 */
				} else {
					return False;
				}
			} else if (x_2 == 2) {
				if (y_2 == 1) {
					Swap01_21(w, reverse);
				} else {
					return False;
				}
			} else if (x_2 == 3) {
				if (y_2 == 1) {
					Swap01_31(w, reverse);
				} else {
					return False;
				}
			} else {
				return False;
			}
		} else if (y_1 == 2) {
			if (x_2 == 1) {
				if (y_2 == 0) {
					Swap32_20(w, !reverse); /* 02_10 */
				} else if (y_2 == 1) {
					Swap32_21(w, !reverse); /* 02_11 */
				} else if (y_2 == 2) {
					Swap22_32(w, !reverse); /* 02_12 */
				} else {
					return False;
				}
			} else if (x_2 == 2) {
				if (y_2 == 2) {
					Swap02_22(w, reverse);
				} else {
					return False;
				}
			} else if (x_2 == 3) {
				if (y_2 == 2) {
					Swap02_32(w, reverse);
				} else {
					return False;
				}
			} else {
				return False;
			}
		} else {
			return False;
		}
	} else if (x_1 == 1) {
		if (y_1 == 0) {
			if (x_2 == 1) {
				if (y_2 == 1) {
					Swap21_20(w, !reverse); /* 10_11 */
				} else {
					return False;
				}
			} else {
				return False;
			}
		} else if (y_1 == 1) {
			if (x_2 == 1) {
				if (y_2 == 2) {
					Swap22_21(w, !reverse); /* 11_12 */
				} else {
					return False;
				}
			} else {
				return False;
			}
		} else {
			return False;
		}
	} else {
		return False;
	}
#endif
	return True;
}

static void
SolveMiddleFromEnd(BarrelWidget w, int tile, int otherTile)
{
	int face, otherFace, pos, otherPos;
	Boolean found = True;

	while (found) {
		found = False;
		for (face = 0; face < w->barrel.faces - 1; face++) {
			pos = face * w->barrel.tiles + tile;
			for (otherFace = 0; otherFace < w->barrel.faces - 1;
					otherFace++) {
				otherPos = otherFace * w->barrel.tiles + otherTile;
				if (w->barrel.tileOfPosition[pos] - 1 == otherPos) {
					found = SolveSpecialCases(w, pos, otherPos);
				}
			}
		}
	}
}

static void
SolveEnd(BarrelWidget w, int tile)
{
	int face, otherFace, pos, otherPos;
	Boolean found = True;

	while (found) {
		found = False;
		for (face = 0; face < w->barrel.faces - 1; face++) {
			pos = face * w->barrel.tiles + tile;
			for (otherFace = 0; otherFace < w->barrel.faces - 1;
					otherFace++) {
				otherPos = otherFace * w->barrel.tiles + tile;
				if (pos != otherPos) {
					if (w->barrel.tileOfPosition[pos] - 1 == otherPos) {
						found = SolveSpecialCases(w, pos, otherPos);
					}
				}
			}
		}
	}
}

static void
SolveEnds(BarrelWidget w, int tile, int otherTile)
{
	int face, pos, otherPos;
	Boolean found = True;

	while (found) {
		found = False;
		for (face = 0; face < w->barrel.faces - 1; face++) {
			pos = face * w->barrel.tiles + tile;
			otherPos = face * w->barrel.tiles + otherTile;
			if (w->barrel.tileOfPosition[pos] - 1 == otherPos) {
				found = SolveSpecialCases(w, pos, otherPos);
			}
		}
	}
}

static void
MatchLeftRight(BarrelWidget w, int tile, int otherTile)
{
	int face, otherFace, pos, otherPos;
	Boolean found = True;

	while (found) {
		found = False;
		for (face = 0; face < w->barrel.faces - 1; face++) {
			pos = face * w->barrel.tiles + tile;
			for (otherFace = 0; otherFace < w->barrel.faces - 1;
					otherFace++) {
				otherPos = otherFace * w->barrel.tiles + otherTile;
				if (pos != otherPos) {
					if (w->barrel.tileOfPosition[pos] - 1 == otherPos) {
						found = SolveSpecialCases(w, pos, otherPos + 3);
					}
				}
			}
		}
	}
}

static void
ClearOut(BarrelWidget w, int tile, int otherTile, int shift)
{
	int face, otherFace, pos, otherPos;

	for (face = 0; face < w->barrel.faces - 1; face++) {
		pos = face * w->barrel.tiles + tile;
		for (otherFace = 0; otherFace < w->barrel.faces - 1;
				otherFace++) {
			otherPos = otherFace * w->barrel.tiles + otherTile;
			if (pos != otherPos) {
				if (w->barrel.tileOfPosition[pos] - 1 == otherPos) {
					(void) SolveSpecialCases(w, pos, pos + shift);
				}
			}
		}
	}
}

static void
SolveLeftMiddle(BarrelWidget w)
{
	SolveMiddleFromEnd(w, 0, 1);
	ClearOut(w, 1, 1, -1);
	SolveMiddleFromEnd(w, 0, 1);
	ClearOut(w, 3, 1, -3);
	SolveMiddleFromEnd(w, 0, 1);
	ClearOut(w, 2, 1, 1);
	ClearOut(w, 3, 1, -3);
	SolveMiddleFromEnd(w, 0, 1);
}

static void
SolveLeft(BarrelWidget w)
{
	SolveEnd(w, 0);
	MatchLeftRight(w, 3, 0);
	SolveEnds(w, 3, 0);
	ClearOut(w, 2, 0, 1);
	MatchLeftRight(w, 3, 0);
	SolveEnds(w, 3, 0);
}

static void
SolveRightMiddle(BarrelWidget w)
{
	SolveMiddleFromEnd(w, 3, 2);
	ClearOut(w, 2, 2, 1);
	SolveMiddleFromEnd(w, 3, 2);
}

static void
SolveRight(BarrelWidget w)
{
	SolveEnd(w, 3);
}

/* This procedure coordinates the solution process. */
void
SolveSomeTiles(BarrelWidget w)
{
        SetBarrel(w, BARREL_RESET);
#ifdef JMP
	if (!setjmp(solve_env))
#endif
	{
		SolvingFlag = True;
		if (!CheckSolved(w)) {
			SolveWhite(w);
			SolveLeftMiddle(w);
			SolveLeft(w);
			SolveRightMiddle(w);
			SolveRight(w);
		}
	}
#ifdef JMP
	else {
		DrawAllTiles(w);
	}
	AbortSolvingFlag = False;
#endif
	SolvingFlag = False;
	w->barrel.cheat = True; /* Assume the worst. */
        SetBarrel(w, BARREL_COMPUTED);
}
