function isUndefined ( arg ) {
	// null == undefined, so check string too
	// must use "arg == null" instead of "arg == undefined" because of MSIE5
	return ( ( arg == null ) && ( String(arg) == "undefined" ) );
}

function exists ( vtext ) {
	return (! isUndefined(vtext) );
}

function existsWarn ( vtext ) {
	var rv = exists ( eval (vtext) );
	if ( ! rv ) {
		outTaggedText("omit","TEST(S) OMITTED:  " + vtext + " is undefined.");
	}
	return rv;
}

function notNullWarn ( vtext ) {
	var rv = ! (eval(vtext) == null);
	if ( ! rv ) {
		outTaggedText("omit","TEST(S) OMITTED:  " + vtext + " is null.");
	}
	return rv;
}

function canOutput () {
	return ( exists(document)
	         && exists(document.createElement)
			 && exists(document.createTextNode)
			 && exists(document.createElement("p").appendChild) ); // lowercase for XHTML
}

function canOutputWarn() {
	var rv = canOutput();
	if ( ! rv ) {
		window.alert ("Cannot create output.  Skipping tests.");
	}
	return rv;
}

function makeP ( text ) {
	var pElem, textNode;

	pElem = document.createElement("p"); // lowercase for XHTML
	textNode = document.createTextNode(text);

	pElem.appendChild ( textNode );

	return pElem;
}

function makeClassedP ( className, text ) {
	var pElem;


	pElem = makeP( text );
//dump(pElem + "\n");   // gives [object Element] in XHTML, [object HTMLParagraphElement] in HTML
	// XXX Mozilla bug (????) - No HTML dom in XHTML
	if ( ! isUndefined(pElem.setAttribute) ) {
		pElem.setAttribute("class", className);
	} else if ( ! isUndefined(pElem.className) ) {
		pElem.className = className;
	}

	return pElem;
}

function getOutputElement() {

	// Workaround for bug 38763:
	if ( !is_nav5 && document.getElementById ) { // if HTML document
		return document.getElementById("output");
	} else {
		var rv = null;
	
		var divlist = document.getElementsByTagName("div");
		for (var i = 0; i < divlist.length; i++) {
			// window.alert ("At item number " + i);
			// window.alert( divlist.item(i) );
			if ( divlist.item(i).getAttribute("id") == "output" ) {
				rv = divlist.item(i);
			}
		}
// dump(rv + "\n"); // gives [object HTMLDivElement] in XHTML
		return rv;
	}
}

function outText ( text ) {
	getOutputElement().appendChild( makeP(text) );
}

function setBugInformation(bugnum, desc) {
	bugInformation = new Object();
	bugInformation.bugnum = bugnum;
	bugInformation.desc = desc;
}

function outTaggedText ( className, text ) {
	getOutputElement().appendChild( makeClassedP( className, text) );
	if (automatedTesting) {
		if ( (className == "pass") || (className == "fail")) {
			var failed = (className == "fail");
			var descrip = text;
			var bugnum = failed ? "No bug filed." : null;
			if ( bugInformation ) {
				if ( bugInformation.desc ) {
					descrip = bugInformation.desc;
				}
				if ( bugInformation.bugnum ) {
					bugnum = bugInformation.bugnum;
				}
			}
			bugInformation = null;
			output += ('<TR><TD' + ((failed)?' style="color:white;background:red;"':'') +'>' + "Test Number " + (++TestCaseNumber) );

			if (failed) {
				// this version doesn't write bug number
				if (bugnum) {
					output += ('<TD style="color:white;background:red;">failed');
					output += ('<TD style="color:white;background:red;">' + bugnum);
				} else {
					output += ('<TD style="color:white;background:red;" colspan=2>failed');
				}
				output += ('<TD style="color:white;background:red;">' + descrip);
			} else {
				output += ("<TD colspan=2>passed");
				output += ('<TD>' + descrip);
			}
			output += ('</TR>');

		}
	}
}

function valToString ( val ) {
	if ( typeof val == "string" ) {
		return '"' + val + '"';
	} else {
		return val;
	}
}

function writeResult( text, passed ) {
	if (passed) {
		outTaggedText ( "pass", text );
	} else {
		outTaggedText ( "fail", text );
	}
}

function report ( text ) {
	val = eval(text);
	outText(text + " is " + valToString(val));
}

function shouldBe ( atext, btext ) {
	var aval = eval(atext);
	var bval = eval(btext);
	if ( isUndefined(aval) ) {
		outTaggedText("fail", "Incorrect:  " + atext + " is undefined, but it should be " + btext + "." );
	} else if ( isUndefined(bval) ) {
		outTaggedText("fail", "Incorrect:  " + btext + " is undefined, but it should be " + atext + "." );
	} else if (aval == bval) {
		outTaggedText ( "pass", "Correct:  " + atext + " is " + btext + "." );
	} else {
		outTaggedText ( "fail", "Incorrect:  " + atext + " is not " + btext + ", but it should be.  Instead, it is " + valToString(aval) + "." );
	}
}

