/*
 *	My Universal Javascript Vocabulary Trainer  (MUJaVoT)
 *	(C) 2003-2004 by Markus Stengel
 *	All rights reserved
 */

/******************************************************************
 * 									generic
 ******************************************************************/


/* some constants */

var br_dall			= (document.all ? true : false); // for broken browsers...
var br_impl			= (document.implementation ? true : false); // for standard compliant browsers...
var br_impl2			= (br_impl ? (document.implementation.createDocument ? true : false) : false)
var browser_ie			= (br_dall && !(!window.ActiveXObject));
var browser_std			= br_impl2;
var browser_konq		= br_dall && br_impl2;
var browser_opera		= (window.opera ? true : false);

/* note: (browser_ie x browser_std):
	(t,t) : konqueror
	(f,t) : Mozilla
	(t,f) : Internet Explorer
*/

var cnstVersion			= "0.9.1";

var cnstRaceCheckTime		= 10000;

var cnstDisplayVocableCutOff	= 59;

var cnstTrainCheckYomi		= 1;
var cnstTrainCheckTrans		= 2;
var cnstTrainCheckAll 		= 3;

var cnstTrainCheckYomiHide	= 0;
var cnstTrainCheckYomiShow	= 1;

var cnstLangJapOther		= 1;
var cnstLangOtherJap		= 2;

var cnstGUITrainFontJapSize	= "28pt";
var cnstGUITrainFontOthSize	= "14pt";
var cnstGUITrainShuffleYes	= 1;
var cnstGUITrainShuffleNo	= 0;


var cnstGUIMultipleFontJapSize	= "20pt";
var cnstGUIMultipleFontOthSize	= "14pt";
var cnstGUIMultipleShuffleYes	= 1;
var cnstGUIMultipleShuffleNo	= 0;


var cnstGUISearchSearchForeign	= 0;
var cnstGUISearchSearchReading	= 1;
var cnstGUISearchSearchNative	= 2;
var cnstGUISearchSearchGrammar	= 4;
var cnstGUISearchSearchComment	= 8;


var cnstGUIMultipleChoices	= 5;

var cnstModeTrainGeneral	= 0;
var cnstModeTrainCheck		= 1;
var cnstModeTrainMultiple	= 2;
var cnstModeBuildTree		= 4;
var cnstModeSearch		= 8;

var cnstModeMenuCheck		= 0;
var cnstModeMenuMultiple	= 1;
var cnstModeMenuBuildTree	= 2;
var cnstModeMenuSearch		= 4;

var cnstSubFromTo  = 0;
var cnstSubFromReading		= 1;
var cnstSubReadingTo		= 2;
var cnstSubFromReadingTo	= 3;
var cnstSubFromToReading	= 4;

var cnstLecturesPath		= "lectures/";

var cnstMainFile		= "index.html"

var cnstTrainerModeOld		= 0;
var cnstTrainerModeNew		= 1;

var cnstTrainerSortListYes	= 1;
var cnstTrainerSortListNo	= 0;

var cnstTrainerRemDuplYes	= 1;
var cnstTrainerRemDupNo		= 0;

var cnstVocFastCopyYes		= 1;
var cnstVocFastCopyNo		= 0;

var cnstVocFastShuffleYes	= 1;		
var cnstVocFastShuffleNo	= 0;
var defVocFastShuffle		= cnstVocFastShuffleYes;

var cnstVocCompExact		= 1;		
var cnstVocCompBegin		= 2;		
var cnstVocCompPartial		= 4;		
// var defVocComp			= cnstVocCompExact;


/* general defaults */
var defOptSubMode			= cnstSubFromTo;
var defOptCheckMode			= cnstLangJapOther;
var defOptCheckShowYomi		= cnstTrainCheckYomiHide;

var defOptCheckShuffle		= cnstGUITrainShuffleYes;
//var defOptCheckShuffle		= cnstGUITrainShuffleNo;

var defOptMultipleShuffle	= cnstGUIMultipleShuffleYes;
//var defOptMultipleShuffle	= cnstGUIMultipleShuffleNo;

//var defTrainerMode			= cnstTrainerModeOld;
var defTrainerMode			= cnstTrainerModeNew;

var defOptTrainerSort		= cnstTrainerSortListYes;

var defOptTrainerRemDupl	= cnstTrainerRemDuplYes;

var defVocFastCopy		= cnstVocFastCopyYes;

var cnstVocRemoveDel		= 0;
var cnstVocRemoveCopy		= 1;
var defOptVocRemove		= cnstVocRemoveCopy;

var vStartTime = 0;


/*	Function: 		goMain
 *	Purpose:		check before going to main menu
 */
function goMain() {
	var Check = confirm("Do you really want to go to the main menu? Your current results will be lost!");
	if (Check == true) {
		window.location.href=cnstMainFile;
	}
	return false;
}

/*	Function: 		debug
 *	Purpose:		helps for debugging
 *	Parameters:
 *		text:		what to print/say
 */
function debug(text) {
	//statusPrint(text);
}

/*	Function: 		allLectures
 *	Purpose:		check/uncheck all lectures
 */
function allLectures()
{
	var x,y,z;
	var all = window.document.getElementsByName("ALLLECS")[0];

	var docNode = document.getElementById("lectureListBody");
	var cnc = docNode.childNodes.length;
	if (browser_ie && !browser_std) {			// stupid browser can't use anything but forms with the getElementsById!
		for(x=1; x < cnc;x++) {		
			docNode.childNodes[x].firstChild.firstChild.checked = all.checked;
		}
	}
	else {
		var elements = window.document.getElementsByName("s");
		for(x=0; x < elements.length;x++) {		
			elements[x].checked = all.checked;
		}
	}
	
}
/*	Function: 		aboutProg
 *	Purpose:		informs about the program, its creator etc.
 */
function aboutProg() {

	text  =  "My Universal Javascript Vocabulary Trainer  (MUJaVoT) v"+cnstVersion;
	text += "\n"+"(C) 2003-2004 by Markus Stengel, all rights reserved";
	text += "\n\n";
	text += "\n"+"Free for private usage and non-commercial purposes, for details check out";
	text += "\n"+"http://www.MarkusStengel.de or mail to kontakt@markusstengel.de.";
	text += "\n\n";
	text += "\n"+"Note: This application requires at least Mozilla 1.2/Firebird/FireFox.";
	alert(text);
}

/*	Function:   showFailed
 *	Purpose:		shows a list of the failed vocables
 *	Parameters:
 		vocList:	the vocable list to use
 		min:		how many times these vocables have to have been wrong
 */
function showFailedGeneral(vocList, min) {
	var failed;
	var text = "";
	var voc = null;
	var diff = 0;
	var cur = "";
	var i;
	
	if (!(min > 0)) min = 1;
	failed = vocList.getFailed();

	if (failed.length == 0) {
		alert("No mistakes so far!");
	}
	else {
		for (i=0; i<failed.length; i++) {
			voc	= vocList.get(failed[i]);
			if (defOptSubMode != cnstSubReadingTo) {
				cur 	= voc.fromToString();
			}
			else {
				cur 	= voc.readingToString();
			}
			cur	= cur + "("+voc.getTestWrongCount()+"x)";
			if (i<=failed.length-1 && i>0) text += ";";
			if (text.length + cur.length - diff >=cnstDisplayVocableCutOff) {
				text = text + "\n";
				diff = text.length;
				text = text + cur;
			}
			else {
				text 	= text + " " + cur;
			}
		}

		alert(text);
	}
	if (window.document.InputForm && window.document.InputForm.InputField) {
		document.InputForm.InputField.focus();
	}
	else {
		choices = window.document.getElementsByName('choices');
		choices[0].focus();
	}
}

/*	Function: 		arrContainsStr
 *	Purpose:		checks whether the string str exists in array
 *	Parameters:
 *		arr:		Array
 *		str:		string that is searched for
 *		ignCase:	ignore case or not
 *		compMode:	exact match, beginning or partial match
 *	Returns:
 *		true:		found
 *		false:		not found
 */
function arrContainsStr(arr, str, ignCase, compMode) {
	var i;

	if (ignCase) {								// ignore case
		str = str.toUpperCase();				// change str accordingly
	}
	if (arr.length == 0) return false;			// array contains no items -> can't find it
	//alert (compMode);
	for (i=0; i<arr.length; i++) {				// search for the string
		var cur = arr[i];						// get current element
		if (ignCase) {							// ignore case
			cur = cur.toUpperCase();
		}
		switch (compMode) {
			case cnstVocCompBegin:
			if (cur.substring(0, str.length) == str) {
				return true;
			}
			break;
			
			case cnstVocCompPartial:
			var spl = cur.split(str);
			if (spl.length>1) return true;
			break;
			
			case cnstVocCompExact:
			default:
				// exact match	
			if (str == cur) {						// found?
				return true;						// yes, signal success
			}
			break;
		}
	}
	return false;								// could not find it, signal failure
}
/*	Function: 		statusPrint
 *	Purpose:		print text to status bar if possible
 *	Parameters:
		text:		text to be printed
 */
function statusPrint(text) {
	/*
	if (window.statusbar && window.statusbar.visible)
		window.defaultStatus = text;
	*/
	window.status = text;
	return true;
}

/*	Function: 		sleep
 *	Purpose:		simulates a sleep
 *	Parameters:
 *		t:		how many miliseconds to sleep
 */

function sleep(t) {
	var count = 0;
	var ended = false;

	function OK() {
		ended = true;
	}
	window.setTimeout("OK()", t);
	while (!ended) {
		// do nothing but count
		count++;
	}

	return count;
}


/*	Function: 		removeWhiteSpaces 
 *	Purpose:		removes the whitespaces at the beginning and the
 *					end of a string
 *	Parameters:
 *		str:		the string
 *	Returns:
 *		the new string without the whitespaces
 */
function removeWhiteSpaces(str) {
	if (typeof str != "string") {
		//alert(str+ " : "+(typeof string));
		return str; 	// fail silently
	}
	var whtSpEndings 	= new RegExp("^\\s*|\\s*$", "g");
	var whtSpMultiples 	= new RegExp("\\s\\s+", "g");


  	str = str.replace(whtSpMultiples, " ");
  	str = str.replace(whtSpEndings, "");

	return str;

}

/*	Function: 		normalizeNode
 *	Purpose:		removes all whitespace nodes
 *	Parameters:
 *		node:		the node which should be normalized
 *	Returns:
 *		the new string without the whitespaces
 */
function normalizeNode(node) {
  var i;
  var childNode, remove;
  var j=0;
  var total=0;  
  for (i = 0; i < node.childNodes.length; i++) {
    childNode = node.childNodes[i];    
    remove = false;
    if (childNode.nodeType == 3) {
     if (childNode.nodeValue) {       
      if (childNode.nodeValue.search(/\S+/) == -1) remove = true;      
     }
     else remove = true;
     if (remove) {        
        node.removeChild(node.childNodes[i]);
        i--;	// there is now one node less in there...
        j++;
     }
    }
    if (childNode.nodeType == 1) {
      total+=normalizeNode(childNode);	// recursively normalize the children
    }
  }  
  total += j;
  return total;
}

