mozile.root = "..";
mozile.require("mozile.rng");
mozile.require("mozile.util");
mozile.require("mozile.xml");
mozile.require("mozile.xpath");
// Disable GUI
delete mozile.gui;

/**** Global Variables ****/
var rngFile, xmlFile, rngBox, xmlBox, resultBox, errorsCheck, debugCheck, parseRNGButton, validateXMLButton;
var maxLength = 30;
var width = 12;
var library;
var schema, schemaValidation;
var parseRNGResult = new Array();
var validation;
var validateXMLResult = new Array();

/**
 * Assigns some variables, loads the library file, and builds the library menu.
 * @type Void
 */
function load() {
	// Warn Safari users of crashes
	if(mozile.browser.isSafari && mozile.browser.safariVersion <= 418)
		alert("Warning!!\nDue to serious bugs in the Safari Browser's XML tools, you may experience a browser crash when running the 'Parse RNG', 'Validate XML', or 'Report' commands on this page.\nFor more information see http://mozile.mozdev.org/0.8/KnownIssues");

	rngFile = document.getElementById("rngFile");
	xmlFile = document.getElementById("xmlFile");
	rngBox = document.getElementById("rngBox");
	xmlBox = document.getElementById("xmlBox");
	resultBox = document.getElementById("result");
	errorsCheck = document.getElementById("errorsCheck");
	debugCheck = document.getElementById("debugCheck");
	parseRNGButton = document.getElementById("parseRNG");
	validateXMLButton = document.getElementById("validateXML");

	library = mozile.xml.load("RNGLibrary.xml");
	try {
		build("/xmlns:testSuite[1]");
	} catch(e) {
		alert(e);
	}
}

/**
 * Print output in the "results" text area.
 * @param {String} content The string to be printed.
 * @param {Boolean} append Optional. When true the content will be appended to what is already there.
 * @type Void
 */
function printResult(content, append) {
	if(resultBox.firstChild && resultBox.firstChild.nodeValue && append) {
		content = resultBox.firstChild.nodeValue +"\n\n"+ content;
	}

	if(typeof(resultBox.innerText) != "undefined") {
		resultBox.innerText = content;
	}
	else {
		mozile.dom.removeChildNodes(resultBox);
		resultBox.appendChild(document.createTextNode(content));
	}
}


/**
 * Parse the RNG document in the "RNG" text box.
 * @type Void
 */
function parseRNG() {
	var rngDoc, xmlDoc;
	validation = null;
	validateXMLResult = new Array();
	printResult("");

	// Get the RNG document
	try {
		if(rngBox.value) rngDoc = mozile.xml.parse(rngBox.value);
		else {
			printResult("Error: No RNG document to parse.");
			parseRNGButton.setAttribute("class", "button false");
			return;
		}
	}
	catch(e) {
		printResult("Error parsing RNG document.\n"+ e);
		parseRNGButton.setAttribute("class", "button false");
	}
	
	// Prepare the schema
	schema = new mozile.rng.Schema();
	mozile.rng.debug = debugCheck.checked;
	if(rngFile.value) {
		var loc = location.toString()
		loc = loc.substring(0, loc.indexOf("validation.html"));
		schema.filepath = loc + rngFile.value;
		//alert(schema.filepath);
	}
	
	// Parse the schema
	var result = new Array();
	var date = new Date();
	var startTime = date.getTime();
	var endTime;
	try {
		schemaValidation = schema.parse(rngDoc.documentElement);
	} catch(e) { 
		printResult("Error: XML validation failed. Error was:"+ mozile.dumpError(e));
		return;
	}
	date = new Date();
	endTime = date.getTime();

	// Print a report.
	if(schemaValidation) {
		result.push("Parsing RNG");
		result.push(mozile.util.pad("Result: ", width, true) + schemaValidation.isValid);
		result.push(mozile.util.pad("Time: ", width, true) + (endTime - startTime) +"ms");
		result.push(mozile.util.pad("Messages: ", width, true) + schemaValidation._messages.length);
		parseRNGResult = result;
		printResult(result.join("\n"));
		parseRNGButton.setAttribute("class", "button "+schemaValidation.isValid);
	}
	else {
		printResult("Error: Schema parsing failed. Result was:"+ schemaValidation);
		parseRNGButton.setAttribute("class", "button false");
		return;
	}

}