function shouldBeCI ( atext, btext ) {
	// If case insensitive
	var aval = eval(atext);
	var bval = eval(btext);
	if ( isUndefined(aval) ) {
		outTaggedText("fail", "Incorrect:  " + atext + " is undefined, but it should be " + btext + "." );
	} else if ( isUndefined(bval) ) {
		outTaggedText("fail", "Incorrect:  " + btext + " is undefined, but it should be " + atext + "." );
	} else if (aval.toString().toLowerCase() == bval.toString().toLowerCase()) {
		outTaggedText ( "pass", "Correct:  " + atext + " is " + btext + "." );
	} else {
		outTaggedText ( "fail", "Incorrect:  " + atext + " is not " + btext + ", but it should be.  Instead, it is " + valToString(aval) + "." );
	}
}

function shouldBeCQ ( atext, btext ) {
	// If case sensitivity is questionable
	var aval = eval(atext);
	var bval = eval(btext);
	if ( isUndefined(aval) ) {
		outTaggedText("fail", "Incorrect:  " + atext + " is undefined, but it should be " + btext + "." );
	} else if ( isUndefined(bval) ) {
		outTaggedText("fail", "Incorrect:  " + btext + " is undefined, but it should be " + atext + "." );
	} else if (aval == bval) {
		outTaggedText ( "pass", "Correct:  " + atext + " is " + btext + "." );
	} else {
		if (aval.toString().toLowerCase() == bval.toString().toLowerCase()) {
			outTaggedText ( "maybe", "Maybe:  " + atext + " is not " + btext + ", but it should be.  Instead, it is " + valToString(aval) + "." );
		} else {
			outTaggedText ( "fail", "Incorrect:  " + atext + " is not " + btext + ", but it should be.  Instead, it is " + valToString(aval) + "." );
		}
	}
}

function shouldNotBe ( atext, btext ) {
	var aval = eval(atext);
	var bval = eval(btext);
	if ( isUndefined(aval) ) {
		outTaggedText("fail", "Incorrect:  " + atext + " is undefined" );
	} else if ( isUndefined(bval) ) {
		outTaggedText("fail", "Incorrect:  " + btext + " is undefined" );
	} else if (aval == bval) {
		outTaggedText ( "fail", "Incorrect-N:  " + atext + " is " + btext + ", but it should not be." );
	} else {
		outTaggedText ( "pass", "Correct-N:  " + atext + " is not " + btext + ", but rather " + valToString(aval) + "." );
	}
}

function shouldNotBeCI ( atext, btext ) {
	// If case insensitive
	var aval = eval(atext);
	var bval = eval(btext);
	if ( isUndefined(aval) ) {
		outTaggedText("fail", "Incorrect:  " + atext + " is undefined" );
	} else if ( isUndefined(bval) ) {
		outTaggedText("fail", "Incorrect:  " + btext + " is undefined" );
	} else if (aval.toString().toLowerCase() == bval.toString().toLowerCase()) {
		outTaggedText ( "fail", "Incorrect-N:  " + atext + " is " + btext + ", but it should not be." );
	} else {
		outTaggedText ( "pass", "Correct-N:  " + atext + " is not " + btext + ", but rather " + valToString(aval) + "." );
	}
}

function shouldNotBeCQ ( atext, btext ) {
	// If case sensitivity is questionable
	var aval = eval(atext);
	var bval = eval(btext);
	if ( isUndefined(aval) ) {
		outTaggedText("fail", "Incorrect:  " + atext + " is undefined" );
	} else if ( isUndefined(bval) ) {
		outTaggedText("fail", "Incorrect:  " + btext + " is undefined" );
	} else if (aval == bval) {
		outTaggedText ( "fail", "Incorrect-N:  " + atext + " is " + btext + ", but it should not be." );
	} else {
		if (aval.toString().toLowerCase() == bval.toString().toLowerCase()) {
			outTaggedText ( "maybe", "Maybe:  " + atext + " is not " + btext + ", but it only a difference in case.  Instead, it is " + aval + "." );
		} else {
			outTaggedText ( "pass", "Correct-N:  " + atext + " is not " + btext + ", but rather " + valToString(aval) + "." );
		}
	}
}

function shouldBeUndefined ( atext ) {
	aval = eval(atext);
	if (isUndefined(aval)) {
		outTaggedText ( "pass", "Correct:  " + atext + " is undefined." );
	} else {
		outTaggedText ( "fail", "Incorrect:  " + atext + " is not undefined, but it should be.  Instead, it is " + valToString(aval) + "." );
	}
}

function shouldThrow ( ctext, extext ) {
	var caught = false;
	var exval = eval(extext);
	var cval;
	try {
		outText("Trying " + ctext + " :");
		cval = eval(ctext);
	} catch (ex) {
		caught = true;
		if (ex.code == exval) {
			outTaggedText("pass", "Correct:  " + ctext + " threw the exception " + extext + ".");
		} else {
			outTaggedText("fail", "Incorrect:  " + ctext + " threw the exception " + ex + "(code " + ex.code + ") when it should have thrown " + extext + ".");
		}
	}
	if (! caught) {
		outTaggedText("fail", "Incorrect:  " + ctext + " did not throw an exception, but instead returned the value " + cval + ".  It should have thrown the exception " + extext + ".");
	}
}