/*	Function: 		getNode
 *	Purpose:		searches for a specified subnode
 *	Parameters:
 *		parentNode:	a node-object which's children are traversed
 *		search:		the node's name
 *	Returns:
 *		the desired node, null if failure
 */
function getNode(parentNode, search, last) {

	if (!browser_ie && parentNode.getElementsByTagName) {
		// fast method
		var all = parentNode.getElementsByTagName(search);
		if (all.length==0) return null;
		if (!last) return all[0]; else return all[all.length-1];
	}
	var vocs	= parentNode.childNodes;
	var vocsCnt = vocs.length;
	var found 	= -1;
	var i;
	if (last != true) last = false;

//	if (search=="lecture") alert("searching for "+search+" in "+vocs+" , last is "+last);
	for (i=0; i<vocsCnt; i++) {
		var cn = vocs[i];
		if (cn.nodeName == search) {
			//if (found > -1) alert("revising an old one");
			found = i;
			if (!last) i = vocsCnt;
		}
	}
	if (found < 0) {
		//alert ("XML document error: no "+search+" group found!");
		return null;
	}
	else {
		return vocs[found];
	}
}

/*	Function: 		shuffleVocList
 *	Purpose:		shuffle a vocabulary list
 *	Parameters:
 *		vocList:	the vocabulary list
  *		
 *		new, shuffled version of the orignial vocabulary list
 */
function shuffleVocList(vocList) {
	var i = 0;
	var j;
	var el, rand;
	var old;
	var number = vocList.getCount()	;
	var newList = new vocableList();
	var locCntVocs = vocList.getCount();
	if (defVocFastShuffle == cnstVocFastShuffleNo) {
		while (locCntVocs > 0) {
			rand = Math.round((vocList.getCount()-1)*Math.random());
			old = vocList.get(rand);
			if (defVocFastCopy == cnstVocFastCopyNo) {
				var v = new vocable();
				for (j=0; j<old.getFromCount(); j++) {
					v.addFrom(old.getFrom(j));
				}
				for (j=0; j<old.getReadingCount(); j++) {
					v.addReading(old.getReading(j));
				}
				for (j=0; j<old.getToCount(); j++) {
					v.addTo(old.getTo(j));
				}
				for (j=0; j<old.getGrammarCount(); j++) {
					v.addTo(old.getGrammar(j));
				}
				for (j=0; j<old.getCommentCount(); j++) {
					v.addTo(old.getComment(j));
				}
				newList.add(v);
			}
			else {
				newList.add(old);
			}	
			vocList.del(rand);
			locCntVocs--;
		}
		if (vocList.getCount() > 0) {
			alert("ERROR(shuffleVocList): vocList should be empty now, but is not! Aborting!");
			return null;
		}
		if (newList.getCount() != number) {
			alert("ERROR(shuffleVocList): Vocables got lost! Aborting!");
			return null;
		}
	
		for (var ladd=0; ladd<newList.getCount(); ladd++) {
			var v = newList.get(ladd);
			vocList.add(v);
		}
		
		return newList;
	}
	else {
		var trackArr = new Array();
		var maxSize = locCntVocs;		
		for (i=0; i<locCntVocs; i++) trackArr[i]=false;
		while (locCntVocs > 0) {
			decd = Math.round(Math.random()*(maxSize-1));
			srch = decd;
			// search for unused vocable
			while (trackArr[srch]) {
				srch++;
				if (srch>=maxSize) srch = 0;
			}
			// found it -> add it
			var v = vocList.get(srch);
			newList.add(v);
			trackArr[srch] = true;			
			locCntVocs--;
			//alert("added: "+srch+" -> "+locCntVocs+" left");
		}
		if (newList.getCount() != vocList.getCount()) {
			alert("ERROR(shuffleVocList): Vocables got lost! Aborting!");
			return null;
		}		
		for (i=maxSize-1; i>-1;i--) {
			vocList.del(i);
		}
		for (i=0; i<maxSize;i++) {
			vocList.add(newList.get(i));
		}
		
		return newList;
	}
}
	
/******************************************************************
								mask-specific functions
 ******************************************************************/

function maskTrainGetCheckOptions() {
	var group = document.InputForm.checkWhat;
	if (group[0].checked)
		return cnstTrainCheckAll;
	else if (group[1].checked)
		return cnstTrainCheckYomi;
	else
		return cnstTrainCheckTrans;
	
}


/******************************************************************
 * 									statistics
 ******************************************************************/

// statistics object
function statistics() {
	
}


/******************************************************************
 * 									lecture
 ******************************************************************/

// lecture object
function lecture(desc, fileName, ID) {
	// internal constants
	// create the necessary data storage objects
	this.desc 				= desc;
	this.fileName			= removeWhiteSpaces(fileName);
	this.ID					= ID;	
	
	// add the functions
	this.getDesc			= _lectureGetDesc;
	this.getFileName		= _lectureGetFileName;
	this.getID				= _lectureGetID;
}

function _lectureGetDesc() {
	return this.desc;
}
function _lectureGetFileName() {
	return this.fileName;
}
function _lectureGetID() {
	return this.ID;
}
/******************************************************************
 * 									vocable
 ******************************************************************/

// vocable object
function vocable() {
	// internal constants
	
	this.cnstTestRight		= 1;
	this.cnstTestWrong		= 2;
	this.cnstTestAll		= 3;
	
	// create the necessary data storage objects
	this.from 			= new Array();
	this.reading			= new Array();
	this.to				= new Array();	
	this.grammar			= new Array();	
	this.comment			= new Array();	
	
	this.checked			= false;
	this.testRight			= 0;
	this.testWrong			= 0;
	
	// add the functions
	this._get			= _vocable_Get;
	this._add			= _vocable_Add;
	this._toString			= _vocable_ToString;
	this._comp			= _vocable_Comp;
	
	this._getTestCount		= _vocable_GetTestCount;
	this._setTestCount		= _vocable_SetTestCount;

	this.getTestCount		= _vocableGetTestCount;
	this.getTestRightCount		= _vocableGetTestRightCount;
	this.getTestWrongCount		= _vocableGetTestWrongCount;

	this.setTestCount		= _vocableSetTestCount;
	this.setTestRightCount		= _vocableSetTestRightCount;
	this.setTestWrongCount		= _vocableSetTestWrongCount;
	
	this.addFrom 			= _vocableAddFrom;
	this.addReading 		= _vocableAddReading;
	this.addTo 			= _vocableAddTo;
	this.addGrammar			= _vocableAddGrammar;
	this.addComment			= _vocableAddComment;

	this.getFrom 			= _vocableGetFrom;
	this.getReading			= _vocableGetReading;
	this.getTo 			= _vocableGetTo;
	this.getGrammar 		= _vocableGetGrammar;
	this.getComment			= _vocableGetComment;

	this.toToString			= _vocableToToString;
	this.fromToString		= _vocableFromToString;
	this.readingToString		= _vocableReadingToString;
	this.grammarToString		= _vocableGrammarToString;
	this.commentToString		= _vocableCommentToString;
	this.toString			= _vocableToString;
	
	this.compFrom			= _vocableCompFrom;
	this.compReading		= _vocableCompReading;
	this.compTo			= _vocableCompTo;
	this.compGrammar		= _vocableCompGrammar;
	this.compComment		= _vocableCompComment;
	
	this.setChecked			= _vocableSetChecked;
	this.getChecked			= _vocableGetChecked;
	
	this.getFromCount		= _vocableGetFromCount;
	this.getToCount			= _vocableGetToCount;
	this.getReadingCount		= _vocableGetReadingCount;
	this.getGrammarCount		= _vocableGetGrammarCount;
	this.getCommentCount		= _vocableGetCommentCount;


}

// count functions
function _vocableGetFromCount() {
	return this.from.length;
}
function _vocableGetToCount() {
	return this.to.length;
}
function _vocableGetReadingCount() {
	return this.reading.length;
}
function _vocableGetGrammarCount() {
	return this.grammar.length;
}
function _vocableGetCommentCount() {
	return this.comment.length;
}

// test functions, useful for statistics
function _vocableGetTestCount() {
	return this._getTestCount(this.cnstTestAll);
}

function _vocableGetTestRightCount() {
	return this._getTestCount(this.cnstTestRight);
}
function _vocableGetTestWrongCount() {
	return this._getTestCount(this.cnstTestWrong);
}

function _vocable_GetTestCount(whichCount) {
	var i_cnt = 0;
	switch  (whichCount) {
		case this.cnstTestRight:
			i_cnt = this.testRight;
			break;
		case this.cnstTestWrong:
			i_cnt = this.testWrong;
			break;
		case this.cnstTestAll:
			i_cnt = this.testRight + this.testWrong;
			break;
		default:
			alert("ERROR (_vocable_GetTestCount): Illegal parameter!");
			return -1;
	}
	return i_cnt;
}

function _vocable_SetTestCount(whichCount, value) {
	switch  (whichCount) {
		case this.cnstTestRight:
			this.testRight = value;
			break;
		case this.cnstTestWrong:
			this.testWrong = value;
			break;
		case this.cnstTestAll:
			this.testRight = value;
			this.testWrong = value;
			break;
		default:
			alert("ERROR (_vocable_SetTestCount): Illegal parameter!");
			return -1;
	}
}

function _vocableSetTestRightCount(value) {
	this._setTestCount(this.cnstTestRight, value);
}
function _vocableSetTestWrongCount(value) {
	this._setTestCount(this.cnstTestWrong, value);
}
function _vocableSetTestCount(value) {
	this._setTestCount(this.cnstTestRight, value);
	this._setTestCount(this.cnstTestWrong, value);
}

// add a new string to the vocable
function _vocable_Add(array, str) {
	array[array.length] = removeWhiteSpaces(str);
}
function _vocableAddFrom(from) {
	this._add(this.from, from);
}
function _vocableAddTo(to) {
	this._add(this.to, to);
}
function _vocableAddReading(reading) {
	this._add(this.reading, reading);
}
function _vocableAddGrammar(reading) {
	this._add(this.grammar, reading);
}
function _vocableAddComment(reading) {
	this._add(this.comment, reading);
}

// get a string from the vocable
function _vocable_Get(array, index) {
	if (index >= array.length) {
		alert("_vocable_Get: index to large!");
	}
	else {
		return array[index];
	}
}

function _vocableGetFrom(index) {
	return this._get(this.from, index);
}

function _vocableGetTo(index) {
	return this._get(this.to, index);
}

