//////////////////////////////////////////////////////////////
//															//
//	© 2005 - 2007 Vereyon									//
//  http://www.vereyon.nl									//
//	All rights reserved										//
//															//
//////////////////////////////////////////////////////////////

Wordlist.prototype.__type = "Wordlist";
Wordlist.prototype.wordlistId = null;
Wordlist.prototype.wordId = null;
Wordlist.prototype.wordClientId = null;
Wordlist.prototype.priTranslation = null;
Wordlist.prototype.secTranslation = null;
Wordlist.prototype.priRemark = null;
Wordlist.prototype.secRemark = null;
Wordlist.prototype.priLanguage = null;
Wordlist.prototype.secLanguage = null;
Wordlist.prototype.iLastIndex = -1;
Wordlist.prototype.iLastClientId = -1;
Wordlist.prototype.strName = null;
Wordlist.prototype.strDescription = null;
Wordlist.prototype.bChangesSaved = true;
Wordlist.prototype.bCanOverwrite = false;
Wordlist.prototype.hEditor = null;

Wordlist.prototype.transactions = null;
Wordlist.prototype.iTransactionCursor = -1;
Wordlist.prototype.iLogLength = 100;
Wordlist.prototype.bLogging = true;
Wordlist.prototype.OnLog = null;
Wordlist.prototype.OnLoad = null;
Wordlist.prototype.OnSave = null;

Wordlist.prototype.ajaxRequest = null;


