/*	-------------------------------------------------------
	BEAUTIFUL CODE BELOW
	BY MICHIEL VAN DER BLONK
	(C) 2003
	Modifications allowed under GNU public license
	send all modification proposals to
	blonkm@zonnet.nl
	-------------------------------------------------------
*/

// this one speaks for itself.
// Ok then: integer random number 0..nn
function random(nn)
{ return(Math.floor(Math.random()*1000)%nn);
}

// for debug purposes only
function debugAlert(str)
{
	if (str==null)
		WScript.Echo("nothing");
	else
		WScript.Echo(str);
}

// directions: clock and counter clockwise
var CLOCKWISE = 1;
var COUNTERCW = -1;

// move constants (starting at 0 as array index)
var NEGMOVE=2;
var DOUBLEMOVE=1;
var NOMOVE=0;

// operation constants
var basicCodes = new Array('R', 'L', 'U', 'D', 'B', 'F');

var operationCodes = new Array(
										'R', 'L', 'U', 'D', 'B', 'F', 'M', 'E', 'S',
										'r', 'l', 'u', 'd', 'b', 'f', 'm', 'e', 's');
var operatorExp = /[RLUDBFMES]/i;

// quantifier constants
var quantifierCodes = new Array('','2',"'");
var quantifierExp = /['\-23]/;

var NOTFOUND = -1;

function initClasses()
{
	Cube.prototype.scramble = scramble;
	Algorithm.prototype.toString = alg2str;
	Algorithm.prototype.reverse = algReverse;
	Algorithm.prototype.length = length;
	Algorithm.prototype.append = append;
	Algorithm.prototype.mirror = algMirror;
	Operation.prototype.toString = op2str;
	Operation.prototype.reverse = opReverse;
	Operation.prototype.opposite = opposite;
	Operation.prototype.mirror = mirror;
	String.prototype.toAlgorithm = str2alg;
	String.prototype.toOperation = str2op;
}

// classes
function Cube(dimension)
{
	this.dimension = dimension;
//	this.colors = colors;
}

function Operation(code, turns, direction)
{
	this.code = code;
	this.turns = turns;
	this.direction = direction;
}

function Algorithm(operations, cursor)
{
	this.operations = operations;
	if (typeof operations == "String")
		this.operations = operations.toAlgorithm();
	if (!operations)
		this.operations = new Array();
	this.cursor = 0;
}

// Operation methods
function op2str()
{
	var q = quantifierCodes[ this.turns-1 ];
	if (this.direction == COUNTERCW)
		q = quantifierCodes[ NEGMOVE ];
	return this.code + q;
}

// reverse an operator by reversing direction
function opReverse()
{
	if (this.turns < 2)
		this.direction = -this.direction;
	return this;
}

function opposite()
{
	switch (this.code)
	{
		case 'R': this.code = 'L';break;
		case 'L': this.code = 'R';break;
		case 'F': this.code = 'B';break;
		case 'B': this.code = 'F';break;
		case 'U': this.code = 'D';break;
		case 'D': this.code = 'U';break;
	}
	return this;
}

function mirror()
{

	if (this.code=="L" || this.code=="R")
		this.code = this.opposite().code;
	if (this.turns < 2)
		this.direction = -this.direction;
	return this; // just some default
}

// Cube methods
// ===============
function scramble(nCount)
{
	var quantifier;

	opCode = "";

	// create random alg
	alg = new Algorithm();
	cursor = 0;

	// default length = 25
	if (!nCount)
		nCount = 25;

	// append random ops
  while (cursor < nCount)
  {
  	var randomMove;
		var nTurns = 0;
		var sentinel = 0; // to prevent endless loop

		operation = new Operation;
		// find a new op, different from the last one
		do
		{
			lastOpCode = opCode;
			randomMove =  random(basicCodes.length);
			opCode = operationCodes[ randomMove ];
		} while (lastOpCode == opCode); // && sentinel++ < 1000);

		// choose a random number of turns (1 or 2)
		nTurns = random(2) ? 1 : 2;

		// choose a random direction
		direction = random(2) ? COUNTERCW : CLOCKWISE;

		// double negmove is double move, correct it
		if (nTurns == 2 && direction == COUNTERCW)
			direction = CLOCKWISE;

		operation.direction = direction;
		operation.code = opCode;
		operation.turns = nTurns;

		alg.append(operation);

		cursor++;
	}
	return alg;
}


// Algorithm methods
// ===============

/* cursor move functions
	moves an integer pointer
	through the array of operations
*/

function append(op)
{
	this.operations[this.operations.length] = op;
}

function length(metric)
{
	ret = n = 0;
	var oldOp = "";
	switch(metric)
	{
		case "QTM":
			for (n in this.operations)
				ret += this.operations[n].turns;
			break;
		case "STM":
			for (n in this.operations)
			{
				ret++;
				if (oldOp == this.operations[n].opposite())
					ret--;
				oldOp = this.operations[n];
			}
			break;
		case "SQTM":
			for (n in this.operations)
			{
				ret += this.operations[n].turns;
				if (oldOp == this.operations[n].opposite())
					ret--;
				oldOp = this.operations[n];
			}
			break;
		case "HTM":
		default:
			ret = this.operations.length;
	}
	return ret;
}

function moveFirst()
{
	this.cursor = 0;
}

function moveNext()
{
	if (this.cursor < this.operations.length)
		this.cursor++;
}

function movePrevious()
{
	if (this.cursor < this.operations.length)
		this.cursor++;
}

function moveLast()
{
	if (this.operations.length>0)
		this.cursor = this.operations.length - 1;
}

// create the algorithm string
function alg2str()
{
	var op;
	var out = "";
	for (op in this.operations)
		out += this.operations[op] + ' ';
	return out;
}

// Inverse an algorithm by inverting ops
function algReverse()
{
	var op;

	// reversing the array of operations
	this.operations = this.operations.reverse();
	// and then reversing the operations themselves
	for (op in this.operations)
		this.operations[op] = this.operations[op].reverse();
	return this;
}

// Mirror an algorithm by mirroring ops
function algMirror()
{
	var op;

	// mirror the operations themselves
	for (op in this.operations)
		this.operations[op] = this.operations[op].mirror();
	return this;
}

// calc turns from quantifier string
function calcTurns(quantifier)
{
	// if it is a double op, set retval according to quantifier
	if (quantifier == quantifierCodes[DOUBLEMOVE]) 			// check double turn
		return 2;
	return 1;
}

// calc direction from quantifier string
function calcDirection(quantifier)
{
	// if it is a neg op, set direction according to quantifier
	if (quantifier == quantifierCodes[NEGMOVE])
		return COUNTERCW;
	return CLOCKWISE;
}

// String methods
//============

// convert a string to an operation
function str2op()
{
	// defaults
	nTurns = 1;
	quantifier='';
	nCount = 0;
	operation = 'R';

	// skip trash before operator
	var isOperator = false;
	while (nCount < this.length && !isOperator)
	{
		var ch = new String(this.charAt(nCount));
		var isOperator = ch.search(operatorExp) != NOTFOUND;
		// read operation
		if (isOperator)
			operation = this.charAt(nCount);
		nCount++;
	}

	var isTrash = true;
	// skip trash after operation
	// trash is defined as: not an operator, and not a quantifier
	while (	nCount < this.length && isTrash)
	{
		var ch = new String(this.charAt(nCount));
		var isOperator = ch.search(operatorExp) != NOTFOUND;
		var isQuantifier = ch.search(quantifierExp) != NOTFOUND;
		isTrash = !isOperator && !isQuantifier;
		// read quantifier
		if (!isTrash)
			quantifier = ch;
		nCount++;
	}

	nTurns = calcTurns(quantifier);
	direction = calcDirection(quantifier);

	// create and initialise
	objOp = new Operation(operation, nTurns, direction);

	return objOp;
}

// convert a string to a complete algorithm
function str2alg()
{
	var strAlgorithm;	// L2R2B-U- etc.
	var bSingle;		// is a single op like R, L, not R2, R-
	var bquantifier;	// match for a possible quantifier
	var operation;		// L, R, B, ...
	var quantifier;		// -, 2, ...
	var nCount;
	var nOps = 0;
	var objAlg = new Algorithm();
	objAlg.operations = new Array();

	for (nCount=0; nCount < this.length; nCount++)
	{
		// defaults
		bSingle = false;
		nTurns = 1;
		operation='';
		quantifier='';

		// last char
		if (nCount == this.length - 1)
			bSingle = true;

		// alg. is only 1 char
		if (this.length==1)
			bSingle = true;

		operation = this.substr(nCount,1);

		// operation is a quantifier (not an operator)
		bquantifier = operation.search(operatorExp)==NOTFOUND;

		// skip quantifiers & trash
		if (bquantifier)
			continue;

		if (!bSingle)
		{
			// quantifier is next char
			var ch = this.substr(nCount + 1,1);

			// if next char is an operator it is a single op after all
			if (ch.search(operatorExp) != NOTFOUND)
			{
				bSingle = true;
				quantifier = '';
			}
			else
				quantifier = ch;
		}

		// calc quantifiers
		nTurns = calcTurns(quantifier);
		direction = calcDirection(quantifier);

		// create the object
		objAlg.operations[nOps++] = new Operation(operation, nTurns, direction);
	}
	return objAlg;
}

initClasses();