function _vocableGetReading(index) {
	return this._get(this.reading, index);
}
function _vocableGetGrammar(index) {
	return this._get(this.grammar, index);
}
function _vocableGetComment(index) {
	return this._get(this.comment, index);
}

// make a string from the vocable for quick display
function _vocable_ToString (array, cutOff) {
	if (!cutOff) {
		return new Array(array.join("; "));
	}
	else {
		var i, diff;
		var cur, str;
		str = "";
		diff = 0;
		for (i=0; i<array.length; i++) {
			cur = array[i];
			if (str.length+cur.length-diff >= cnstDisplayVocableCutOff) {
				if (i>0) str += ";\n";
				diff = str.length;
			}
			else {
				if (i>0) str += "; ";
			}
			str = str + cur;
		}
		return str;
	}
}
function _vocableToString() {
	var both = new Array(this.from.join("; "), this.to.join("; "));
	return both.join(" | ");
}

function _vocableFromToString(cutOff) {
	return this._toString(this.from, cutOff);
}

function _vocableToToString(cutOff) {
	return this._toString(this.to, cutOff);
}

function _vocableReadingToString(cutOff) {
	return this._toString(this.reading, cutOff);
}

function _vocableGrammarToString(cutOff) {
	return this._toString(this.grammar, cutOff);
}

function _vocableCommentToString(cutOff) {
	return this._toString(this.comment, cutOff);
}


// check whether a str is in the from/reading/to list of the vocable
function _vocable_Comp(array, str, ignCase, compMode) {
	return arrContainsStr (array, removeWhiteSpaces(str), ignCase, compMode);
}

function _vocableCompFrom(str, ignCase, compMode) {
	return this._comp(this.from, str, ignCase, compMode);
}

function _vocableCompTo(str, ignCase, compMode) {
	return this._comp(this.to, str, ignCase, compMode);
}

function _vocableCompReading(str, ignCase, compMode) {
	return this._comp(this.reading, str, ignCase, compMode);
}

function _vocableCompGrammar(str, ignCase, compMode) {
	return this._comp(this.grammar, str, ignCase, compMode);
}

function _vocableCompComment(str, ignCase, compMode) {
	return this._comp(this.comment, str, ignCase, compMode);
}

// check/set whether the vocable was already checked
function _vocableSetChecked(checked) {
	if (checked) {
		this.checked = true;
	}
	else {
		this.checked = false;
	}
}
function _vocableGetChecked() {
	return this.checked;
}



/******************************************************************
 * 									vocableList
 ******************************************************************/

// vocableList object
function vocableList() {

	// constants	
	this.cnstTestRight		= 1;
	this.cnstTestWrong		= 2;
	this.cnstTestAll		= 3;

	// create the necessary data storage objects
	this.list 	= new Array();
	
	// add the functions
	this._getChkUnchkCount	= _vocableList_GetChkUnchkCount;
	this._getChkUnchk		= _vocableList_GetChkUnchk;

	this._getTestCount		= _vocableList_GetTestCount;

	this.getTestCount		= _vocableListGetTestCount;
	this.getTestRightCount	= _vocableListGetTestRightCount;
	this.getTestWrongCount	= _vocableListGetTestWrongCount;
	
	this.add 				= _vocableListAdd;
	
	this.del				= _vocableListDel;

	this.get 				= _vocableListGet;
	this.getChecked			= _vocableListGetChecked;
	this.getUnchecked		= _vocableListGetUnchecked;
	
	this.getCount 			= _vocableListGetCount;
	this.getCheckedCount	= _vocableListGetCheckedCount;
	this.getUncheckedCount	= _vocableListGetUnCheckedCount;

	this.getVocablesNode	= _vocableListGetVocablesNode;

	this.printList			= _vocableListPrintList;

	this.getFailed		= _vocableListGetFailed;

	this.sort		= _vocableListSort;
	this.removeDuplicates	= _vocableListRemoveDuplicates;
}

// test/statistics data

function _vocableListGetTestCount(){
	return this._getTestCount(this.cnstTestAll);
}
function _vocableListGetTestRightCount(){
	return this._getTestCount(this.cnstTestRight);
}
function _vocableListGetTestWrongCount(){
	return this._getTestCount(this.cnstTestWrong);
}

function _vocableList_GetTestCount(whichCount){
	var i;
	var v_voc;		
	var i_cnt = 0;
	for (i=0; i<this.getCount(); i++) {
		v_voc = this.get(i);
		switch  (whichCount) {
			case this.cnstTestRight:
				i_cnt += v_voc.getTestRightCount();
				break;
			case this.cnstTestWrong:
				i_cnt += v_voc.getTestWrongCount();
				break;
			case this.cnstTestAll:
				i_cnt += v_voc.getTestCount();
				break;
			default:
				alert("ERROR (_vocableList_GetTestCount): Illegal parameter!");
				return -1;
		}
	}
	return i_cnt;
}
// add a vocable to the list
function _vocableListAdd(vocable) {
	// store the vocable in the array
	this.list[this.getCount()]	= vocable;
}
// delete a vocable from the list
function _vocableListDel(index) {
	if (index >= this.getCount()) {
		alert("_vocableListDel: index to large!");
	}
	else {
		if (index == 0) {
			this.list.shift();
		}
		else if (index == this.getCount()-1) {
			this.list.pop();
		}
		else {
			var newList = new Array();
			for (i=0; i<this.getCount(); i++) {
				if (i!=index)
					newList[newList.length] = this.get(i);
			}
			this.list = newList;
		}
	}
}

// get a vocable from the list
function _vocableListGet(index) {
	if (index >= this.getCount()) {
		alert("_vocableListGet: index to large!");
	}
	else {
		return this.list[index];
	}
}

// get a checked/unchecked vocable ID from a list

function _vocableList_GetChkUnchk(checked, start, includeStart) {
	var i;
	var i_stCheck;
	var v_cur;
	if (this._getChkUnchkCount(checked) == 0) {
		// no more vocables like that left
		return -1;
	}
	if (isFinite(start)) {
		 i_stCheck = start;
	}
	else {
		 i_stCheck = 0;
	}
	if (i_stCheck >= this.getCount()) i_stCheck = 0;
	
	for (i=i_stCheck; i<this.getCount(); i++) {
		v_cur = this.get(i);
		if (v_cur.getChecked() == checked) {
			return i;
		}			
	}
	for (i=0; i<i_stCheck; i++) {
		v_cur = this.get(i);
		if (v_cur.getChecked() == checked) {
			return i;
		}			
	}

	alert("ERROR (_vocableList_GetChkUnchk): Logic error - shouldn't be able reach this!");
	return -1;
}

function _vocableListGetChecked (start, includeStart) {
	return this._getChkUnchk(true, start, includeStart);
}

function _vocableListGetUnchecked (start, includeStart) {
	return this._getChkUnchk(false, start, includeStart);
}

// get the number of vocables stored in the list
function _vocableListGetCount() {
	return this.list.length;
}

// get the number of checked/unchecked vocables in the list
function _vocableList_GetChkUnchkCount(checked) {
	var i;
	var i_cnt = 0;
	
	for (i=0; i<this.getCount(); i++) {
		if (this.get(i).getChecked() == checked) i_cnt++;
	}
	return i_cnt;
}

function _vocableListGetCheckedCount () {
	return this._getChkUnchkCount(true);
}

function _vocableListGetUnCheckedCount () {
	return this._getChkUnchkCount(false);
}

// get a certain vocables node from a lectures node
function _vocableListGetVocablesNode(lecsNode, index, isNormalized) {
	var i,j;
	if (lecsNode == null) return null;
	
	var cn = lecsNode.childNodes;
	if (isNormalized && cn.length > index) return cn[index];
	
	if (cn.length <= index) {
		// doesn't exist
		return null;								
	}
	else {
		// need to search for it, ignore the #text nodes
		j = 0;
		for (i=0; i<cn.length; i++) {
			if (cn[i].nodeName == "vocables") {
				if (j == index) {
					return cn[i];
				}
				else {
					j++;
				}
			}
		}
		// not found
		return null;
	}
}

// print the current list of vocabulary
function _vocableListPrintList() {
	var i;
	var s_str = "";

	for (i=0; i<this.getCount(); i++) {
		s_str += this.get(i)+"\n";
	}
	alert(s_str);
}

// get a list of all min-times failed vocables
function _vocableListGetFailed(min) {
	var i;
 	var times = 0;
	var failed = new Array();

 	if (!(min>0)) min = 1;

	for (i=0; i<this.getCount(); i++) {
		times = this.get(i).getTestWrongCount();
		if (times >= min) failed[failed.length] = i;
	}
	return failed;
}

// sort the vocable list, sub function
function _vocableListSort_Sub(a,b) {
	var strA = a.fromToString();
	var strB = b.fromToString();
	
	if (strA < strB) {
		return -1;
	}
	else if (strA == strB) {
		return 0;
	}
	else {
		return 1;
	}

}

// sort the vocable list
function _vocableListSort() {
	this.list.sort (_vocableListSort_Sub);
}

// remove duplicates from the list, also sorts the list
function _vocableListRemoveDuplicates() {
	var i, removed;
	var strA, strB;
	var newList;
	
	// first sort the list to make the comparisons easier
	statusPrint("Removing duplicates (Step 1/2): Presorting...");
	this.sort ();

	// now remove duplicate entries
	i = 0;
	removed = 0;
	newList = new Array();
	statusPrint("Removing duplicates (Step 2/2): Filtering out duplicates...");
	while (i<this.list.length-1) {
		// get to consecutive entries
		strA = this.get(i).fromToString();
		strB = this.get(i+1).fromToString();
		
		strA = removeWhiteSpaces(new String(strA));
		strB = removeWhiteSpaces(new String(strB));
		
		if (defOptVocRemove == cnstVocRemoveDel) {
			// check if they are equal
			if (strA == strB) {
				// remove the latter duplicate
				this.del (i);	
				removed++;
			}
			else {
				i++;
			}
		}
		else if (defOptVocRemove == cnstVocRemoveCopy) {
			i++;
			// check if they are equal
			if (strA == strB) {
				// don't copy
				removed++;
			}
			else {
				// copy
				newList [newList.length] = this.get(i);
			}
		}	
		else {
			alert("ERROR(removeDuplicates): Unknown remove mode!");
			return false;
		}
	}	
	if (defOptVocRemove == cnstVocRemoveCopy) {
		if (strA != this.get(i).fromToString()) {
			// copy
			newList [newList.length] = this.get(i);
		}
		// exchange lists now
		this.list = newList;
	}
	//alert("new length: "+this.list.length);
	statusPrint("Removing duplicates: Done, removed "+removed+" duplicates.");
	//alert("removed "+removed+" duplicates");
}

/******************************************************************
 * 									Main
 ******************************************************************/

var vocList 			= new vocableList();
var correctMultipleChoice 	= -1;
var correctMultipleVocID 	= -1;
var lastMultipleCorrectChoice	= -1;