/**
 * Parse and validate the XML document in the "XML" text box.
 * @type Void
 */
function validateXML() {
	var xmlDoc;

	// Get the XML document
	try {
		if(xmlFile.value) {
			xmlDoc = mozile.xml.load(xmlFile.value);
			xmlBox.value = mozile.xml.serialize(xmlDoc);
		}
		if(!xmlBox.value) {
			printResult("Error: No XML document to validate.");
			validateXMLButton.setAttribute("class", "button false");
			return;
		}
		else xmlDoc = mozile.xml.parse(xmlBox.value);
	}
	catch(e) {
		printResult("Error parsing XML document.\n"+ e);
		validateXMLButton.setAttribute("class", "button false");
	}
	
	var result = new Array();
	var date = new Date();
	var startTime = date.getTime();
	var endTime;
	
	// Validate the XML
	if(schema && schemaValidation && schemaValidation.isValid) {
		mozile.rng.debug = debugCheck.checked;
		try {
			validation = schema.validate(xmlDoc.documentElement);
		} catch(e) { 
			printResult("Error: XML validation failed. Error was:"+ mozile.dumpError(e));
			return;
		} 
		date = new Date();
		endTime = date.getTime();
	
		// Print a report.
		if(validation) {
			result.push("Validating XML");
			result.push(mozile.util.pad("Result: ", width, true) + validation.isValid);
			result.push(mozile.util.pad("Time: ", width, true) + (endTime - startTime) +"ms");
			result.push(mozile.util.pad("Messages: ", width, true) + validation._messages.length);
			validateXMLResult = result;
			result = parseRNGResult.concat(result);
			printResult(result.join("\n"));
			validateXMLButton.setAttribute("class", "button "+ validation.isValid);
		}
		else {
			printResult("Error: XML validation failed. Result was:"+ validation);
			return;
		}
	}
	else {
		printResult("Error: An RNG schema must be successfully parsed before the XML document can be validated against it.");
		validateXMLButton.setAttribute("class", "button false");
	}

}

/**
 * Print a report based on the last validation operation.
 * @type Void
 */
function report() {
	var result = new Array();
	var date = new Date();
	var startTime = date.getTime();
	var endTime;

	result.push("Report");
	var report;
	if(validation) {
		result.push(mozile.util.pad("Target: ", width, true) + "XML");
		report = validation.report(errorsCheck.checked);
	}
	else if(schemaValidation) {
		result.push(mozile.util.pad("Target: ", width, true) + "RNG");
		report = schemaValidation.report(errorsCheck.checked);
	}
	else {
		printResult("Error: Nothing to report about.");
		return;
	}

	date = new Date();
	endTime = date.getTime();
	result.push(mozile.util.pad("Time: ", width, true) + (endTime - startTime) +"ms");
	if(report) result.push(report);
	else result.push("Nothing to report.");
	result = parseRNGResult.concat(validateXMLResult, result);
	printResult(result.join("\n"));

}

/**
 * Send a message to the Firebug extension for Firefox.
 * @type Void
 */
function printfire() {
	if (document.createEvent)	{
		printfire.args = arguments;
		var ev = document.createEvent("Events");
		ev.initEvent("printfire", false, true);
		dispatchEvent(ev);
	}
}










/**
 * Given a path, builds a series of "select" elements for each stage in the hierarchy.
 * @param {String} path A simple XPath.
 * @param {Boolean} last Optional. When true that last fee option from a selection will be used instead of the first.
 * @type Void
 */
