function Chr2Num(aChr)
{
	return "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%*".indexOf(aChr);
}

function Num2Chr(aNum)
{
	var chr = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ%*".charAt(aNum);
	return chr;
}

function playAsound(aSound)
{
	document.write('<embed src=' + aSound + ' type="audio/mpeg" autostart=true loop=false  hidden=true>');
}


function Num2Bits(aNum)
{
	var myBits = new Array(6);
	myBits[0] = ((aNum & 1) == 1);
	myBits[1] = ((aNum & 2) == 2);
	myBits[2] = ((aNum & 4) == 4);
	myBits[3] = ((aNum & 8) == 8);
	myBits[4] = ((aNum & 16) == 16);
	myBits[5] = ((aNum & 32) == 32);
	return myBits;
}

function Bits2Num(anArray,start)
{
	var pow2 = 1;
	var num = 0;
	for (var i = start; i < (start + 6); i++)
	{
		if (anArray[i]) num += pow2;
		pow2 *= 2;
	}
	return num;
}


function Num2ChrPair(aNum)
{
	var startNum = aNum;
	if (aNum > 4095) startNum = 4095;
	var lowDigit = startNum % 64;
	var str = Num2Chr(lowDigit);
	startNum -= lowDigit;
	str = Num2Chr(startNum / 64) + str;
	return str;
}


function ChrPair2Num(str,startPos)
{
	var result = (Chr2Num(str.charAt(startPos)) * 64);
	result += Chr2Num(str.charAt(startPos + 1));
	return result;
}


function Jumble(anArray)
{
	newArray = anArray.slice(0,anArray.length);
	return newArray.sort(function(a,b) {return (Math.random() - 0.5);});
}


function RandomUpTo(max)
{
	var dt = new Date();
	var rint = dt.getHours() + dt.getMinutes() + dt.getSeconds() + dt.getMilliseconds();
	var rupto = rint % (max + 1);
	return rupto;
}


function DayOfWeek()
{
	var dt = new Date();
	return dt.getDate();
}


function AllBar(anArray,nth)
{
	var newArr = new Array(anArray.length - 1);
	var j = 0;
	for (var i = 0;i < anArray.length;i++)
		if (i != nth)
		{
			newArr[j] = anArray[i];
			j++;
		};
	return newArr;
}


function FilteredList(aList,toggles)
{
	var newArr = new Array(0);
	for (var i = 0;i < aList.length;i++) if (toggles[i]) newArr[newArr.length] = aList[i];
	return newArr;
}


function AdvanceSequence(ArrayLength)
{
	var undefined;
	var seqs = sequences[ArrayLength - 1];
	if (iSeq == undefined)
		iSeq = RandomUpTo(ArrayLength - 1);
	else
		iSeq = parseInt(iSeq);
	if (seqNo == undefined)
		seqNo = RandomUpTo(seqs.length - 1);
	else
		seqNo = parseInt(seqNo);
	iSeq += 1;
	if (iSeq >= ArrayLength)
	{
		iSeq = 0;
		seqNo = RandomUpTo(seqs.length - 1);
	}
}


function colinise(astring,shft)
{
	newstring = "";
	for (i=0;i<astring.length;i++) {
		code = astring.charCodeAt(i);
		newstring += String.fromCharCode(code+shft);
	}
	return newstring;
}