function statTrain(over) {
	var s_ratio;
	var s_resStr;
	if (vocList.getTestCount() > 0) {
		s_ratio = ""+Math.round((vocList.getTestRightCount()/vocList.getTestCount())*100*100)/100+"%";
	}
	else {
		s_ratio = "N/A";
	}
	s_resStr 	= 	"Correct answers: " + vocList.getTestRightCount() +
					"\nWrong answers:  "+ vocList.getTestWrongCount()+
					"\n=>Success Rate: "+s_ratio+"\n";
	if (vStartTime >0) {
		var nD = new Date();
		var curTime = nD.getTime();
		if (curTime > vStartTime) {
			var diff = (curTime - vStartTime)/1000;
			var sec  = Math.floor(diff % 60);
			var min  = Math.floor((diff- sec)/60);
			var tmin ="minute";
			var tsec = "second";
			if (sec != 1) tsec +="s";
			if (min != 1) tmin +="s";

			s_resStr	+= "Time: "+min+" "+tmin+" and "+sec+" "+tsec+"\n";
		}
	}
	if (over) {
		alert("All vocables checked! ^_^\n"+s_resStr);
	}
	else {
		var s_cnt =	"Vocables: "+ vocList.getCount()+
					"\nTotal checks:    "+ vocList.getTestCount() +
					"\n";
		alert(s_cnt + s_resStr);
	}
}

function showFailed(min) {
	return showFailedGeneral(vocList, min);
}
/******************************************************************
 * 									GUI
 ******************************************************************/


function drawGUITrain(vocList) {

	debug("drawGUITrain");

	//var chk = window.document.InputForm.CheckField;
	var acc 	= window.document.getElementsByName('From');
	var chk 	= acc[0].firstChild;
	var inpForm 	= window.document.getElementsByName('InputForm')[0];
	var inp 	= document.InputForm.InputField;
	var vid 	= document.InputForm.VocID;
	var vocLeft 	= window.document.getElementsByName('vocLeft')[0].firstChild;


	switch(defOptCheckMode) {
		case cnstLangJapOther:
			acc[0].style.fontSize 	= cnstGUITrainFontOthSize;
			inp.style.fontSize 	= cnstGUITrainFontJapSize;
			break;
		case cnstLangOtherJap:
			acc[0].style.fontSize 	= cnstGUITrainFontJapSize;
			inp.style.fontSize 	= cnstGUITrainFontOthSize;
			break;
		default:
			alert("ERROR(drawGUITrain): Unknown check mode!");
			return false;
			break;
	}

	var tmp = inp.style.fontSize;
	inp.style.fontSize = acc[0].style.fontSize;
	acc[0].style.fontSize = tmp;

	if (vocList.getUncheckedCount() == 0) {
		alert("No vocables to check!");
	}
	else {
	
		if (isFinite(vid.value)) {
			var n = vid.value;
			n++;
			if (n > (vocList.getCount()-1)) 
				vid.value= 0; 
			else 
				vid.value=n;		
		}
		else {
			vid.value= 0;
		}

		vocLeft.nodeValue = " "+(vocList.getCheckedCount()+1)+"/"+vocList.getCount()+" ";
		vid.value = vocList.getUnchecked(vid.value, true);

		var r = vocList.get(vid.value);	
		

		switch (defOptSubMode) {
			case cnstSubFromTo:
			case cnstSubFromToReading:
			case cnstSubFromReading:
				chk.nodeValue = r.fromToString();
				break;
			case cnstSubFromReadingTo:
				chk.nodeValue = r.fromToString()+" | "+ r.readingToString();
				break;
			case cnstSubReadingTo:
				chk.nodeValue = r.readingToString();
				break;
			default:
				alert("ERROR(drawGUITrain): Reading mode unknown!");
				alert(defOptSubMode);
				return false;
		}

		inp.value = "";
		inp.focus();
	}
}

function drawGUIMultiple(vocList) {

	debug("drawGUIMultiple");

	var i, j;
	var curSel;
	var trueSlot;
	var fakeChoices 	= new Array();
	var found		= false;
	var acc 		= window.document.getElementsByName('From');
	var chk 		= acc[0].firstChild;
	var inpForm 		= window.document.getElementsByName('InputForm')[0];
	var inp 		= document.InputForm.InputField;
	var vid 		= document.InputForm.VocID;
	var choices 		= window.document.getElementsByName('choices');
	//var choicesText		= window.document.getElementsByName('choicesText'); // not possible since IE is broken
	var vocLeft 		= window.document.getElementsByName('vocLeft')[0].firstChild;

	// create the necessary array for managing the text entries (IE cannot address anything else besides form elements)
	var choicesText = new Array(cnstGUIMultipleChoices);
	var choicesPartText = "choicesText";
	for (i=0; i<cnstGUIMultipleChoices; i++) {
		var choicesFullText = choicesPartText+(i+1);
		choicesText[i] = document.getElementById(choicesFullText);
	}

	//alert("entered drawGUIMultiple");

	switch(defOptCheckMode) {
		case cnstLangJapOther:
			acc[0].style.fontSize 	= cnstGUIMultipleFontJapSize;
			for (i=0; i<choicesText.length; i++) {
				choices[i].style.fontSize 	= cnstGUIMultipleFontOthSize;
				choicesText[i].style.fontSize 	= cnstGUIMultipleFontOthSize;
			}
			break;
		case cnstLangOtherJap:
			acc[0].style.fontSize 	= cnstGUIMultipleFontOthSize;
			for (i=0; i<choicesText.length; i++) {
				choices[i].style.fontSize 	= cnstGUIMultipleFontJapSize;
				choicesText[i].style.fontSize 	= cnstGUIMultipleFontJapSize;
			}
			break;
		default:
			alert("ERROR(drawGUIMultiple): Unknown check mode!");
			return false;
			break;
	}

	if (vocList.getCount() < cnstGUIMultipleChoices) {
		alert("Cannot continue checking: There are less ("+vocList.getCount()+") vocables in the list than are required ("+cnstGUIMultipleChoices+"). Aborting!");
		return false;
	}
	
	if (vocList.getUncheckedCount() == 0) {
		alert("No vocables to check!");
	}
	else {

		if (isFinite(vid.value)) {
			var n = vid.value;
			n++;
			if (n > (vocList.getCount()-1)) 
				vid.value= 0; 
			else 
				vid.value=n;		
		}
		else {
			vid.value= 0;
		}

		vid.value = vocList.getUnchecked(vid.value);
		vocLeft.nodeValue = " "+(vocList.getCheckedCount()+1)+"/"+vocList.getCount()+" ";
		
		trueSlot = Math.floor(Math.random()*cnstGUIMultipleChoices);
		if (Math.floor(Math.random()*cnstGUIMultipleChoices*2) == trueSlot * 2) lastMultipleCorrectChoice = -1;
		while (lastMultipleCorrectChoice ==	trueSlot) {
			trueSlot = Math.floor(Math.random()*cnstGUIMultipleChoices);
		}
	
		// putting the true choice into the fakeChoices array, so it gets checked and no duplicate choice can occur; removed later
		fakeChoices[0]=vid.value; i=1;
		// find all necessary choices
		while (i<cnstGUIMultipleChoices) {
			curSel = Math.round(Math.random()*vocList.getCount());
			if (curSel >= vocList.getCount()) curSel = vocList.getCount()-1;
			if (curSel != vid.value) {
				found = false;
				// check if duplicate exists
				for (j=0; j<fakeChoices.length; j++) {
					if (fakeChoices[j] == curSel) {
						found = true;
					}
					else {
						var sameDisplay = true;
						var fVoc = vocList.get(fakeChoices[j]);
						var cVoc = vocList.get(curSel);
						var sameCount = fVoc.getToCount();
						if (sameCount == cVoc.getToCount) {
							for (compFC=0; compFC<sameCount; compFC++) {
								if (fVoc.getTo(compFC) != cVoc.getTo(compFC)) {
									sameDisplay = false;
									break;
								}
							}
							if (sameDisplay) found = true;
						}
						if (!found || !sameDisplay) {
							sameDisplay = true;
							sameCount = fVoc.getFromCount();
							if (sameCount == cVoc.getFromCount) {
								for (compFC=0; compFC<sameCount; compFC++) {
									if (fVoc.getFrom(compFC) != cVoc.getFrom(compFC)) {
										sameDisplay = false;
										break;
									}
								}
								if (sameDisplay) found = true;
							}
						}

					}
				}
				if (!found) {
					// no duplicate, add current selection to fakeChoices array
					fakeChoices[i] = curSel;
					i++;
				}
			}
		}
		
		// store last result
		lastMultipleCorrectChoice = trueSlot;

		// remove true slot from fakeChoices array
		fakeChoices.shift();
		
		// draw the GUI
		j = 0;
		correctMultipleChoice 	= -1;
		correctMultipleVocID	= -1;
				
		for (i=0; i<cnstGUIMultipleChoices; i++) {
			var node;
			if (i == trueSlot)  {
				var str;
				switch (defOptSubMode) {
					case cnstSubFromTo:
					case cnstSubReadingTo:
					case cnstSubFromReadingTo:
						str = vocList.get(vid.value).toToString();
						break;
					case cnstSubFromToReading:
						str = vocList.get(vid.value).toToString()+" | "+ vocList.get(vid.value).readingToString();
						break;
					case cnstSubFromReading:
						str = vocList.get(vid.value).readingToString();
						break;
					default:
						alert("ERROR(drawGUIMultiple): sub mode unknown!");
						return false;
				}
				node = document.createTextNode(str);
				choicesText[i].replaceChild(node, choicesText[i].firstChild );
				correctMultipleChoice 	= i+1;
				correctMultipleVocID	= vid.value;
			}
			else {
				//if (fakeChoices[j] == null) alert("fakeChoices problem: "+j+" length: "+fakeChoices.length+" true: "+trueSlot+" i:"+i);
				var str;
				switch (defOptSubMode) {
					case cnstSubFromTo:
					case cnstSubReadingTo:
					case cnstSubFromReadingTo:
						str = vocList.get(fakeChoices[j]).toToString();
						break;
					case cnstSubFromToReading:
						str = vocList.get(fakeChoices[j]).toToString()+" | "+ vocList.get(fakeChoices[j]).readingToString();
						break;
					case cnstSubFromReading:
						str = vocList.get(fakeChoices[j]).readingToString();
						break;
					default:
						alert("ERROR(drawGUIMultiple): sub mode unknown!");
						return false;
				}
				node = document.createTextNode(str);
				choicesText[i].replaceChild(node, choicesText[i].firstChild );
				j++;
			}
		}

		if (correctMultipleChoice < 0 || correctMultipleVocID < 0) {
			alert("ERROR(drawGUIMultiple): no correct option was selected!");
			return false;
		}

		var r = vocList.get(vid.value);
		

		switch (defOptSubMode) {
			case cnstSubFromTo:
			case cnstSubFromReading:
			case cnstSubFromToReading:
				chk.nodeValue = r.fromToString();
				break;
			case cnstSubFromReadingTo:
				chk.nodeValue = r.fromToString()+" | "+ r.readingToString();
				break;
			case cnstSubReadingTo:
				chk.nodeValue = r.readingToString();
				break;
			default:
				alert("ERROR(drawGUIMultiple): Yomi mode unknown!");
				return false;
		}

		choices[0].focus();
	}
}	