function build(path, last) {
	var selectors = document.getElementById("selectors");
	mozile.dom.removeChildNodes(selectors);
	
	var components = mozile.xpath.getComponents(path);
	var selector, value;
	var newPath = "";
	for(var i=0; i < components.length; i++) {
		newPath = newPath +"/"+ components[i];
		if(selector) selectOption(selector, newPath);

		selector = document.createElement("select");
		//selector.setAttribute("onchange", "pick(this.value)");
		selector.onchange = function() { pick(this.value) };
		populate(selector, newPath);
		if(last) selector.selectedIndex = selector.length - 1;
		if(selector.childNodes.length > 0)
			selectors.appendChild(selector);

		if(i+1 == components.length) {
			value = null;
			if(last && selector.lastChild && selector.lastChild.value)
				value = selector.lastChild.value;
			else if(!last && selector.firstChild && selector.firstChild.value)
				value = selector.firstChild.value;
			if(value) {
				var component = mozile.xpath.getComponent(value);
				//alert(value +"\n"+ mozile.util.dumpValues(component));
				if( (component.localName == "testSuite" || 
					component.localName == "testCase") && 
					component.position != undefined) {
					components.push(component.name +"["+ component.position +"]");
				}
			}
		}
	}
	
	if(selectors.lastChild) {
		selectors.lastChild.setAttribute("id", "finalSelect");
		select();
	}
}

/**
 * Used by "option" elements to rebuild the libaray menu.
 * @type Void
 */
function pick(path) {
	var element = mozile.xpath.getNode(path, library);
	if(!element) return;
	build(path);
}

/**
 * Selects the next option in the library menu. Climbs the tree as necessary.
 * @type Void
 */
function next() {
	var finalSelect = document.getElementById("finalSelect");
	var selector = finalSelect;
	while(selector) {
		if(selector.selectedIndex + 1 == selector.length)
			selector = selector.previousSibling;
		else break;
	}
	if(selector) {
		selector.selectedIndex = selector.selectedIndex + 1;
		if(selector != finalSelect) build(selector.value);
		else select();
	}
}

/**
 * Selects the previous option in the library menu. Climbs the tree as necessary.
 * @type Void
 */
function previous() {
	var finalSelect = document.getElementById("finalSelect");
	var selector = finalSelect;
	while(selector) {
		if(selector.selectedIndex == 0)
			selector = selector.previousSibling;
		else break;
	}
	if(selector) {
		selector.selectedIndex = selector.selectedIndex - 1;
		if(selector != finalSelect) build(selector.value, true);
		else select();
	}
}

/**
 * Selects the current option in the library menu.
 * @type Void
 */
function select() {
	var select = document.getElementById("finalSelect");
	var path = select.value;
	
	rngFile.value = "";
	xmlFile.value = "";
	
	rngBox.value = "";
	xmlBox.value = "";
	
	display(path);
}

/**
 * Displays the RNG and XML documents for testCase at the given path.
 * @param {String} path A simple XPath pointing to a testCase element or tis children.
 * @type Void
 */
function display(path) {
	var element = mozile.xpath.getNode(path, library);
	if(!element) return; 
	
	var content;
	var testCase;
	if(element.nodeName == "valid" || element.nodeName == "invalid") {	
		if(element.getAttribute("file")) {
			xmlBox.value = "Loading XML...";
			xmlFile.value = element.getAttribute("file");
			var xmlDoc = mozile.xml.load(xmlFile.value);
			xmlBox.value = mozile.xml.serialize(xmlDoc);
		}
		else {
			content = mozile.xml.serialize(mozile.dom.getFirstChildElement(element));
			content = content.replace(/\t/g, "  ");
			xmlBox.value = content;
		}
		testCase = element.parentNode;
	}
	else if(element.nodeName == "testCase") {
		testCase = element;
	}
	
	if(!testCase) return;
	
	var name;
	for(var c=0; c < testCase.childNodes.length; c++) {
		name = testCase.childNodes[c].nodeName;
		if(name == "correct" || name == "incorrect") {
			if(testCase.childNodes[c].getAttribute("file")) {
				rngBox.value = "Loading schema...";
				rngFile.value = testCase.childNodes[c].getAttribute("file");
				var rngDoc = mozile.xml.load(rngFile.value);
				rngBox.value = mozile.xml.serialize(rngDoc);
			}
			else {
				var element = mozile.dom.getFirstChildElement(testCase.childNodes[c]);
				content = mozile.xml.serialize(element);
				content = content.replace(/\t/g, "  ");
				rngBox.value = content;
			}
		}
	}
}