function RandomTrialFromElements(nelems,allElems,filter)
{
	//
	// Generates a trial with an array of NELEMS elements chosen at random from allElems
	// (except those contra-indicated by filter), a random one being the target
	// 
	var elems = FilteredList(allElems,filter);
	var trial = new Array(4);
	var seqs = sequences[elems.length - 1];
	var i;
	AdvanceSequence(elems.length);
	var seq = seqs[seqNo];
	var target = seq[iSeq];									// a random position in elems
	var distractors = AllBar(seq,iSeq);						// a list of all other positions
	var shownElems = [];
	do
		shownElems = shownElems.concat(distractors);
	while ((shownElems.length + 1) < nelems);				// distractor sequence repeated to get enough
	
	shownElems = Jumble(shownElems);						// ..jumbled (otherwise always same distractors)
	
	shownElems = shownElems.slice(0,nelems - 1); 			// ...then trimmed to the number wanted
	shownElems[shownElems.length] = target;					// target position tacked on the end...
	shownElems = Jumble(shownElems);						// ...and the whole lot jumbled up (randomise target pos)
	var targetPos;
	for (i=0;i < nelems;i++) // find position of target amongst distractors
	{
		if (shownElems[i] == target)
		{
			targetPos = i;
			break;
		}
	}	
	var targ = elems[target]; //the actual target element
	// build trial structure
	trial[0] = targ[0];											// the Sd audio file
	trial[1] = new Array(nelems);								// the array of stills.
	for(i=0; i < nelems; trial[1][i] = elems[shownElems[i]][1],i++);
	trial[2] = [targetPos];	//array of length 1					// target's pos within array
	trial[3] = targ[2];											// target's reinforcer command
	
	return trial;
	
}


function RandomDistractorTrial(nelems,target,distractors)
{
	//
	// Generates a trial with an array of NELEMS elements including TARGET
	// the remainder being chosen from DISTRACTORS.
	//
	var trial = new Array(4);
	var myDistractors = Jumble(distractors);			
	var shownElems = [];
	do
		shownElems = shownElems.concat(myDistractors);	// repeat distractors if necessary
	while ((shownElems.length + 1) < nelems);
	shownElems = Jumble(shownElems);					// ..jumbled (otherwise always same distractors)
	shownElems = shownElems.slice(0,nelems - 1);		// trimmed to required length
	shownElems[shownElems.length] = target;				// target tacked on the end...
	shownElems = Jumble(shownElems);					// ...and the whole lot jumbled up
	trial[0] = target[0];
	trial[1] = new Array(nelems);
	var targetPos;
	for (i=0;i < nelems;i++) // find position of target amongst distractors
	{
		if (shownElems[i] === target) targetPos = i;
		trial[1][i] = shownElems[i][1];
	}	
	trial[2] = [targetPos];
	trial[3] = target[2];
	
	return trial;
	
}


function RandomTrialFromList(TrialList,filter)
{
	var list = FilteredList(TrialList,filter);
	var seqs = sequences[list.length - 1];
	AdvanceSequence(list.length);
	var seq = seqs[seqNo];
	var targ = list[seq[iSeq]];
	
	return targ;
}