function drawGUIBuildTree(vocList) {

	
	// sort compare functions - only work when defined INSIDE the calling function
	function kanjiFreqCmp (a,b) {
		if (a[1]>b[1]) return -1;
		else if (a[1]<b[1]) return 1;
		else return 0;
	}
	
	function multipleOccCmp(a,b) {
		return -(a[1].length-b[1].length);
	}
	
	// variables
	var i,j, k, l, m;
	var cnt, vocCnt, eLen, kanjiCnt;
	var voc;
	var cur, c;	
	var kanji = new Array();
	var found;
	var out;
	
	debug("drawGUIBuildTree");

	cnt = vocList.getCount();
	for (i=0; i<cnt; i++) {
		voc	= vocList.get(i);
		vocCnt  = voc.getFromCount();
		for (j=0; j<vocCnt; j++) {
			cur = voc.getFrom(j);
			eLen = cur.length;
			//document.write(cur+" - ");
			for (k=0; k<eLen; k++) {
				c = cur.charAt(k);
				if (c>='\u4E00' && c<='\u9FAF') {
					// Kanji found
					//document.write(c);
					// check whether it is already included in the kanji array
					// TODO: faster search function -> binary search?
					
					kanjiCnt = kanji.length;					
					found = false;				
					kanji.push(c);					
					
				}
				else {
					//document.write("_");
				}
			}	
			//document.write(" "+kanji.length+":");
			/*for (i=0; i<kanji.length; i++) {
				document.write(kanji[i]);
			}
			*/
			//document.write(" <br />");
		}	
	}
	kanji.sort();
	/*
	for (i=0; i<kanji.length; i++) {
		document.write(kanji[i]);
	}
	*/
	var kanjiStats = new Array();
	var c = kanji[0];
	var cnt = 1;
	var len= kanji.length;
	for (i=1; i< len; i++) {
		var nC = kanji[i];
		if (nC != c) {
		   // new kanji		   
		   kanjiStats[kanjiStats.length] = new Array(c, cnt);
		   cnt = 1;
		   c = nC;
		}
		else {
		  cnt++;
		}
	}	
	kanjiStats[kanjiStats.length] = new Array(c, cnt);
	document.write("<br />");
	kanjiStats.sort(kanjiFreqCmp);
	
	var kanjiPrtOld="";
	for (i=0; i<kanjiStats.length; i++) {
		if (kanjiPrtOld != kanjiStats[i][0])
			document.write(kanjiPrtOld);
		kanjiPrtOld=kanjiStats[i][0];
	}
	
	if (kanjiPrtOld != kanjiStats[kanjiStats.length-1][0])
		document.write(kanjiStats[kanjiStats.length-1][0]);
	document.write("<br />");
	
	var oneOccIdx = -2;
	for (i=0; i<kanjiStats.length; i++) {
		var t = kanjiStats[i];
		if (t[1] == 1 && oneOccIdx < -1) oneOccIdx = i-1;		// 
	//	document.write("("+t[0]+","+t[1]+")");
	}
	//document.write("<br />");
	// create new array which keeps track of vocables already printed
	var trackVocs = new Array();
	cnt = vocList.getCount();
	for (i=0; i<cnt; i++) {
		trackVocs[i] = false;		// not yet printed
	}
	
	// create array which checks which vocables contain which kanji
	oneOccIdx++;
	var multipleVocsIdx = new Array();
	for (m=0; m<oneOccIdx; m++) {		
		multipleVocsIdx[m] = new Array();
		cur = kanjiStats[m][0];	// get current kanji
		multipleVocsIdx[m][0]=cur;
		multipleVocsIdx[m][1]=new Array();
		cnt = vocList.getCount();
		var leftOnes = kanjiStats[m][1];
		for (i=0; i<cnt; i++) {
			voc	= vocList.get(i);
			vocCnt  = voc.getFromCount();
			for (j=0; j<vocCnt; j++) {
				if (voc.getFrom(j).indexOf(cur)>-1) {
					// contains searched kanji -> store vocable index
					multipleVocsIdx[m][1][multipleVocsIdx[m][1].length] = i;
					leftOnes--;
				}
			}
			if (leftOnes == 0) break;	// all occurrences have been found
		}
	}
	// now remove entries which include the same vocables more than once
	cnt = multipleVocsIdx.length-1;
	var p, lastE;
	for (m=cnt; m>-1; m--) {
		cur = multipleVocsIdx[m][1];
		
		if (cur.length > 1) {
			cur.sort();
			lastE = cur[0];			
			for (p=1; p<cur.length; p++) {
				
				if (lastE == cur[p]) {
					var left = cur.slice(0, p-1);
					var right = cur.slice(p+1, cur.length-1);
					cur = left.concat(right);
					p--;
				
				}
				else {
					lastE=cur[p];
				}
			}
			
		}		
	}
	// sort for correct orders
	multipleVocsIdx.sort(multipleOccCmp);
	// remove entries with only one member, i.e. those that were added because they are double/triple/...-kanji entries	
	cnt = multipleVocsIdx.length-1;
	for (m=cnt; m>-1; m--) {
		if (multipleVocsIdx[m][1].length==1) {
			multipleVocsIdx[m] = null;
			oneOccIdx--;
		} 
		else break;
	}
	/*
	for (m=0; m<oneOccIdx; m++) {		
		document.write("<br>");
		cur = multipleVocsIdx[m];
		for (i=0; i<cur.length; i++) document.write(cur[i]+" ");
		
	}
	*/
	// now print the kanji groups
	document.write("<html><head><style type=\"text/css\">.cols {margin-left:3mm; margin-right:3mm;padding-left:2mm;padding-right:2mm;}; </style></head><body>");
	document.write("<table cellpadding=\"0\" cellspacing=\"0\" style=\"border-collapse:auto\">");
	// - start with the multiple ones
	for (i=0; i<oneOccIdx; i++) {
		cur = multipleVocsIdx[i][1];
		var curKanji = multipleVocsIdx[i][0];
		//document.write("<div style=\"border-bottom:1px solid black;margin-bottom:1mm;\">");		
		
		out = "<tr><td colspan=\"3\" class=\"cols\"><span style=\"color:red;font-size:1.2em;\">"+multipleVocsIdx[i][0]+"</span></td></tr>";
		document.write(out);
		/*
		document.write("<tr>");		
		document.write("<td colspan=\"3\">");
		document.write("<span style=\"color:red;font-size:1.2em;\">"+multipleVocsIdx[i][0]+"</span>");
		document.write("</td>");
		document.write("</tr>");		
		*/
		
		for (j=0; j<cur.length; j++) {
			voc = vocList.get(cur[j]);	
			var p;
			var out1="";
			var str= voc.fromToString();
			/*
			for (p=0; p<str.length; p++) {
				if (str.substring(p,1)==curKanji) {
					out1 +="<span style=\"text-decoration:underline;\">"+curKanji+"</span>";
				}
				else {
					out1 += str.substring(p,1);
				}
			}
			*/		
			out = "<tr><td class=\"cols\">"+ str+"</td><td class=\"cols\">"+voc.readingToString()+"</td><td class=\"cols\">"+voc.toToString()+"</td></tr>";
			document.write(out);
			/*
			document.write("<tr>");
			document.write("<td>");
			//document.write("<blockquote>");			
			//for (k=0; k<j; k++) document.write("<span style=\"color:white;\">矢</span>");
			document.write(voc.fromToString());
			//document.write("</blockquote>");
			document.write("</td>");
			document.write("<td>");
			document.write(""+voc.readingToString());
			document.write("</td>");
			document.write("<td>");
			document.write(""+voc.toToString());
			document.write("</td>");
			document.write("</tr>");
			*/
			trackVocs[cur[j]] = true;
		}
		//document.write("</div>");
		document.write("<tr><td colspan=\"3\" class=\"cols\"><hr /></td></tr>");
	}
	// - now all unique kanji containing vocables
	document.write("<tr style=\"margin-bottom:0.5em;\"><td colspan=\"3\" class=\"cols\">&nbsp;</td></tr>");
	cnt = vocList.getCount();
	for (i=0; i<cnt; i++) {
		voc = vocList.get(i);		
		if (!trackVocs[i]) {
			// this vocable has not yet been printed
			//document.write("<div style=\"border-bottom:1px solid black;margin-bottom:1mm;\">");	
			/*
			document.write("<tr><td colspan=\"3\"><hr /></td></tr>");
			document.write("<tr>");
			document.write("<td>");
			document.write(voc.fromToString());
			document.write("</td>");
			document.write("<td>");
			document.write(""+voc.readingToString());
			document.write("</td>");
			document.write("<td>");
			document.write(""+voc.toToString());
			document.write("</td>");
			document.write("</tr>");
			*/
			out ="";
			//out = "<tr><td colspan=\"3\" class=\"cols\"><hr /></td></tr>";
			out += "<tr><td class=\"cols\">"+voc.fromToString()+"</td><td class=\"cols\">"+voc.readingToString()+"</td><td class=\"cols\">"+voc.toToString()+"</td></tr>";
			document.write(out);
			//document.write("</div>");

		}
	}
	document.write("</table>");
	document.write("</body></html>");
}


function drawGUISearch(vocList) {

	debug("drawGUISearch");

	//var chk = window.document.InputForm.CheckField;
	var inpForm 	= window.document.getElementsByName('InputForm')[0];
	var inp 	= document.InputForm.InputField;
	var vocLeft 	= window.document.getElementsByName('vocLeft')[0].firstChild;


	vocLeft.nodeValue = " "+vocList.getCount()+" ";

	inp.value = "";
	inp.focus();
}


/******************************************************************
 * 								LoadLecture(s)
 ******************************************************************/
var vLoaded_xmlDoc = null;
var vLoaded_vocList = null;
var vLoaded_fileNameArray = null;
var vLoaded_fileName = null;
var vLoaded_par = null;
var vLoaded_mode = null;

function LoadLecture_loaded()
{
		statusPrint("still need to load "+(vLoaded_fileNameArray.length*1+1)+" lectures, currently loading "+ vLoaded_fileName);
		var lecs = getNode(vLoaded_xmlDoc, "lecture", true);
		statusPrint("still need to load "+(vLoaded_fileNameArray.length*1)+" lectures");
		
		loadLectureDone(lecs, vLoaded_vocList, vLoaded_fileNameArray, vLoaded_par, vLoaded_mode);		
}