function Wordlist() {

	this.Initialize = function Wordlist$Initialize () {
		
		this.wordlistId = -1;
		
		this.wordId = new Array();
		this.wordClientId = new Array();
		this.priTranslation = new Array();
		this.secTranslation = new Array();
		this.priRemark = new Array();
		this.secRemark = new Array();
		
		this.priLanguage = "";
		this.secLanguage = "";
		
		this.iLastIndex = -1;
		this.iLastClientId = -1;
		
		this.strName = "Nieuwe woordenlijst";
		this.strDescription = "";
		
		this.transactions = new Array();
		this.iTransactionCursor = -1;
		
		this.bCanOverwrite = false;
		
		this.OnLog = new Delegate();
		this.OnLoad = new Delegate();
		this.OnSave = new Delegate();
	}
	
	this.LoadFromXml = function Wordlist$LoadFromXml (url) {
	
		// Delete current wordlist
		this.Clear();
		
		// Fetch wordlist from server
		this.ajaxRequest = new Net.Request(url, "GET", null, true);
		this.ajaxRequest.OnComplete.Suscribe(this.LoadFromXmlCallback, this);
		this.ajaxRequest.MakeRequest();
	}
	
	this.LoadFromXmlCallback = function Wordlist$LoadFromXmlCallback () {
	
		// Declare variables
		var xmlHeaderNode, xmlHeaderNodes, xmlHeaderSubNode;
		var xmlWordDataNode, xmlWordDataNodes, xmlWordNode, xmlWordSubNode;
		var iNewWordIndex;
		var strRights;
		
		// Clear the wordlist
		this.Clear();
		this.SuspendLogging();
		
		if(this.ajaxRequest.ExtendedResult) {
		
			//alert(this.ajaxRequest.responseText);
			
			// Process header
			xmlHeaderNodes = this.ajaxRequest.ExtendedResult.getElementsByTagName("header");
			if(xmlHeaderNodes.length > 0) {
				xmlHeaderNode = xmlHeaderNodes[0];
				
				// Iterate trough header child nodes
				for(i = 0; i < xmlHeaderNode.childNodes.length; i++) {
					xmlHeaderSubNode = xmlHeaderNode.childNodes[i];
					
					switch(xmlHeaderSubNode.nodeName) {
						case "listId":
							if(xmlHeaderSubNode.hasChildNodes()) {
								this.wordlistId = xmlHeaderSubNode.firstChild.nodeValue;
							}
							break;
						case "listName":
							if(xmlHeaderSubNode.hasChildNodes()) {
								this.strName = xmlHeaderSubNode.firstChild.nodeValue;
							}
							break;
						case "listDescription":
							if(xmlHeaderSubNode.hasChildNodes()) {
								this.strDescription = xmlHeaderSubNode.firstChild.nodeValue;
							}
							break;
						case "primaryLanguage":
							if(xmlHeaderSubNode.hasChildNodes()) {
								this.priLanguage = xmlHeaderSubNode.firstChild.nodeValue;
							}
							break;
						case "secondaryLanguage":
							if(xmlHeaderSubNode.hasChildNodes()) {
								this.secLanguage = xmlHeaderSubNode.firstChild.nodeValue;
							}
							break;
						case "userRights":
							if(xmlHeaderSubNode.getAttribute("write") == "1") {
								this.bCanOverwrite = true;
							} else {
								this.bCanOverwrite = false;
							}
							break;
					}
				}
			}
			
			// Process word data
			xmlWordDataNodes = this.ajaxRequest.ExtendedResult.getElementsByTagName("wordlistData");
			if(xmlWordDataNodes.length > 0) {
				xmlWordDataNode = xmlWordDataNodes[0];
				
				// Iterate trough word nodes
				for(i = 0; i < xmlWordDataNode.childNodes.length; i++) {
					xmlWordNode = xmlWordDataNode.childNodes[i];
					
					// Skip non element nodes
					if(xmlWordNode.nodeType != 1) continue;
					
					// Create new word
					iNewWordIndex = this.AppendNewWord();
					
					// Iterate trough word node child nodesand store word values
					for(x = 0; x < xmlWordNode.childNodes.length; x++) {
						xmlWordSubNode = xmlWordNode.childNodes[x];
						
						switch(xmlWordSubNode.nodeName) {
							case "id":
								if(xmlWordSubNode.hasChildNodes()) {
									this.SetValue(iNewWordIndex, 0, xmlWordSubNode.firstChild.nodeValue);
								}
								break;
							case "primaryTranslation":
								if(xmlWordSubNode.hasChildNodes()) {
									this.SetValue(iNewWordIndex, 1, xmlWordSubNode.firstChild.nodeValue);
								}
								break;
							case "secondaryTranslation":
								if(xmlWordSubNode.hasChildNodes()) {
									this.SetValue(iNewWordIndex, 2, xmlWordSubNode.firstChild.nodeValue);
								}
								break;
							case "primaryRemark":
								if(xmlWordSubNode.hasChildNodes()) {
									this.SetValue(iNewWordIndex, 3, xmlWordSubNode.firstChild.nodeValue);
								}
								break;
							case "secondaryRemark":
								if(xmlWordSubNode.hasChildNodes()) {
									this.SetValue(iNewWordIndex, 4, xmlWordSubNode.firstChild.nodeValue);
								}
								break;
						}
					}
				}
			}
		}
		// Append a new empty word to the end of th list
		// Resume transaction logging and call the OnLoadCallback function
		this.AppendNewWord();
		this.bChangesSaved = true;
		this.ResumeLogging();
		this.OnLoad.Invoke();
	}
	
	this.SaveAsToXml = function Wordlist$SaveAsToXml (url) {
		
		// Scramble wordlist id
		this.wordlistId = -1;
		
		// Scramble server side word id's
		for(var i = 0; i < this.wordId.length; i++)
			this.wordId[i] = "new";
			
		this.SaveToXml(url);
	}
	
	this.SaveToXml = function Wordlist$SaveToXml (url) {
	
		// Declare variables
		var xmlDoc;
		var hHeaderNode, newNode, hDataNode, newWordNode, newId;
		var strSerialized;
		
		// Create xml document
		xmlDoc = new Xml.Document("wordlist");
		
		// Create header
		hHeaderNode = xmlDoc.createElement("header");
		xmlDoc.documentElement.appendChild(hHeaderNode);
		
			newNode = xmlDoc.createElement("listId");
			newNode.appendChild(xmlDoc.createTextNode(this.wordlistId));
			hHeaderNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("listName");
			newNode.appendChild(xmlDoc.createTextNode(this.strName));
			hHeaderNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("listAuthor");
			newNode.appendChild(xmlDoc.createTextNode("Vocabulum Online Overhoren"));
			hHeaderNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("listDescription");
			newNode.appendChild(xmlDoc.createTextNode(this.strDescription));
			hHeaderNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("primaryLanguage");
			newNode.appendChild(xmlDoc.createTextNode(this.priLanguage));
			hHeaderNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("secondaryLanguage");
			newNode.appendChild(xmlDoc.createTextNode(this.secLanguage));
			hHeaderNode.appendChild(newNode);
		
		// Create data segment
		hDataNode = xmlDoc.createElement("wordlistData");
		xmlDoc.documentElement.appendChild(hDataNode);
		
		for(i = 0; i < this.wordId.length; i++) {
			
			newWordNode = xmlDoc.createElement("word");
			
			newNode = xmlDoc.createElement("id");
			newNode.appendChild(xmlDoc.createTextNode(this.wordId[i]));
			newWordNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("clientId");
			newNode.appendChild(xmlDoc.createTextNode(this.wordClientId[i]));
			newWordNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("primaryTranslation");
			newNode.appendChild(xmlDoc.createTextNode(this.priTranslation[i]));
			newWordNode.appendChild(newNode);
			//alert(this.priTranslation[i]);
			
			newNode = xmlDoc.createElement("secondaryTranslation");
			newNode.appendChild(xmlDoc.createTextNode(this.secTranslation[i]));
			newWordNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("primaryRemark");
			newNode.appendChild(xmlDoc.createTextNode(this.priRemark[i]));
			newWordNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("secondaryRemark");
			newNode.appendChild(xmlDoc.createTextNode(this.secRemark[i]));
			newWordNode.appendChild(newNode);
			
			hDataNode.appendChild(newWordNode);
		}
		
		// Serialize wordlist
		strSerialized = Xml.Serialize(xmlDoc);
		strSerialized = strSerialized.replace(/&/g, escape("&"));
		
		// Send wordlist to server for storage
		var _this = this;
		this.ajaxRequest = getXmlHttpRequest();
		this.ajaxRequest.open("POST", url, true);
		this.ajaxRequest.onreadystatechange = function() { _this.SaveToXmlCallback() };
		this.ajaxRequest.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=utf-8");
		this.ajaxRequest.setRequestHeader("Content-length", strSerialized.length);
		this.ajaxRequest.setRequestHeader("Connection", "close");
		this.ajaxRequest.send("wordlist=" + strSerialized);
		
		// Make callback
		this.OnSave.Invoke();
		
		// Dirty firefox xmlhttprequest post bug workaround
		if(navigator.userAgent.indexOf("Firefox") != -1)
			alert(TEXT['Wordlist.Saved']);
	}
	
	this.SaveToXmlCallback = function Wordlist$SaveToXmlCallback () {
	
		// Declare variables
		var xmlAssignedIdsNodes, xmlAssignedIdsNode;
		var xmlAssignedIdNode, xmlSubNode;
		var iNewWordIndex;
	
		if(this.ajaxRequest.readyState == 4) {
			
			// Parse response
			if(this.ajaxRequest.responseXML) {
				
				// Process assigned word id's
				xmlAssignedIdsNodes = this.ajaxRequest.responseXML.getElementsByTagName("assignedIds");
				if(xmlAssignedIdsNodes.length > 0) {
					xmlAssignedIdsNode = xmlAssignedIdsNodes[0];
					
					// Update wordlist id
					this.wordlistId = parseInt(xmlAssignedIdsNode.getAttribute("wordListId"));
					
					// Iterate trough assignedId nodes
					for(i = 0; i < xmlAssignedIdsNode.childNodes.length; i++) {
						xmlAssignedIdNode = xmlAssignedIdsNode.childNodes[i];
						
						// Skip non element nodes
						if(xmlAssignedIdNode.nodeType != 1) continue;
						
						// Zero variables
						iClientId = null;
						iWordId = null;
						
						// Parse assignedId child nodes
						for(x = 0; x < xmlAssignedIdNode.childNodes.length; x++) {
							xmlSubNode = xmlAssignedIdNode.childNodes[x];
							
							// Skip non element nodes
							if(xmlSubNode.nodeType != 1) continue;
							
							switch(xmlSubNode.nodeName) {
								case "clientId":
									if(xmlSubNode.hasChildNodes()) {
										iClientId = parseInt(xmlSubNode.firstChild.nodeValue);
									}
									break;
								case "wordId":
									if(xmlSubNode.hasChildNodes()) {
										iWordId = parseInt(xmlSubNode.firstChild.nodeValue);
									}
									break;
							}
						}
						
						// Check if result is valid
						if(iClientId != null && iWordId != null) {
						
							// Commit new wordId to wordlist word
							for(x = 0; x < this.wordClientId.length; x++) {
								if(this.wordClientId[x] == iClientId) {
									this.wordId[x] = iWordId;
									break;
								}
							}
						}
					}
				}
			}
			
			// Wordlist has been saved
			this.bChangesSaved = true;
			
			// Make callback
			this.OnSave.Invoke(TEXT['Wordlist.Saved']);
		}
	}
	
	this.GenerateWrd2Xml = function Wordlist$GenerateWrd2Xml () {
	
		// Declare variables
		var xmlDoc;
		var hHeaderNode, newNode, hDataNode, newWordNode, newId, newAttribute;
		var oPi;
		
		// Create xml document
		xmlDoc = new Xml.Document("wordlist");
		
		// Add processing instruction
		oPi = xmlDoc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
		xmlDoc.insertBefore(oPi, xmlDoc.firstChild);
		
		// Create header
		hHeaderNode = xmlDoc.createElement("header");
		xmlDoc.documentElement.appendChild(hHeaderNode);
		
			
			newNode = xmlDoc.createElement("listName");
			newNode.appendChild(xmlDoc.createTextNode(this.strName));
			hHeaderNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("listAuthor");
			newNode.appendChild(xmlDoc.createTextNode("Vocabulum Online Overhoren"));
			hHeaderNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("listRemark");
			newNode.appendChild(xmlDoc.createTextNode(this.strDescription));
			hHeaderNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("primaryLanguage");
			newNode.appendChild(xmlDoc.createTextNode(this.priLanguage));
			hHeaderNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("secondaryLanguage");
			newNode.appendChild(xmlDoc.createTextNode(this.secLanguage));
			hHeaderNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("listSchemeVersion");
			newNode.appendChild(xmlDoc.createTextNode("1.0"));
			hHeaderNode.appendChild(newNode);
		
		// Create data segment
		hDataNode = xmlDoc.createElement("wordlistData");
		hDataNode.setAttribute("compressed", "false");
		xmlDoc.documentElement.appendChild(hDataNode);
		
		for(i = 0; i < this.wordId.length; i++) {
			
			newWordNode = xmlDoc.createElement("word");
			
			newNode = xmlDoc.createElement("primaryTranslation");
			newNode.appendChild(xmlDoc.createTextNode(this.priTranslation[i]));
			newWordNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("secondaryTranslation");
			newNode.appendChild(xmlDoc.createTextNode(this.secTranslation[i]));
			newWordNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("primaryRemark");
			newNode.appendChild(xmlDoc.createTextNode(this.priRemark[i]));
			newWordNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("secondaryRemark");
			newNode.appendChild(xmlDoc.createTextNode(this.secRemark[i]));
			newWordNode.appendChild(newNode);
			
			hDataNode.appendChild(newWordNode);
		}
		
		return xmlDoc;
	}
	
	this.GenerateT2kXml = function Wordlist$GenerateT2kXml () {
	
		// Declare variables
		var xmlDoc;
		var newNode, newMessageNode, newItemsNode, newItemNode;
		var strRemarks;
		var oPi;
		
		// Create xml document
		xmlDoc = new Xml.Document("teach2000");
		
		// Add processing instruction
		oPi = xmlDoc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
		xmlDoc.insertBefore(oPi, xmlDoc.firstChild);
		
		newNode = xmlDoc.createElement("a");
		newNode.setAttribute("href", "http://www.woordenleren.nl");
		newNode.setAttribute("title", "Vocabulum Online");
		newNode.appendChild(xmlDoc.createTextNode("Free vocabulary trainer Vocabulum Online"));
		xmlDoc.documentElement.appendChild(newNode);
		
		newNode = xmlDoc.createElement("version");
		newNode.appendChild(xmlDoc.createTextNode("814"));
		xmlDoc.documentElement.appendChild(newNode);
		
		newNode = xmlDoc.createElement("description");
		newNode.appendChild(xmlDoc.createTextNode(this.strDescription));
		xmlDoc.documentElement.appendChild(newNode);
		
		newMessageNode = xmlDoc.createElement("message_data");
		newMessageNode.setAttribute("mm_files_embedded", "0");
		newMessageNode.setAttribute("encrypted", "0");
		xmlDoc.documentElement.appendChild(newMessageNode);
		
		newNode = xmlDoc.createElement("font_question");
		newNode.appendChild(xmlDoc.createTextNode("Arial"));
		newMessageNode.appendChild(newNode);
		
		newNode = xmlDoc.createElement("font_answer");
		newNode.appendChild(xmlDoc.createTextNode("Arial"));
		newMessageNode.appendChild(newNode);
		
		newItemsNode = xmlDoc.createElement("items");
		newMessageNode.appendChild(newItemsNode);
		
		// Add words to document
		for(i = 0; i < this.wordId.length; i++) {
		
			newItemNode = xmlDoc.createElement("item");
			newItemsNode.appendChild(newItemNode);
			newItemNode.setAttribute("id", i);
			
			newNode = xmlDoc.createElement("rf");
			newNode.appendChild(xmlDoc.createTextNode("0"));
			newItemNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("question");
			newNode.appendChild(xmlDoc.createTextNode(this.priTranslation[i]));
			newItemNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("answer");
			newNode.appendChild(xmlDoc.createTextNode(this.secTranslation[i]));
			newItemNode.appendChild(newNode);
			
			// Store remarks is appropriate
			if(this.priRemark[i].length > 0 || this.secRemark[i].length > 0) {
				if(this.priRemark[i].length > 0 && this.secRemark[i].length > 0) {
					strRemarks = this.priRemark[i] + "\r\n" + this.secRemark[i];
				} else if(this.priRemark[i].length > 0 && this.secRemark[i].length == 0) {
					strRemarks = this.priRemark[i];
				} else if(this.priRemark[i].length == 0 && this.secRemark[i].length > 0) {
					strRemarks = this.secRemark[i];
				}
				
				newNode = xmlDoc.createElement("remarks");
				newNode.appendChild(xmlDoc.createTextNode(strRemarks));
				newItemNode.appendChild(newNode);
			}
			
			newNode = xmlDoc.createElement("error");
			newNode.appendChild(xmlDoc.createTextNode("0"));
			newItemNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("testcount");
			newNode.appendChild(xmlDoc.createTextNode("0"));
			newItemNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("is_multiplechoice");
			newNode.appendChild(xmlDoc.createTextNode("N"));
			newItemNode.appendChild(newNode);
		}
		
		newNode = xmlDoc.createElement("testresults");
		newMessageNode.appendChild(newNode);
		
		return xmlDoc;
	}
	
	this.GenerateBackpackXml = function Wordlist$GenerateBackpackXml () {
	
		// Declare variables
		var xmlDoc;
		var newNode, newWoordenNode, newWordNode, newGeneralInfo;
		var strRemarks;
		var oPi;
		var oDate;
		
		// Create xml document
		xmlDoc = new Xml.Document("Root");
		
		// Add processing instruction
		oPi = xmlDoc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
		xmlDoc.insertBefore(oPi, xmlDoc.firstChild);
		
		// General info node
			newGeneralInfo = xmlDoc.createElement("General_Info");
			newGeneralInfo.appendChild(xmlDoc.createTextNode(this.strDescription));
			xmlDoc.documentElement.appendChild(newGeneralInfo);
			
			newNode = xmlDoc.createElement("Name");
			newNode.appendChild(xmlDoc.createTextNode("Vocabulum Online"));
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("Methode");
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("Jaar");
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("Land");
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("Taal1");
			newNode.appendChild(xmlDoc.createTextNode("0"));
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("Taal2");
			newNode.appendChild(xmlDoc.createTextNode("0"));
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("Opleiding");
			newNode.appendChild(xmlDoc.createTextNode(this.strDescription));
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("Hoofdstuk");
			newGeneralInfo.appendChild(newNode);
			
			oDate = new Date();
			newNode = xmlDoc.createElement("datum_lw");
			newNode.appendChild(xmlDoc.createTextNode(oDate.getDate() + "-" + (oDate.getMonth() + 1) + "-" + oDate.getFullYear() + " " + oDate.getHours() + ":00"));
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("datum_ss");
			newNode.appendChild(xmlDoc.createTextNode(oDate.getDate() + "-" + (oDate.getMonth() + 1) + "-" + oDate.getFullYear() + " " + oDate.getHours() + ":00"));
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("file_os");
			newNode.appendChild(xmlDoc.createTextNode("w32"));
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("file_versie");
			newNode.appendChild(xmlDoc.createTextNode("2.0.2"));
			newGeneralInfo.appendChild(newNode);
			
			newNode = xmlDoc.createElement("file_buildnr");
			newNode.appendChild(xmlDoc.createTextNode("007"));
			newGeneralInfo.appendChild(newNode);
		
		newWoordenNode = xmlDoc.createElement("Woorden");
		newWoordenNode.appendChild(xmlDoc.createTextNode(this.strDescription));
		xmlDoc.documentElement.appendChild(newWoordenNode);
		
		// Store words
		for(i = 0; i < this.wordId.length; i++) {
		
			newWordNode = xmlDoc.createElement("Word");
			newWoordenNode.appendChild(newWordNode);
			
			newNode = xmlDoc.createElement("Original");
			newNode.appendChild(xmlDoc.createTextNode(this.priTranslation[i]));
			newWoordenNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("Translation");
			newNode.appendChild(xmlDoc.createTextNode(this.secTranslation[i]));
			newWoordenNode.appendChild(newNode);
			
			// Store remarks is appropriate
			if(this.priRemark[i].length > 0 || this.secRemark[i].length > 0) {
				if(this.priRemark[i].length > 0 && this.secRemark[i].length > 0) {
					strRemarks = this.priRemark[i] + "\r\n" + this.secRemark[i];
				} else if(this.priRemark[i].length > 0 && this.secRemark[i].length == 0) {
					strRemarks = this.priRemark[i];
				} else if(this.priRemark[i].length == 0 && this.secRemark[i].length > 0) {
					strRemarks = this.secRemark[i];
				}
			} else {
				strRemarks = "";
			}
			
			newNode = xmlDoc.createElement("Extra_info");
			newNode.appendChild(xmlDoc.createTextNode(strRemarks));
			newWoordenNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("vink");
			newNode.appendChild(xmlDoc.createTextNode("1"));
			newWoordenNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("label");
			newNode.appendChild(xmlDoc.createTextNode("0"));
			newWoordenNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("fout_ov");
			newWoordenNode.appendChild(newNode);
			
			newNode = xmlDoc.createElement("goed_ov");
			newWoordenNode.appendChild(newNode);
		}
		
		
		return xmlDoc;
	}
	
	this.GenerateOh4Txt = function Wordlist$GenerateOh4Txt () {
	
		// Declare variables
		var strOutput;
		
		// Header
		strOutput = "[FONT:Arial,9] = [FONT:Arial,9]\r\n";
		
		for(i = 0; i < this.wordId.length; i++) {
		
			strOutput += this.priTranslation[i].replace(/=/g, "");
			strOutput += " = ";
			strOutput += this.secTranslation[i].replace(/=/g, "");
			strOutput += "\r\n";
		}
		
		return strOutput;
	}
	
	this.Clear = function Wordlist$Clear () {
		
		// Reset data arrays
		this.wordId = new Array();
		this.wordClientId = new Array();
		this.priTranslation = new Array();
		this.secTranslation = new Array();
		this.priRemark = new Array();
		this.secRemark = new Array();
		
		this.iLastIndex = -1;
		this.iLastClientId = -1;
		
		this.transactions = new Array();
		this.iTransactionCursor = -1;
		
		// Log callback
		this.OnLog.Invoke();
	}
	
	this.Count = function Wordlist$Count () {
		return this.wordId.length;
	}
	
	this.AppendNewWord = function Wordlist$AppendNewWord () {
		
		// Increment the last index counter
		this.iLastIndex++;
		
		// Set values
		this.wordId[this.iLastIndex] = "new";
		this.wordClientId[this.iLastIndex] = ++this.iLastClientId;
		this.priTranslation[this.iLastIndex] = "";
		this.secTranslation[this.iLastIndex] = "";
		this.priRemark[this.iLastIndex] = "";
		this.secRemark[this.iLastIndex] = "";
		
		// Log transaction
		if(this.bLogging) {
			
			// Declare variables
			newTransaction = new Transaction();
			newTransaction.transactionType = "new";
			newTransaction.wordIndex = this.iLastIndex;
			this.LogTransaction(newTransaction);
		}
		
		return this.iLastIndex;
	}
	
	this.AppendWord = function Wordlist$AppendWord (strPrimaryTranslation, strSecondaryTranslation, strPrimaryRemark, strSecondaryRemark) {
	
		// Declare variables
		var iWord;
		
		iWord = this.AppendNewWord();
		
		this.SetValue(iWord, 1, strPrimaryTranslation);
		this.SetValue(iWord, 2, strSecondaryTranslation);
		this.SetValue(iWord, 3, strPrimaryRemark);
		this.SetValue(iWord, 4, strSecondaryRemark);
	}
	
	this.CreateNewWord = function Wordlist$CreateNewWord (wordIndex) {
	
		// Check if a word exists on specified index
		if(this.wordId[wordIndex] != undefined)
			return false;
			
		// Set values
		this.wordId[wordIndex] = "new";
		this.wordClientId[this.iLastIndex] = ++this.iLastClientId;
		this.priTranslation[wordIndex] = "";
		this.secTranslation[wordIndex] = "";
		this.priRemark[wordIndex] = "";
		this.secRemark[wordIndex] = "";
		
		if(wordIndex > this.iLastIndex)
			this.iLastIndex = wordIndex;
	
		// Log transaction
		if(this.bLogging) {
			
			// Declare variables
			newTransaction = new Transaction();
			newTransaction.transactionType = "new";
			newTransaction.wordIndex = wordIndex;
			this.LogTransaction(newTransaction);
		}
		
		return true;
	}
	
	this.RemoveWordAt = function Wordlist$RemoveWordAt (iIndex) {
		
		// Check if iWord index is valid
		if(iIndex >= this.wordId.length)
			return false;
			
		// Log transaction
		if(this.bLogging) {
			
			// Declare variables
			newTransaction = new Transaction();
			newTransaction.transactionType = "delete";
			newTransaction.wordIndex = iIndex;
			newTransaction.columnValues = [ this.wordId[iIndex], this.wordClientId[iIndex], this.priTranslation[iIndex], this.secTranslation[iIndex], this.priRemark[iIndex], this.secRemark[iIndex] ];
			this.LogTransaction(newTransaction);
		}
		
		// Decrement the last index value
		this.iLastIndex--;
		
		// Actually delete values from data arrays
		this.wordId.splice(iIndex, 1);
		this.wordClientId.splice(iIndex, 1);
		this.priTranslation.splice(iIndex, 1);
		this.secTranslation.splice(iIndex, 1);
		this.priRemark.splice(iIndex, 1);
		this.secRemark.splice(iIndex, 1);
		
		return true;
	}
	
	this.InsertWordAt = function Wordlist$InsertWordAt (iIndex, wordId, wordClientId, priTranslation, secTranslation, priRemark, secRemark) {
	
		// Increment the last index value
		this.iLastIndex++;
		
		// Store values
		this.wordId.splice(iIndex, 0, wordId);
		this.wordClientId.splice(iIndex, 0, wordClientId);
		this.priTranslation.splice(iIndex, 0, priTranslation);
		this.secTranslation.splice(iIndex, 0, secTranslation);
		this.priRemark.splice(iIndex, 0, priRemark);
		this.secRemark.splice(iIndex, 0, secRemark);
	}
	
	this.GetValue = function Wordlist$GetValue (iWord, iColumn) {
		
		// Check if iWord index is valid
		if(iWord >= this.wordId.length)
			return undefined;
		
		switch(iColumn) {
			case 0:
				return this.wordId[iWord];
				break;
			case 1:
				return this.priTranslation[iWord];
				break;
			case 2:
				return this.secTranslation[iWord];
				break;
			case 3:
				return this.priRemark[iWord];
				break;
			case 4:
				return this.secRemark[iWord];
				break;
			default:
				return undefined;
				break;
		}
	}
	
	this.SetValue = function Wordlist$SetValue (iWord, iColumn, strValue) {
		
		// Declare variables
		var strOldValue;
		
		strOldValue = this.GetValue(iWord, iColumn);
		
		// Check if iWord index is valid
		if(iWord >= this.wordId)
			return false;
		
		switch(iColumn) {
			case 0:
				this.wordId[iWord] = strValue;
				break;
			case 1:
				this.priTranslation[iWord] = strValue;
				break;
			case 2:
				this.secTranslation[iWord] = strValue;
				break;
			case 3:
				this.priRemark[iWord] = strValue;
				break;
			case 4:
				this.secRemark[iWord] = strValue;
				break;
			default:
				return false;
				break;
		}
		
		if(this.bLogging) {
			
			// Declare variables
			newTransaction = new Transaction();
			newTransaction.transactionType = "edit";
			newTransaction.wordIndex = iWord;
			newTransaction.columnIndex = iColumn;
			newTransaction.oldValue = strOldValue;
			newTransaction.newValue = strValue;
			this.LogTransaction(newTransaction);
		}
		
		// Wordlist changed
		this.bChangesSaved = false;
		
		return true;
	}
	
	this.CanUndo = function Wordlist$CanUndo () {
		
		// Check if undoing is available
		if(this.iTransactionCursor < 0)
			return false;
			
		if(this.transactions[this.iTransactionCursor] == undefined)
			return false;
		
		return true;
	}
	
	this.Undo = function Wordlist$Undo () {
		
		// Declare variables
		var transaction;
		
		this.SuspendLogging();
		
		transaction = this.CurrentTransaction();
		
		switch(transaction.transactionType) {
			case "new":
				
				this.RemoveWordAt(transaction.wordIndex);
				break;
			case "edit":
			
				this.SetValue(transaction.wordIndex, transaction.columnIndex, transaction.oldValue);
				break;
			case "delete":
			
				this.InsertWordAt(transaction.wordIndex, transaction.columnValues[0], transaction.columnValues[1], transaction.columnValues[2], transaction.columnValues[3], transaction.columnValues[4], transaction.columnValues[5]);
				break;
		}
		
		this.iTransactionCursor--;
		
		this.ResumeLogging();
		
		// Log callback
		this.OnLog.Invoke();
		
		// Return transaction for further processing
		return transaction;
	}
	
	this.CanRedo = function Wordlist$CanRedo () {
	
		// Check if redoing is available
		if(this.iTransactionCursor > this.transactions.length - 1)
			return false;
		
		if(this.transactions[this.iTransactionCursor + 1] == undefined)
			return false;
		
		return true
	}
	
	this.Redo = function Wordlist$Redo () {
	
		// Declare variables
		var transaction;
		
		this.SuspendLogging();
		
		transaction = this.GetNextTransaction();
		
		switch(transaction.transactionType) {
			case "new":
				
				this.CreateNewWord(transaction.wordIndex);
				break;
			case "edit":
			
				this.SetValue(transaction.wordIndex, transaction.columnIndex, transaction.newValue);
				break;
			case "delete":
			
				this.RemoveWordAt(transaction.wordIndex);
				break;
		}
		
		this.iTransactionCursor++;
		
		this.ResumeLogging();
		
		// Log callback
		this.OnLog.Invoke();
		
		return transaction;
	}
	
	this.CanRepeat = function Wordlist$CanRepeat () {
	
		// Check if repeating is available
		if(this.iTransactionCursor < 0)
			return false;
			
		if(this.transactions[this.iTransactionCursor] == undefined)
			return false;
			
		if(this.transactions[this.iTransactionCursor].transactionType != "new")
			return false;
		
		return true
	}
	
	this.Repeat = function Wordlist$Repeat () {
		
		// Declare variables
		var transaction;
		
		transaction = this.CurrentTransaction();
		
		switch(transaction.transactionType) {
			case "new":
				
				this.AppendNewWord();
				break;
		}
		
		// Log callback
		this.OnLog.Invoke();
		
		return transaction;
	}
	
	this.LogTransaction = function Wordlist$LogTransaction (transaction) {
		
		// Declare variables
		var newArray;
		
		// Remove entries beyond transaction cursor (tree branch is invalid after this log entry)
		if(this.iTransactionCursor + 1 < this.transactions.length)
			this.transactions.splice(this.iTransactionCursor + 1,  this.transactions.length - this.iTransactionCursor - 1);
			
		this.transactions.push(transaction);
		this.iTransactionCursor++;
			
		// Remove entries at the beginning of the log if the log length exceeded the limit
		while(this.transactions.length > this.iLogLength)
			this.transactions.shift();
			
		if(this.iTransactionCursor >= this.transactions.length)
			this.iTransactionCursor = this.transactions.length - 1;
		
		// Log callback
		this.OnLog.Invoke();
	}
	
	this.CurrentTransaction = function Wordlist$CurrentTransaction () {
		
		return this.transactions[this.iTransactionCursor];
	}
	
	this.GetNextTransaction = function Wordlist$GetNextTransaction () {
	
		return this.transactions[this.iTransactionCursor + 1];
	}
	
	this.SuspendLogging = function Wordlist$SuspendLogging () {
		this.bLogging = false;
	}
	
	this.ResumeLogging = function Wordlist$ResumeLogging () {
		this.bLogging = true;
	}

	this.Initialize();
};