function readCurrentLog(modNo,submodNo) // have defaults arg too? (set on submodule)
{
	// get raw log (if any) and set properties of currentLog
	var i,j,k;
	var bits;
	var obchr;
	var rawLog = "";
	var best = 12;
	currentLog = new Object();
	currentLog.bits = new Array(96);
	currentLog.optbits = [true,false,false,false,false,false];
	
	if (logs[modNo] != undefined)
	{
		var nsub = 0;
		for (var subno in logs[modNo]) nsub += 1;
		//alert("nsub = " + nsub);
		if (nsub >= 1) best -= 3;
		if (nsub >= 2) best -= 2;
		if (logs[modNo][submodNo] != undefined) rawLog = logs[modNo][submodNo];
	}
			
			
	if (rawLog == "")
	{
		for (i=0 ;i < 96;i++) currentLog.bits[i] = true;
		currentLog.arraySize = arraySize;	// should get from submodule defaults (backstop global defaults)
		currentLog.nRetries = nRetries;	// should get from submodule defaults (backstop global defaults)
		currentLog.showPictures = true;
		currentLog.showWords = false;
		currentLog.nTrials = 0;
		currentLog.n1stTimeHits = 0;
		currentLog.nMisses = 0;
		currentLog.nPrompted = 0;
		currentLog.best = best;
		currentLog.day = DayOfWeek();
	}
	else
	{
		i = 0;
		for (j=16;j < 32;j++)
		{
			bits = Num2Bits(Chr2Num(rawLog.charAt(j)));
			for (k = 0; k < 6 ;k++)
			{
				currentLog.bits[i] = bits[k];
				i++;
			}
		}
		currentLog.arraySize = Chr2Num(rawLog.charAt(2));
		currentLog.nRetries = Chr2Num(rawLog.charAt(3));
		obchr = rawLog.charAt(4);
		if (obchr != " ") currentLog.optbits = Num2Bits(Chr2Num(obchr));
		currentLog.showPictures = currentLog.optbits[0];
		currentLog.showWords = currentLog.optbits[1];
		// optbits 2-5 are currently spare.
		// rawlog.charAt(5) is currently spare.
		currentLog.best = Chr2Num(rawLog.charAt(6));
		currentLog.day = Chr2Num(rawLog.charAt(7));
		//if (currentLog.day != DayOfWeek())
		//{
		//	currentLog.best = 8;
		//	currentLog.day = DayOfWeek();
		//}
		currentLog.nTrials = ChrPair2Num(rawLog,8);
		currentLog.n1stTimeHits = ChrPair2Num(rawLog,10);
		currentLog.nMisses = ChrPair2Num(rawLog,12);
		currentLog.nPrompted = ChrPair2Num(rawLog,14);
	}
}


function writeCurrentLog(modNo,submodNo)
{
	// construct raw log from currentLog and hang in logs array
	var i,j;
	var rawLog = "";
	rawLog += Num2Chr(modNo);
	rawLog += Num2Chr(submodNo);
	rawLog += Num2Chr(currentLog.arraySize);
	rawLog += Num2Chr(currentLog.nRetries);
	currentLog.optbits[0] = currentLog.showPictures;
	currentLog.optbits[1] = currentLog.showWords;
	rawLog += Num2Chr(Bits2Num(currentLog.optbits,0));
	rawLog += " "; // spare "knob"
	rawLog += Num2Chr(currentLog.best);
	rawLog += Num2Chr(currentLog.day);
	rawLog += Num2ChrPair(currentLog.nTrials);
	rawLog += Num2ChrPair(currentLog.n1stTimeHits);
	rawLog += Num2ChrPair(currentLog.nMisses);
	rawLog += Num2ChrPair(currentLog.nPrompted);
	for (i=0; i < 16 ;i++) rawLog += Num2Chr(Bits2Num(currentLog.bits,(i * 6)));
	var modLogs = logs[modNo];
	if (modLogs == undefined) logs[modNo] = modLogs = new Object();
	modLogs[submodNo] = rawLog;
}


function writeCookie()
{
	var modlogs,log;
	var str = "";                                                                      
	for (var modNo in logs)
	{
		modlogs = logs[modNo];
		for (var submodNo in modlogs)
		{
			log = modlogs[submodNo];
			if (str.length > 0) str += ":";
			str += log;
		}
	}
	str = "mousetrial=" + str;
	var decadeHence = new Date();
	decadeHence.setFullYear(decadeHence.getFullYear() + 10);
	//document.cookie = str + "; expires=" + decadeHence.toGMTString();
	document.cookie = str + "; path=/; expires=" + decadeHence.toGMTString(); // need path property?
}


function readCookie()
{
	var str = document.cookie;
	//alert("Reading Cookie: " + str);
	var start = str.indexOf("mousetrial=") + 11;
	var end = str.indexOf(";",start);
	if (end == -1) end = str.length;
	var logMod,subMod;
	var aLog;
	var allLogs = str.substring(start,end).split(":");
	for (var i = 0; i < allLogs.length ;i++)
	{
		aLog = allLogs[i];
		//alert("Reading next cookie log: " + aLog);
		//alert(aLog.length);
		if (aLog.length != 32) continue; //..plus other tests for corrupt logs??
		logMod = Chr2Num(aLog.charAt(0));
		subMod = Chr2Num(aLog.charAt(1));
		//alert("& it's a good 'un at (" + aLog.charAt(0) + "," + aLog.charAt(1) + ")");
		if (logs[logMod] == undefined) logs[logMod] = new Object();
		logs[logMod][subMod] = aLog;
	}
}