function loadLecture(fileNameArray, vocList, mode) {

	debug("loadLecture");

	var raceEnded = false;
	var fileName = fileNameArray.shift();
	var xmlDoc = null;

	if (browser_ie && !browser_std) {
		xmlDoc = new ActiveXObject("Microsoft.XMLDOM");
	}
	else {
		xmlDoc= document.implementation.createDocument("","doc",null);
	}
	vLoaded_xmlDoc = xmlDoc;
	vLoaded_vocList = vocList;
	vLoaded_fileNameArray = fileNameArray;
	vLoaded_fileName = fileName;
	vLoaded_mode = mode;


	//statusPrint("trying to load lecture from "+fileName+", still "+fileNameArray.length+" left to load after that.");

	if (fileNameArray.length == 0) {
		vLoaded_par = true;
	}
	else {
		vLoaded_par = false;
	}

	function checkForRace() {
		if (raceEnded) {
			// nothing to do, everything OK (hopefully)
		}
		else {
			// try again
			statusPrint("trying to load again");
			window.setTimeout("checkForRace()", cnstRaceCheckTime);
			loadIt();
		}
	}
	function loadIt() {
		
		if (window.XMLHttpRequest) {			
			request = new XMLHttpRequest();
			request.open('GET', cnstLecturesPath+fileName, true);
			request.onreadystatechange = 
				function (aEvt) {
					if (request.readyState == 4) {  	
						if(request.status == 200 || !request.status || request.status == 304) {
							vLoaded_xmlDoc = request.responseXML;		
							LoadLecture_loaded();			
							}					
					else {
						alert("ERROR(loadLecture): couldn't load the lecture data file.");
						return false;
					}					
				}
			};
			if (!browser_konq) request.send(null); else request.send();
				
		}	
		else
		if (browser_ie && !browser_std) {
			function keep_going() {
				if (xmlDoc.readyState!=4) return false;
			}
			statusPrint("Loading lectures. Left: "+fileNameArray.length+" Current: "+ cnstLecturesPath+fileName+"...");
			xmlDoc.async="false";
 		        xmlDoc.validateOnParse="false";
			xmlDoc.onreadystatechange=keep_going;
			//alert("loading "+cnstLecturesPath+fileName);
			xmlDoc.load(cnstLecturesPath+fileName);

			LoadLecture_loaded();
		}
		else {
			xmlDoc.onload=LoadLecture_loaded;

			if (typeof xmlDoc!="undefined") {
				try {
					statusPrint("Loading lectures. Left: "+fileNameArray.length+" Current: "+ cnstLecturesPath+fileName+"...");
					xmlDoc.load(cnstLecturesPath+fileName);	
		
				}
				catch (objException) {
					alert("ERROR(loadLecture): couldn't load the lecture data file.");
					return false;
				}
			}
			else {
				alert("ERROR(loadLecture): cannot access XML processor - are you using the correct version of Mozilla?");
				return false;
			}
		}
		//return true;

	}
	//window.setTimeout("this.checkForRace()", cnstRaceCheckTime);
	loadIt();

	//return true;
}

function loadLectureDone(nodes, vocList, fileNameArray, done, mode) {

	debug("loadLectureDone");

	var i;
	var sel 	= 0;
	var tst 	= true;

	// normalize the node
	if (!browser_konq) {	// konqueror doesn't seem to like this
		var removed=normalizeNode(nodes);
		debug("removed "+removed+" nodes");		
	}

	//statusPrint("loading lecture data");

	while (tst != null) {
		tst = vocList.getVocablesNode(nodes, sel, true);

		if (tst != null) {
			// create new vocable
			var v = new vocable();
			
			// select the Japanese writing, the reading and the translation
			var vocCont 	= tst.getElementsByTagName("vocable");
			var yomi 	= tst.getElementsByTagName("general");
			var trans 	= tst.getElementsByTagName("entry");
			var grammar 	= tst.getElementsByTagName("grammar");
			var comment 	= tst.getElementsByTagName("comment");
			
			//alert("kanji: "+vocCont.length);

			// add entries to the vocable
			// the reading, comment and grammar always have the same array
			for (i=0; i<yomi.length; i++) {
				v.addReading (yomi[i].firstChild.nodeValue);
			}
			for (i=0; i<grammar.length; i++) {
				v.addGrammar (grammar[i].firstChild.nodeValue);
			}
			for (i=0; i<comment.length; i++) {
				v.addComment (comment[i].firstChild.nodeValue);
			}
			// depending on Questioning-Direction select which array to use
			for (i=0; i<vocCont.length; i++) {
				if (defOptCheckMode == cnstLangJapOther) {
					v.addFrom	(vocCont[i].firstChild.nodeValue);
				}
				else 
				if (defOptCheckMode == cnstLangOtherJap) {
					v.addTo		(vocCont[i].firstChild.nodeValue);
				}
				else {
					alert("ERROR (loadLectureDone): Unknown language selection!");
					return false;
				}
			}
			for (i=0; i<trans.length; i++) {
				if (defOptCheckMode == cnstLangJapOther) {
					v.addTo		(trans[i].firstChild.nodeValue);
				}
				else 
				if (defOptCheckMode == cnstLangOtherJap) {
					v.addFrom	(trans[i].firstChild.nodeValue);
				}
				else {
					alert("ERROR (loadLectureDone): Unknown language selection. Aborting!");
					return false;
				}
			}
			// add vocable to list	
			vocList.add(v);
			// next vocable
			sel++;
		}
	}
	if (done) { 
		if (defOptTrainerRemDupl == cnstTrainerRemDuplYes) {
			// remove duplicate entries
			vocList.removeDuplicates();
		}
		else if (defOptTrainerSort == cnstTrainerSortListYes) {
			// sort List
			vocList.sort();
		}
		switch (mode) {
			case cnstModeTrainCheck:
				if (defOptCheckShuffle == cnstGUITrainShuffleYes) {
					statusPrint("Lectures loaded. Shuffling vocables...");
					vocList = shuffleVocList(vocList);
				}
				statusPrint("Check mode entered.");
				return drawGUITrain(vocList);
				break;
			case cnstModeTrainMultiple:
				if (defOptMultipleShuffle == cnstGUIMultipleShuffleYes) {
					statusPrint("Lectures loaded. Shuffling vocables...");
					vocList = shuffleVocList(vocList);
				}
				statusPrint("Check mode entered.");
				return drawGUIMultiple(vocList);
				break;
			case cnstModeBuildTree:
				statusPrint("build kanji tree mode entered.");
				return drawGUIBuildTree(vocList);
				break;
			case cnstModeSearch:
				statusPrint("Search mode entered.");
				return drawGUISearch(vocList);
				break;
			default:
				alert("ERROR (loadLectureDone): Unknown mode. Aborting!");
				return false;
		}
	}
	else {
		loadLecture(fileNameArray, vocList, mode);
	}
}	    

var vLoadedLecs_xmlDoc = null;
var vLoadedLecs_lectures = null;
var vLoadedLecs_fileNameArray = null;
var vLoadedLecs_selLectures = null;
var vLoadedLecs_mode = null;
var vLoadedLecs_vocList = null;

function loadLectures_loaded() {
	var i,j;

	var raceEnded		= false;

	var xmlDoc =vLoadedLecs_xmlDoc;
	var lectures = vLoadedLecs_lectures;
	var fileNameArray  = vLoadedLecs_fileNameArray;
	var selLectures = vLoadedLecs_selLectures;
	var mode = vLoadedLecs_mode;
	var vocList = vLoadedLecs_vocList;

	var lects	 	= getNode(xmlDoc, "lectures");
	if (lects == null) {
		alert("Cannot find lectures! Lectures file corrupt? Aborting!");
		raceEnded = true;
		return false;
	}
	var lecs 		= lects.getElementsByTagName("lecture");
	if (lecs == null) {
		alert("Cannot find lectures! Any entered? Aborting!");
		raceEnded = true;
		return false;
	}
	for (i=0; i<lecs.length; i++) {
		var found = false;
		if (selLectures == null) {
			// no lectures selected, OK so
			found =true;
		}
		else if (selLectures.length == 0) {
			alert("No lectures selected!");
			raceEnded = true;
			window.location.href=cnstMainFile;
			return false;
		}
		else {
			for (j=0; j<selLectures.length; j++) {
				if (selLectures[j]*1 == i) {
					found = true;
					break;
				}
			}
		}
		if (found) {
			var curLecture	= lecs[i];
			var desc		= getNode(curLecture, "desc");
			var fileName	= getNode(curLecture, "file");

			if (desc == null || fileName == null) {
				alert("Lecture entry "+i+" seems corrupt! Aborting!");
				raceEnded = true;
				return false;
			}

			lectures[lectures.length] = new lecture(desc.firstChild.nodeValue, fileName.firstChild.nodeValue, i);
		}
	}

	// now load the appropriate subroutines
	var nD = new Date();
	vStartTime = nD.getTime();

	switch (mode) {
		case cnstModeTrainCheck:
			raceEnded = true;
			loadLecturesDoneTrainCheck(vocList, lectures);
			break;
		case cnstModeTrainGeneral:
			raceEnded = true;
			loadLecturesDoneTrainGeneral(vocList, lectures);
			break;
		case cnstModeTrainMultiple:
			raceEnded = true;
			loadLecturesDoneTrainMultiple(vocList, lectures);
			break;
		case cnstModeBuildTree:
			raceEnded = true;
			loadLecturesDoneBuildTree(vocList, lectures);
			break;
		case cnstModeSearch:
			raceEnded = true;
			loadLecturesDoneSearch(vocList, lectures);
			break;
		default:
			alert("ERROR(loadLectures): Unknown mode! Aborting");
			raceEnded = true;
			return false;			
	}
}