/**
 * Selects an "option" element inside a "select" element which has a "value" attribute matching the given value.
 * @param {Element} select The select element to use.
 * @param {String} value The value to select.
 * @type Void
 */
function selectOption(select, value) {
	if(!select) return;
	var i=0;
	for(var c=0; c < select.childNodes.length; c++) {
		if(select.childNodes[c].nodeName.toLowerCase() == "option") {
			if(select.childNodes[c].getAttribute("value") == value) {
				select.selectedIndex = i;
				return;
			}
			i++;
		}
	}
}

/**
 * Populates a "select" element with "option" elements by parsing the testSuite or testCase at the given path.
 * @param {Element} select The select element to populate.
 * @param {String} path The path to a testSuite or testCase with which to populate the select element.
 * @type Void
 */
function populate(select, path) {
	if(!select) return;
	while(select.childNodes.length) {
		select.removeChild(select.firstChild);
	}
	
	var element = mozile.xpath.getNode(path, library);
	//alert("Result:"+element);
	if(!element) return;
	var units = getUnits(element);
	var option;
	for(var t=0; t < units.length; t++) {
		option = document.createElement("option");
		option.setAttribute("value", units[t]["path"]);
		option.appendChild(document.createTextNode(units[t]["title"]));
		select.appendChild(option);
	}
}

/**
 * Searches a given element for children of a certain type. 
 * Returns an object with "title" and "path" properties which can be used to create an "option" element.
 * @param {Element} element The element to search.
 * @type Object
 */
function getUnits(element) {
	var names = ["testSuite", "testCase", "schema", "valid", "invalid"];
	var units = [];
	if(!element) return units;
	var name, title;
	var t=1;
	for(var c=0; c < element.childNodes.length; c++) {
		name = null;
		for(var n=0; n < names.length; n++) {
			if(element.childNodes[c].nodeName == names[n]) name = element.childNodes[c].nodeName;
		}

		if(name) {
			title = getTitle(element.childNodes[c]);
			if(title) title = t +". "+ title;
			else {
				if(name == "testSuite") title = t + ". Test Suite";
				else if(name == "testCase") title = t + ". Test Case";
				else if(name == "schema") title = t + ". RNG Schema";
				else if(name == "valid") title = t + ". Valid Example";
				else if(name == "invalid") title = t + ". Invalid Example";
				else title = t + ". Example";
			}
			//alert(c +" "+ element.childNodes[c].nodeName);
			var path = mozile.xpath.getXPath(element.childNodes[c]);
			units.push({title: title, path: path});
			t++;
		}
	}
	return units;
}

/**
 * Gets a descriptive name for an element by looking for a "title" or "documentation" element among its children.
 * Name lengths are limited by the global "maxLength" variable.
 * @param {Element} select The select element to use.
 * @type String
 */
function getTitle(element) {
	if(!element) return null;
	var name, title;
	var result = "";
	for(var c=0; c < element.childNodes.length; c++) {
		var name = element.childNodes[c].nodeName;
		if(name == "title" || name == "documentation") {
			if(element.childNodes[c].firstChild) {
				result = element.childNodes[c].firstChild.nodeValue;
				break;
			}
		}	
		if(name == "section") {
			if(element.childNodes[c].firstChild) {
				result = "Section "+ element.childNodes[c].firstChild.nodeValue;
				break;
			}
		}	
	}
	
	if(result && result.length > maxLength) result = result.substring(0, maxLength) + "...";
	return result;
}