Wordlist.prototype.GenerateWrtsXml = function Wordlist$GenerateWrtsXml () {
	
	// Declare variables
	var xmlDoc;
	var newLijstNode, newNode, newTaalNode, newWoordNode, newComment, newDatumNode;
	var oPi;
	var d = new Date();
	
	// Create xml document
	xmlDoc = new Xml.Document("wrts");
	
	// Add processing instruction
	oPi = xmlDoc.createProcessingInstruction("xml", "version=\"1.0\" encoding=\"UTF-8\"");
	xmlDoc.insertBefore(oPi, xmlDoc.firstChild);
	
	newComment = xmlDoc.createComment(" Vocabulum Online, http://www.woordenleren.nl ");
	xmlDoc.insertBefore(newComment, xmlDoc.documentElement);
	
	newLijstNode = xmlDoc.createElement("lijst");
	xmlDoc.documentElement.appendChild(newLijstNode);
	
	newNode = xmlDoc.createElement("titel");
	newNode.appendChild(xmlDoc.createTextNode(this.strName));
	newLijstNode.appendChild(newNode);
	
	// Create date node
	newDatumNode = xmlDoc.createElement("datum");
	newLijstNode.appendChild(newDatumNode);
	
	newNode = xmlDoc.createElement("downloaded");
	newNode.appendChild(xmlDoc.createTextNode(d.toUTCString()));
	newDatumNode.appendChild(newNode);
	
	newNode = xmlDoc.createElement("created");
	newNode.appendChild(xmlDoc.createTextNode(d.toUTCString()));
	newDatumNode.appendChild(newNode);
	
	newNode = xmlDoc.createElement("updated");
	newNode.appendChild(xmlDoc.createTextNode(d.toUTCString()));
	newDatumNode.appendChild(newNode);
	
	// Create language info
	newTaalNode = xmlDoc.createElement("taal");
	newLijstNode.appendChild(newTaalNode);
	
	newNode = xmlDoc.createElement("a");
	newNode.appendChild(xmlDoc.createTextNode(this.priLanguage));
	newTaalNode.appendChild(newNode);
	
	newNode = xmlDoc.createElement("b");
	newNode.appendChild(xmlDoc.createTextNode(this.secLanguage));
	newTaalNode.appendChild(newNode);
	
	// Add words to document
	for(i = 0; i < this.wordId.length; i++) {
	
		newWoordNode = xmlDoc.createElement("woord");
		newLijstNode.appendChild(newWoordNode);
		
		newNode = xmlDoc.createElement("a");
		newNode.appendChild(xmlDoc.createTextNode(this.priTranslation[i]));
		newWoordNode.appendChild(newNode);
			
		newNode = xmlDoc.createElement("b");
		newNode.appendChild(xmlDoc.createTextNode(this.secTranslation[i]));
		newWoordNode.appendChild(newNode);
	}
	
	return xmlDoc;
}

Transaction.prototype.__type = "Transaction";
Transaction.prototype.transactionType = null;
Transaction.prototype.columnValues = null;
Transaction.prototype.wordIndex = null;
Transaction.prototype.columnIndex = null;
Transaction.prototype.oldValue = null;
Transaction.prototype.newValue = null;

function Transaction() {

	this.Initialize = function Transaction$Initialize () {
		
		this.transactionType = "new";
		
		this.columnValues = new Array();
		this.wordIndex = null;
		this.columnIndex = null;
		this.oldValue = null;
		this.newValue = null;
	}
	
	this.GetDescription = function Transaction$GetDescription () {
		
		switch(this.transactionType) {
			case "new":
				return TEXT['Transaction.AddWord'];
				break;
			case "delete":
				return TEXT['Transaction.DeleteWord'];
				break;
			case "edit":
				return TEXT['Transaction.EditWord'];
				break;
		}
	}
	
	this.Initialize();
}