function loadLectures(vocList, selLectures, mode) {

	debug("loadLectures");

	var i;

	var raceEnded		= false;

	var fileNameArray 	= new Array();
	var lectures 		= new Array();
	var xmlDoc		= null;
	var request		= null;
	var fileName 		= cnstLecturesPath + "lectures.xml";

	vLoadedLecs_lectures 		= lectures;
	vLoadedLecs_fileNameArray 	= fileNameArray;
	vLoadedLecs_selLectures 	= selLectures;
	vLoadedLecs_mode 			= mode;
	vLoadedLecs_vocList 		= vocList;

	if (window.XMLHttpRequest) {		
		
		request = new XMLHttpRequest();		
		request.open('GET', fileName, true);
		request.onreadystatechange = 
			function (aEvt) {
				if (request.readyState == 4) {  	
					if(request.status == 200 || !request.status || request.status == 304) {
						vLoadedLecs_xmlDoc = request.responseXML;		
						loadLectures_loaded();			
					}
				
					else {
						alert("ERROR(loadLectures): E1: couldn't load the lecture data file.");
						return false;
					}					
				}
			};
		if (!browser_konq) request.send(null); else request.send();
			
	}	
	else if (browser_ie && !browser_std) {
		xmlDoc= new ActiveXObject("Microsoft.XMLDOM");
		vLoadedLecs_xmlDoc 			= xmlDoc;
		function keep_going() {
			if (xmlDoc.readyState!=4) return false;
		}
		xmlDoc.async="false";
	        xmlDoc.validateOnParse="false";
		xmlDoc.onreadystatechange=keep_going;
		xmlDoc.load(fileName);
		loadLectures_loaded();
	}
	else {
		xmlDoc= document.implementation.createDocument("","doc",null);
		vLoadedLecs_xmlDoc 			= xmlDoc;
		xmlDoc.onload				= loadLectures_loaded;
		if (typeof xmlDoc!="undefined") {
			try {
				xmlDoc.load(fileName);
			}
			catch (objException) {
				alert("ERROR(loadLectures): E2: couldn't load the lecture data file.");
				return false;
			}
			//return true;
		}
		else {
			alert("ERROR(loadLectures): cannot access XML processor - are you using the correct version of Mozilla?");
			return false;
		}
	}

	//return true;
}

// universal function to be called when a lecture is loaded
function loadLecturesDone_Universal(vocList, lectures, mode, modulename) {

	debug(modulename);

	var fileNameArray = new Array();
	var i;
	
	if (lectures == null) {
		alert("ERROR("+modulename+"): no lecture parameter passed. Program error? Aborting!");
		return false;
	}
	else
	if (lectures.length == 0) {
		alert("ERROR("+modulename+"): no lectures selected! Aborting!");
		return false;
	}
	for (i=0; i<lectures.length; i++) {
		fileNameArray[fileNameArray.length] = lectures[i].getFileName();
	
	}
	loadLecture(fileNameArray, vocList, mode);
	return true;

}
// we're in the trainCheck mode - load the selected lecture files
function loadLecturesDoneTrainCheck(vocList, lectures) {
	
	loadLecturesDone_Universal(vocList, lectures, cnstModeTrainCheck, "loadLecturesDoneTrainCheck");
	return true;
}

function loadLecturesDoneTrainMultiple(vocList, lectures) {
	loadLecturesDone_Universal(vocList, lectures, cnstModeTrainMultiple, "loadLecturesDoneTrainMultiple");
	return true;
	
	
}

function loadLecturesDoneBuildTree(vocList, lectures) {

	loadLecturesDone_Universal(vocList, lectures, cnstModeBuildTree, "loadLecturesDoneBuildTree");
	return true;
	
	
}

function loadLecturesDoneSearch(vocList, lectures) {
	
	loadLecturesDone_Universal(vocList, lectures, cnstModeSearch, "loadLecturesDoneSearch");
	return true;
}

function loadLecturesDoneTrainGeneral(vocList, lectures) {

	debug("loadLecturesDoneTrainGeneral");

	var i;
	var str 	= "";
	var docNode = document.getElementById("lectureListBody");
	if (docNode == null) {
		alert("Cannot create lecture list. Might be a synchronization problem - if refreshing does not help, it might be a bug! Aborting!");
		return false;
	}
	for (i=0; i<lectures.length; i++) {
		var curLect 	= lectures[i];
		// new, table based mode
		var line		= document.createElement("tr");
		var attrl		= document.createAttribute("class");
		if (i % 2 == 0) {
			attrl.nodeValue	= "lectureListLineEven";
		}
		else {
			attrl.nodeValue	= "lectureListLineOdd";
		}
		line.setAttributeNode(attrl);

		var column1		= document.createElement("td");
		var column2		= document.createElement("td");
		var column3		= document.createElement("td");
		var attrc1		= document.createAttribute("class");
		var attrc2		= document.createAttribute("class");
		var attrc3		= document.createAttribute("class");
		attrc1.nodeValue	= "lectureListColumn";
		attrc2.nodeValue	= "lectureListColumn";
		attrc3.nodeValue	= "lectureListColumn";
		column1.setAttributeNode(attrc1);
		column2.setAttributeNode(attrc2);
		column3.setAttributeNode(attrc3);


		var elNode		= document.createElement("input");

		var attr1		= document.createAttribute("type");
		attr1.nodeValue	= "checkbox";
		elNode.setAttributeNode(attr1);
		var attr2		= document.createAttribute("value");
		attr2.nodeValue	= i;
		elNode.setAttributeNode(attr2);
		var attr3		= document.createAttribute("name");
		attr3.nodeValue	= "s";
		elNode.setAttributeNode(attr3);

		var lnkNode		= document.createElement("a");

		attr1			= document.createAttribute("href");
		attr1.nodeValue	= cnstLecturesPath+curLect.getFileName();
		lnkNode.setAttributeNode(attr1);
		
		lnkNode.appendChild(document.createTextNode("view"));

		var contNode 	= document.createTextNode(curLect.getDesc());

		// update list
		column1.appendChild(elNode);
		column2.appendChild(contNode);
		column3.appendChild(lnkNode);
		line.appendChild(column1);
		line.appendChild(column2);
		line.appendChild(column3);
		//alert("line: "+line+" - "+line.hasChildNodes());
		//alert("docNode: "+docNode+" - "+docNode.hasChildNodes());

		docNode.appendChild(line);
	}

}


/******************************************************************
 * 								KeyHandlers etc.
 ******************************************************************/

function keyHandlerCheckTrain(ev) {
	if (browser_ie && !browser_std) e=window.event; else e=ev;
	Key = e.keyCode;
	
    if (Key == 13) {
    	checkTrain();
        return false;
  	}
	else
	
	if ((e.charCode==114 || e.charCode==82) && e.ctrlKey) {
		var Check = confirm("Do you really want to refresh? Your current results will be lost!");
		if (Check == true) {
			location.reload();
			return true;
		}
		return false;
	}
	return true;
}

function keyHandlerCheckMultiple(ev) {
	if (browser_ie && !browser_std) e=window.event; else e=ev;
	Key = e.keyCode;
	if ((e.charCode==114 || e.charCode==82) && e.ctrlKey) {
		var Check = confirm("Do you really want to refresh? Your current results will be lost!");
		if (Check == true) {
			location.reload();
			return true;
		}
		return false;
	}
	return true;
}

function keyHandlerBuildTree(ev) {
	if (browser_ie && !browser_std) e=window.event; else e=ev;
	Key = e.keyCode;
	return true;
}

function keyHandlerSearch(ev) {
	if (browser_ie && !browser_std) e=window.event; else e=ev;
	Key = e.keyCode;
	if ((e.charCode==114 || e.charCode==82) && e.ctrlKey) {
		var Check = confirm("Do you really want to refresh? Your current results will be lost!");
		if (Check == true) {
			location.reload();
			return true;
		}
		return false;
	}
	return true;
}

function menuChangeType() {
	var fldType	= window.document.MenuForm.type;	
	var fldRemDups	= window.document.MenuForm.removeDups;	
	var fldMode	= window.document.MenuForm.mode;	
	var fldShuffle	= window.document.MenuForm.shuffle;	
	
	switch (fldType.value*1) {	
		case cnstModeMenuCheck:
		case cnstModeMenuMultiple:
			fldRemDups.value	= 0;
			fldShuffle.value	= 0;	
			break;
		case cnstModeMenuBuildTree:
			fldMode.value		= 10;
			fldRemDups.value	= 0;
			fldShuffle.value	= 1;			
			break;
		case cnstModeMenuSearch:
			fldRemDups.value	= 1;
			fldShuffle.value	= 1;	
			break;		
		default:
			alert("ERROR(menuChangeType): unknown type!");
			return false;
	}
	return true;
}
/******************************************************************
 * 								init functions
 ******************************************************************/

function initTrain(mode) {

	debug("initTrain");

	var i;
	var selLectures = new Array();
	
	var paramLine = location.search;
	if (mode == cnstModeTrainGeneral) {
		var alllecs = window.document.getElementsByName("ALLLECS");
		if (alllecs.length > 0) alllecs[0].checked=false;
		if (paramLine.length == 0) {
			// standard mode, print option list
			loadLectures(vocList, null, cnstModeTrainGeneral);
			return true;
		}
		else {
			// we have to redirect to the appropriate file now
			var parLine = paramLine.substring(1, paramLine.length);
			var parArr = parLine.split("&");
			var newAddr = "";
			for (i=0; i<parArr.length; i++) {
				var options = parArr[i].split("=");
				var opt = options[0];
				var val = options[1]*1;
				if (opt == "type") {				
					if (val == cnstModeMenuCheck)
						newAddr = "checkTrain.html";
					else if (val == cnstModeMenuMultiple)
						newAddr = "checkMultiple.html";
					else if (val == cnstModeMenuBuildTree)
						newAddr = "buildKanjiTree.html";
					else
						newAddr = "search.html";					
					return window.location.href = newAddr + paramLine;
				}
			}
			// didn't find it
			alert("ERROR(initTrain): could not find the appropriate submode!");
			return false;
		}
	}

	if (paramLine.length > 0) {
		// params given
		var parLine = paramLine.substring(1, paramLine.length);
		var parArr = parLine.split("&");

		for (i=0; i<parArr.length; i++) {
			var options = parArr[i].split("=");
			var opt = options[0];
			var val = options[1]*1;
			switch (opt) {
				case "mode":
					if (val >= 10) {
						defOptSubMode = val % 10;
						val = Math.floor(val / 10);
					}
					switch (val) {
						case cnstLangJapOther:
							defOptCheckMode = cnstLangJapOther;
							break;
						case cnstLangOtherJap:
							defOptCheckMode = cnstLangOtherJap;
							break;
						default:
							alert("Train mode unknown. Proceeding with defaults.");
							break;
					}
					break;
				case "yomi":
					switch (val) {
						case cnstTrainCheckYomiHide:
							defOptCheckShowYomi = cnstTrainCheckYomiHide;
							break;
						case cnstTrainCheckYomiShow:
							defOptCheckShowYomi = cnstTrainCheckYomiShow;
							break;
						default:
							alert("Yomi mode unknown. Proceeding with defaults.");
							break;
					}
					break;
				case "s":

					selLectures[selLectures.length] = val;
					break;
				case "shuffle":
     					if (val == 0) {
						defOptCheckShuffle = true;
						defOptMultipleShuffle = true;
					}
					else {
						defOptCheckShuffle = false;
						defOptMultipleShuffle = false;
					}

					break;
				case "removeDups":
     					if (val == 0) {
						defOptTrainerRemDupl = true;
					}
					else {
						defOptTrainerRemDupl = false;
					}				
					break;
				case "type":
					// ignore this one
					break;
				case "ALLLECS":
					// ignore this one
					break;
				case "InputField":
					// ignore this one
					break;
				case "SearchMode":
					// ignore this one
					break;
				case "CompMode":
					// ignore this one
					break;
				case "CaseMode":
					// ignore this one
					break;
				case "sm":
					defOptSubMode = val;
			break;
				default:
					alert("ERROR(initTrain): Unknown mode "+opt+"! Proceeding.");
					break;
			}
		}

	}
	
	switch (mode) {
		case cnstModeTrainCheck:
			document.onkeypress = keyHandlerCheckTrain;
			break;
		case cnstModeTrainMultiple:
			document.onkeypress = keyHandlerCheckMultiple;
			break;
		case cnstModeBuildTree:
			document.onkeypress = keyHandlerBuildTree;
			break;
		case cnstModeSearch:
			document.onkeypress = keyHandlerSearch;
			break;
		default:
			alert("ERROR(initTrain): Unknown mode "+ mode+"! Don't know what to do now. Aborting!");
			return false;
			break;
		
	}
	loadLectures(vocList, selLectures, mode);
	


	return true;
}