// for QA automation

function createHTMLElem(tagName) {
	if (isXML) {
		// XXX nonstandard, temporary
		return document.createElementWithNameSpace(tagName, HTMLns);
	} else {
		return document.createElement(tagName);
	}
}

function writeResults() {
	// THIS PROBABLY WON'T WORK YET FOR THE XML TESTS - it's untested

	if (automatedTesting && testToScreen) {
		document.open();
		document.write(output);
		document.close();
		return;
	}

	isXML = (! document.getElementById);
	HTMLns = "http://www.w3.org/TR/REC-html40";

	bodyElem = document.documentElement.lastChild;
	while ( (bodyElem) && (bodyElem.nodeName.toLowerCase() != "body")) {
		bodyElem = bodyElem.previousSibling;
		}
	if (bodyElem) {
		/*
		// create a new BODY element, blank
		bodyElem =
			bodyElem.parentNode.replaceChild(createHTMLElem("body"), bodyElem);
		*/

		formElem = createHTMLElem("form");
		if (noServerTesting) {
			formElem.setAttribute("method", "get");
		} else {
			formElem.setAttribute("method", "post");
		}
		formElem.setAttribute("action", "/ngdriver/cgi-bin/writeresults.cgi");
		bodyElem.appendChild(formElem);

		textAreaElem = createHTMLElem("textarea");
		textAreaElem.setAttribute("name", "textarea");
		formElem.appendChild(textAreaElem);
		textAreaElem.value = output;

		inputElem = createHTMLElem("input");
		inputElem.setAttribute("name", "resultsfile");
		if (noServerTesting) {
			inputElem.setAttribute("value", "a_filename");
		} else {
			inputElem.setAttribute("value",
				window.opener.document.resultsform.resultsfile.value);
		}
		formElem.appendChild(inputElem);

		formElem.submit();
	} /* else, out of luck */
}

function testsFailed() {
	// XXX What to do if we can't run tests??
dump("Tests failed.\n");
	writeResults();
}

function RunAuto() {
	// Writes Test Filename and Creates Table
	TestCaseNumber = 0;
	if (document.title) {
		titleText = document.title;
	} else {
		titleText = "";
		var nl = document.getElementsByTagName("title");
		if (nl.length > 0) {
			titleElem = nl.item(0);
			if ((titleElem.firstChild) && (titleElem.firstChild.nodeType == 3)){
				titleText = titleElem.firstChild.data;
			}
		}
	}
	output = '<H3>' + titleText + '</H3>' + '<TABLE BORDER=1><TBODY>';
  
	// Writes Header
	output += '<TR><TD><B>Description</B></TD><TD><B>Pass</B></TD>' +
	         '<TD><B>Bug Number</B></TD><TD><B>Actual Result</B></TD></TR>';

	// auto-run the tests for the first submit button
	// XXX only submit one button per page!
	var inputs = document.getElementsByTagName("input");
	if (! inputs ) {
		testsFailed();
		return;
	}
	var firstinput = inputs.item(0)
	if ((! firstinput )
	 || (" " + firstinput != " [object HTMLInputElement]")
	 || (firstinput.type != "button")
	   ) {
		testsFailed();
		return;
	}
	// XXX bug 10013
// dump("Calling firstinput.click().\n");
	// firstinput.click(); // XXX Does this return immediately??
// dump("Finished firstinput.click().\n");
	eval(firstinput.getAttribute("onclick"));
	writeResults();
}

function Boot() {
	if (canOutput() && getOutputElement() ) { // XXX is this sufficient?
		RunAuto();
	} else {
		setTimeout("Boot()", 100);
	}
}

// only code that isn't a function:

// set up browser-detection for tests that crash/halt things
// some of this code is from
// http://developer.netscape.com/docs/examples/javascript/browser_type.html

var agt=navigator.userAgent.toLowerCase();

// Note: On IE5, these return 4, so use is_ie5up to detect IE5.
var is_major = parseInt(navigator.appVersion);
var is_minor = parseFloat(navigator.appVersion);

// Note: Opera and WebTV spoof Navigator.  We do strict client detection.
// If you want to allow spoofing, take out the tests for opera and webtv.
var is_nav  = ((agt.indexOf('mozilla')!=-1) && (agt.indexOf('spoofer')==-1)
            && (agt.indexOf('compatible') == -1) && (agt.indexOf('opera')==-1)
            && (agt.indexOf('webtv')==-1));

var is_ie   = (agt.indexOf("msie") != -1);

var is_nav5 = (is_nav && (is_major == 5));
var is_ie5  = (is_ie && (is_major == 4) && (agt.indexOf("msie 5.0")!=-1) );

// automated testing

noServerTesting=false;
testToScreen=false;
automatedTesting=false;
if (window.name == "testWindow") {
	automatedTesting = true;
}

// to test automatedTesting:
// automatedTesting=true;
// noServerTesting=true;
// testToScreen=true;

if (automatedTesting) {
	Boot();
}