function ScoreString()
{
	var str = ' SCORE: ';
	str += (currentLog.nTrials - currentLog.nMisses);
	str += ' out of ';
	str += currentLog.nTrials;
	str += ' <font color="$FF8800">(';
	str += currentLog.n1stTimeHits;
	str += ' 1st time; ';
	str += currentLog.nPrompted;
	str += ' prompted) </font>';
	return str;

}


function OverallScoreString()
{
	var str = ' OVERALL: ';
	var modvec,log;
	var nTrials = 0;
	var n1stTimeHits = 0;
	var nMisses = 0;
	var nPrompted = 0;
	for (var modno in logs)
	{
		modvec = logs[modno];
		for (var logno in modvec)
		{
			if (logno == 63) continue;
			log = modvec[logno];	
			nTrials += ChrPair2Num(log,8);
			n1stTimeHits += ChrPair2Num(log,10);
			nMisses += ChrPair2Num(log,12);
			nPrompted += ChrPair2Num(log,14);
		}
	}
	str += (nTrials - nMisses);
	str += ' out of ';
	str += nTrials;
	//str += ' <font color="$FF8800">(';
	//str += currentLog.n1stTimeHits;
	//str += ' 1st time; ';
	//str += currentLog.nPrompted;
	//str += ' prompted) </font>';
	return str;
}


function NotLicensed()
{
	if (currentLog.best > 0) currentLog.best -= 1;
	if (currentLog.best >= 6) return false;
	if (currentLog.best == 5) return false;
	if (currentLog.best == 3) return false;
	if (currentLog.best == 1) return false;
	
	return NoLicense();
}


function unpackKey(aLog)
{
	var key = "";
	for(var i = 2;i <= 29;i += 3) key += aLog.substring(i,i + 2);
	return key;
	
}


function TrackString()
{
	var key;
	if (logs == undefined) return "UL";
	var modLogs = logs[moduleNo];
	if (modLogs != undefined)
	{
		var licenseLog = modLogs[63];
		if (licenseLog != undefined)
		{
			key = unpackKey(licenseLog);
			return key.slice(5,15);
		}
	}
	var otherKeyLog,modvec;
	for (var modno in logs)
	{
		if (modno == moduleNo) continue;
		modvec = logs[modno];
		otherKeyLog = modvec[63];
		if (otherKeyLog == undefined) continue;
		key = unpackKey(otherKeyLog);
		return key.slice(5,15);
	}
	return "UL";
}


function NoLicense()
{
	if (logs == undefined) return true;
	var modLogs = logs[moduleNo];
	if (modLogs != undefined)
	{
		var licenseLog = modLogs[63];
		if (licenseLog != undefined)
		{
			var key = unpackKey(licenseLog);
			//alert("NoLicense : key = " + key);
			//alert("NoLicense : VerifyLicense(key) = " + VerifyLicense(key));
			if (VerifyLicense(key) <= 0) return false;
		}
	}
	var otherKeyLog,modvec;
	for (var modno in logs)
	{
		if (modno == moduleNo) continue;
		modvec = logs[modno];
		otherKeyLog = modvec[63];
		if (otherKeyLog == undefined) continue;
		if (VerifyLicense(unpackKey(otherKeyLog)) == -1) return false;
	}
	return true;
}