function initTrainGeneral() {

	debug("initTrainGeneral");

	return initTrain(cnstModeTrainGeneral);
}

function initTrainCheck() {

	debug("initTrainCheck");

	return initTrain(cnstModeTrainCheck);
}

function initTrainMultiple() {

	debug("initTrainMultiple");

	return initTrain(cnstModeTrainMultiple);
}

function initBuildTree() {

	debug("initBuildTree()");
	var result = initTrain(cnstModeBuildTree);
	return result;
}

function initSearch() {

	debug("initSearch");

	return initTrain(cnstModeSearch);
}

/******************************************************************
 * 						check functions and their handlers
 ******************************************************************/

function checkTrainHandler(event) {	

	debug("checkTrainHandler");

	checkTrain();
	return false;
}

function checkSearchHandler(event) {	

	debug("checkSearchHandler");
	search();
	return false;
	
}

function checkTrain(special) {

	debug("checkTrain");
	if (special != 'skip') {	// no skipping?
		var correct 		= removeWhiteSpaces(window.document.getElementsByName('From')[0].firstChild.nodeValue);
		var entered 		= removeWhiteSpaces(window.document.InputForm.InputField.value);
		var IDObj			= window.document.InputForm.VocID;
		var ID				= IDObj.value;
		var voc 			= vocList.get(ID);

		//var checkOpt 		= maskTrainGetCheckOptions();
		var checkFrom 		= voc.compFrom		(entered, true);
		var checkTo 		= voc.compTo		(entered, true);
		var checkReading 	= voc.compReading	(entered, false);

		var correct 		= false;
		switch (defOptSubMode) {
			case cnstSubFromReading:
				if (checkReading)
					correct = true;
				else
					correct = false;
				break;
			case cnstSubFromTo:
			case cnstSubFromReadingTo:
			case cnstSubReadingTo:

				if ( checkTo)
					correct = true;
				else
					correct = false;
				break;
			case cnstSubFromToReading:
				if (checkReading || checkTo)
					correct = true;
				else
					correct = false;
				break;

			default:
				alert("ERROR (checkTrain): I don't know what to check :-( stopping");
				return;
				break;
		}
		if (correct) {
			voc.setChecked(true);
			voc.setTestRightCount(voc.getTestRightCount()+1);
			statusPrint("Last was CORRECT: "+voc.fromToString()+" ("+voc.readingToString()+") "+voc.toToString());
		}
		else {
			var corrStr, corrStr1, corrStr2, corrStr3;
			voc.setTestWrongCount(voc.getTestWrongCount()+1);

			corrStr1 = voc.fromToString(true);
			corrStr2 = voc.readingToString(true);
			corrStr3 = voc.toToString(true);

			corrStr  = "[From]\n"+corrStr1;
			corrStr += "\n[Reading]\n" + corrStr2;
			corrStr += "\n[To]\n" + corrStr3;

			alert("Wrong! Correct answer:\n"+corrStr);
			statusPrint("Last was WRONG, correct: "+voc.fromToString()+" ("+voc.readingToString()+") "+voc.toToString());

		}
	}

	if (vocList.getUncheckedCount() > 0) {
		// still some left
		drawGUITrain(vocList);
	}
	else {
		statTrain(true);
	}
	
}

function checkMultiple(id) {

	debug("checkMultiple");

	if (id != 'skip') { 			// no skip?
		var voc 		= vocList.get(correctMultipleVocID);
		var correct		= id == correctMultipleChoice;
		//var checkOpt 		= maskTrainGetCheckOptions();

		//alert("selected answer "+ id+" is compared to "+correctMultipleChoice+" "+correct+" ++ "+voc);


		if (correct) {
			voc.setChecked(true);
			voc.setTestRightCount(voc.getTestRightCount()+1);
			statusPrint("Last was CORRECT: "+voc.fromToString()+" ("+voc.readingToString()+") "+voc.toToString());
		}
		else {
			var corrStr, corrStr1, corrStr2, corrStr3;
			voc.setTestWrongCount(voc.getTestWrongCount()+1);
			/*
			if (defOptSubMode == cnstSubFromReading) {
				corrStr = voc.readingToString();
			}
			else
			if ( defOptSubMode == cnstSubFromReadingTo || defOptSubMode == cnstSubReadingTo) {
					corrStr = voc.toToString();
			}
			else
			if (defOptSubMode == cnstSubFromTo || defOptSubMode == cnstSubFromToReading) {
				corrStr = voc.readingToString();
				corrStr += "\n" + voc.toToString();
			}
			else {
				alert(defOptSubMode);
				alert("ERROR (checkTrain): I don't know what to check :-( stopping");
				return;
			}
			*/
			corrStr1 = voc.fromToString(true);
			corrStr2 = voc.readingToString(true);
			corrStr3 = voc.toToString(true);

			corrStr  = "[From]\n"+corrStr1;
			corrStr += "\n[Reading]\n" + corrStr2;
			corrStr += "\n[To]\n" + corrStr3;


			alert("Wrong! Correct answer:\n"+corrStr);
			statusPrint("Last was WRONG, correct: "+voc.fromToString()+" ("+voc.readingToString()+") "+voc.toToString());
		}
	}

	if (vocList.getUncheckedCount() > 0) {
		// still some left
		drawGUIMultiple(vocList);
	}
	else {
		statTrain(true);
	}
	

}


function searchAddVocToResults(vocID) {
	var docNode = document.getElementById("matchListBody");
	if (docNode == null) {
		// create the tbody element
		docNode = document.createElement("tbody");
		ttopnode = document.getElementById("matchList");
		ttopnode.appendChild(docNode);
		//alert("ttopnode:"+ttopnode+" docnode:"+docNode);
		//alert("Cannot create results list. Might be a synchronization problem - if refreshing does not help, it might be a bug! Aborting!");
		//return false;
	}
	var voc = vocList.get(vocID);
	// new, table based mode
	var line		= document.createElement("tr");
	var attrl		= document.createAttribute("class");
	// FIXME!!!
	if (1 % 2 == 0) {
		attrl.nodeValue	= "matchListLineEven";
	}
	else {
		attrl.nodeValue	= "matchListLineOdd";
	}
	line.setAttributeNode(attrl);

	var column1		= document.createElement("td");
	var column2		= document.createElement("td");
	var column3		= document.createElement("td");
	var column4		= document.createElement("td");
	var attrc1		= document.createAttribute("class");
	var attrc2		= document.createAttribute("class");
	var attrc3		= document.createAttribute("class");
	var attrc4		= document.createAttribute("class");
	attrc1.nodeValue	= "matchListColumn";
	attrc2.nodeValue	= "matchListColumn";
	attrc3.nodeValue	= "matchListColumn";
	attrc4.nodeValue	= "matchListColumn";
	column1.setAttributeNode(attrc1);
	column2.setAttributeNode(attrc2);
	column3.setAttributeNode(attrc3);
	column4.setAttributeNode(attrc4);

	var attrnd		= document.createAttribute("class");
	attrnd.nodeValue	= "matchListBody";
	docNode.setAttributeNode(attrnd);

	var txtForeign = document.createTextNode(voc.fromToString());
	var txtReading = document.createTextNode(voc.readingToString());
	var txtNative = document.createTextNode(voc.toToString());
	var txtGrammar = document.createTextNode(voc.grammarToString());

			
	// update list
	column1.appendChild(txtForeign);
	column2.appendChild(txtReading);
	column3.appendChild(txtNative);
	column4.appendChild(txtGrammar);
	line.appendChild(column1);
	line.appendChild(column2);
	line.appendChild(column3);
	line.appendChild(column4);
	docNode.appendChild(line);

	//docNode.style.border.value="solid 2px black";
}

function search(special) {

	debug("search");
	var voc,i,j,cur;
	var entered 		= removeWhiteSpaces(window.document.InputForm.InputField.value);
	var searchMode		= window.document.InputForm.SearchMode.value*1;
	var compMode		= window.document.InputForm.CompMode.value*1;
	var caseMode		= (window.document.InputForm.CaseMode.value*1 == 1);
	
	// search for it
	var vocCount = vocList.getCount();
	for (i=0; i<vocCount; i++) {
		voc = vocList.get(i);
		switch (searchMode) {
			case cnstGUISearchSearchForeign:
					if (voc.compFrom(entered, caseMode, compMode)) {
						// found it
						searchAddVocToResults(i);
					}
					
				break;
			case cnstGUISearchSearchReading:
					if (voc.compReading(entered, caseMode, compMode)) {
						// found it
						searchAddVocToResults(i);
					}
					
				break;
			case cnstGUISearchSearchNative:
					if (voc.compTo(entered, caseMode, compMode)) {
						// found it
						searchAddVocToResults(i);
					}
					
				break;
			case cnstGUISearchSearchGrammar:
					if (voc.compGrammar(entered, caseMode, compMode)) {
						// found it
						searchAddVocToResults(i);
					}
					
				break;
			case cnstGUISearchSearchComment:
					if (voc.compComment(entered, caseMode, compMode)) {
						// found it
						searchAddVocToResults(i);
					}
					
				break;
			default:
				alert("ERROR (search): unknown search mode");
				return;
				break;
		}
	}
	return true;	
}
function searchClear() {
	// remove added lines
	var docNode = document.getElementById("matchList");
	while (docNode.hasChildNodes()) docNode.removeChild(docNode.firstChild);
	
	// remove and recreate table - this is necessary since there are some display
	// errors if the list is longer then the window (extra blank lines remain and
	// add up)
	docNode = document.getElementById("matchListParent");
	while (docNode.hasChildNodes()) docNode.removeChild(docNode.firstChild);
	
	var table		= document.createElement("table");
	var attr		= document.createAttribute("class");
	var ID			= document.createAttribute("id");
	attr.nodeValue		= "matchList";
	ID.nodeValue		= "matchList";
	table.setAttributeNode(attr);
	table.setAttributeNode(ID);
	docNode.appendChild(table);
}