function VerifyLicense(aKey)
{
	if (aKey.length != 20) return 1;
	if (aKey.slice(0,5) != colinise("NPVTF",-1)) return 1;
	var mNoPos = Chr2Num(aKey.charAt(5));
	//alert("VerifyLicense : mNoPos = " + mNoPos);
	if (mNoPos <= 5) return 1;
	if (mNoPos > 19)
	{
		var gNoPos = Chr2Num(aKey.charAt(6));
		//alert("VerifyLicense : gNoPos = " + gNoPos);
		if (gNoPos <= 6) return 1;
		if (gNoPos > 14) return 1;
		//alert("VerifyLicense : gCharNum = " + Chr2Num(aKey.charAt(gNoPos)) );
		if (Chr2Num(aKey.charAt(gNoPos)) != 16) return 1;
		return -1;
	}
	else
	{
		if (mNoPos > 14) return 1;
		if (Chr2Num(aKey.charAt(mNoPos)) != moduleNo) return 2;
		return 0;
	}	
}


function WriteLicense(aKey)
{
	// temporary. Add scrambling and fill (useful fill? - date??)
	var log = moduleNo + "*";
	for (var i = 0;i < 19;i += 2)
	{
		log += aKey.substring(i,i + 2); // this needs scrambling. reciprocal in NotLicensed.
		log += "0"; // fill.	
	}
	var modLogs = logs[moduleNo];
	if (modLogs == undefined) logs[moduleNo] = modLogs = new Object();
	modLogs[63] = log;	
}

function BuyKey()
{
	document.location = "http://order.kagi.com/?6FCDJ&lang=en&from=keypadbutton";
}

function MailLostKey()
{
	document.location = "mailto:lostkey@mousetrial.com";
}


function makeKeypad()
{
	var i = currentLog.best;
	var titlestr = '<big><strong>Enter License Key  </strong></big>';
	document.write('<form>');
	document.write(titlestr);
	if (i > 0) document.write('...or <input type="button" value="just have another FREE GO!" onClick="window.location.reload();">');
	document.write('<p>');
	var blurb = 	'Enter your 20 character licence key here (the 5 uppercase characters "MOUSE" ' +
					"followed by fifteen lowercase characters or numerical digits). Don't worry if you've forgotten " +
					'your license key; just email us at ' +
					'<img align=absbottom src="email_lostkey.gif" onClick="MailLostKey();" ' +
					'onmouseover="this.style.cursor=' + "'hand'" + '";>' +
					 "and we'll send you a reminder within a few days. " +
					"If you haven't got a license key yet please vist the " +
					'<a href="http://order.kagi.com/?6FCDJ&lang=en&from=keypad"> Mousetrial Shop </a> page, where you can purchase a key online and ' +
					"have it immediately emailed to you. " +
					"MouseTrial licence keys <strong>last forever!</strong> (it's a one-off purchase, not a subscription service) and you " +
					'can pay by <font color="ORANGE">PayPal</font>, <font color="GREEN">Visa</font>, <font color="RED">MasterCard</font>, ' +
					'<font color="BLUE">Amex</font> or many other options.' +
					"<p> <small>For more information about license keys and the purchase process see our" +
					'<a href="http://www.mousetrial.com/mousetrial_shop.html"> step-by-step "how to buy" guide</small></a>.';
					
	document.write(blurb);
	document.write('<p>');
	document.write('<table><tr>');
	document.write('<td>');
	document.write('<input type="text" name="keytext" size="40" maxlength="20">');
	document.write('</td>');
	document.write('<td>');	
	document.write('   <input type="button" value="Enter License" onClick="LicenseEntered();">');
	document.write('</td>');
	document.write('<td><small>');
	document.write("after you've entered your key<br> you can play uninterrupted<br> without seeing this page again");
	document.write('</small></td>');
	document.write('</tr></table>');
	document.write('<p>');	
	//
	document.write('<input type="button" value="Buy a License Key from our online store" onClick="BuyKey();">');
	document.write('<p>');		
//	var preamble = '...or <input type="button" value="';
//	var postamble = '" onClick="window.location.reload();">';
//	if (i == 7) document.write(preamble + "Give us a few free goes!" + postamble);
//	else if (i == 4) document.write(preamble + "Give us one more free go!" + postamble);
//	else if (i == 2) document.write(preamble + "Come on! ..just one more free go! PLEASE!!" + postamble);
//	else
	if (i == 0)
	{
		document.write('<font color="RED">');
		document.write("You've run out of free goes on this game!  But you've probably still got some free goes left in ");
		document.write('<a href="../mousetrial.html?from=keypad">other games in this module</a>, ');
		document.write("or in the games in the other MouseTrial modules. So why not try them out? ");
		//document.write("Also, don't forget that MouseTrial free goes magically <strong>re-charge</strong> overnight. So you can ");
		//document.write("always come back tomorrow and have more fun with this game!");
		document.write('</font>');
	}
//	
//	
	document.write('<p>');
	document.write('<table width="100%"><tr width="100%">\n');
	document.write('<td align="left">');
	document.write('<a href="../mousetrial.html?from=keypad">[Back to ' + moduleTitle + ' module Main Menu]</a>');
	document.write('</td>\n');
	document.write('<td align="right">');
	document.write('<a href="http://www.mousetrial.com/?from=keypad">[Mousetrial home page: www.mousetrial.com]</a>');
	document.write('</td>\n');
	document.write('</tr></table>\n');
	//
	document.write('</form>');
}


function LicenseEntered()
{
	var frm = document.forms[0];
	var candidateKey = frm.keytext.value;
	var blurb;
	var success = VerifyLicense(candidateKey);
	if (success <= 0)
	{
		blurb =	"License Key Accepted\n\n" +
				"You won't normally need to enter the license key again, but please\n" +
				"keep a copy of it in a safe place in case you need to need to quote\n" +
				"it for support purposes, move Mousetrial to a new computer, or re-enter\n" +
				"it following certain kinds of computer spring cleaning.";
		alert(blurb);
		WriteLicense(candidateKey);
		window.location.reload();
	}
	else
	{
		if (success == 2)
		{
			blurb =	"Incorrect license key for " + moduleTitle + " module\n\n" +
					"Please check that you are not using the license key for a different module.";
		}
		else
		{
			blurb =	"License Key not recognised\n\n" +
					"Please check spelling and capitalisation carefully and try again.";
		}
		alert(blurb);
		document.location = "../mousetrial.html"
	}
}



function filename2caption(filename)
{
	var n;
	var caption = filename;
	n = caption.indexOf(".");
	if (n != -1) caption = caption.slice(0,n);
	n = caption.indexOf("still_");
	if (n != -1) caption = caption.slice(n + 6);
	n = caption.indexOf("word_");
	if (n != -1) caption = caption.slice(n + 5);
	n = caption.indexOf("num_");
	if (n != -1) caption = caption.slice(n + 4);
	n = caption.indexOf("dice_");
	if (n != -1) caption = caption.slice(n + 5);
	caption = caption.replace(/_/g," ");
	caption = " " + caption + " ";
	
	return caption;	
}



function miniFileName(filename)
{
	var n; // position of the dot
	var m; // position of the double underscore
	var mfname = "mini_";
	n = filename.indexOf(".");
	if (n != -1)
		if (n >= 1)
			if (filename.charAt(n - 1) == "_")
			{
				m = filename.indexOf("__");
				if (m != -1) return mfname + filename.slice(0,m) + filename.slice(m + 1,n - 1) + filename.slice(n);
			}
	return mfname + filename;
}



function isSmallStillFile(filename)
{
	var n; // position of the dot
	var m; // position of the double underscore
	n = filename.indexOf(".");
	if (n != -1)
		if (n >= 1)
			if (filename.charAt(n - 1) == "_")
			{
				m = filename.indexOf("__");
				if (m != -1) return true;
			}
	return false;
}