/*
 * Copyright 2009 Aplix Corporation
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

datenow = new Date();
var testrunID             = datenow.getTime(); // Date.now() does not work in Pocket IE
var outerMostAsyncTimeout = null;
var defaultTimeout        = 30000; /* ms */
var ajaxQueue             = new Array();
var testCaseQueue         = new Array();
var testQueue             = new Array();
var pendingAsync          = 0;
var pendingAjaxGets       = 0;
var pendingFeatures       = 0;
var processTestCaseEnd    = 0;
var processTestQID        = null;
var processTestCaseQID    = null;
var processAjaxQID        = null;
var scheduleTestCaseDQID  = null;
var scheduleTestDQID      = null;
var scheduleAjaxDQID      = null;
var failAsyncID           = null;

function getTestPrefix() {
	var testprefix = "";
	if(window.location.host == "") {
		if(bondi) {
			// use hard-coded fallback
			testprefix = "http://tests.bondi.omtp.org/";
		}
		else {
			testprefix = "http://localhost/";
		}
	}
	else {
		var href = window.location.href;
		testprefix = href.substring(0, href.lastIndexOf("/") + 1);
	}
	return testprefix;
}

function ajaxGet(uri, completeCallback) {
	if(uri === undefined) {
		formatErrorMessage("Empty AJAX request");
		return;
	}
	if(uri.substring(0, 4) != "http") {
		var tmpUri = getTestPrefix() + uri;
		uri = tmpUri;
	}
	var xhr = null;
	try {
		xhr = new ActiveXObject('Bondi.BondiXMLHTTP');
	}
	catch(e0) {
		try {
			xhr = new ActiveXObject('Msxml2.XMLHTTP');
		}
		catch(e1) {
			try {
				xhr = new ActiveXObject('Microsoft.XMLHTTP');
			}
			catch(e2) {
				try {
					xhr = new XMLHttpRequest();
				}
				catch(e3) {
					xhr = null;
				}
			}
		}
	}
	if(xhr !== null) {
		pendingAjaxGets++;
		xhr.onreadystatechange = function() {
			if(xhr.readyState == 4) {
				pendingAjaxGets--;
				if(xhr.status == 200) {
					formatDebugMessage("AJAX GET OK: " + uri);
					completeCallback(xhr.responseText);
				}
				else {
					formatErrorMessage("AJAX GET ERROR: " + uri + "(" + xhr.status + ")");
				}
			}
		};
		xhr.open("GET", uri, true);
		xhr.send(null);
	}
	else {
		formatErrorMessage("AJAX not supported");
	}
	return xhr;
}

function addDiv(parent, id, content) {
	var newDiv = document.createElement("div");
	try {
		newDiv.id = id;
		parent.appendChild(newDiv);
		newDiv.innerHTML = content;
	}
	catch(e) {
		formatErrorMessage("error during div creation: " + e.message);
	}
	return newDiv;
}

function scheduleTestCaseDequeue() {
	if(scheduleTestCaseDQID != null) {
		clearTimeout(scheduleTestCaseDQID);
		scheduleTestCaseDQID = null;
	}
	if(pendingFeatures > 0 || pendingAjaxGets > 0) {
		scheduleTestCaseDQID = setTimeout(scheduleTestCaseDequeue, 100);
		return;
	}
	if(processTestCaseQID != null) {
		clearTimeout(processTestCaseQID);
		processTestCaseQID = null;
	}
	if(testCaseQueue.length > 0) {
		processTestCaseQID = setTimeout(
			function() {
				if(processTestCaseQID != null) {
					clearTimeout(processTestCaseQID);
					processTestCaseQID = null;
				}
				while(testQueue.length > 0) {
					var drop = testQueue.shift();
					formatDebugMessage("Drop: " + drop.name);
				}
				if(testCaseQueue.length > 0) {
					var testDiv = document.getElementById("test");
					if(testDiv === null) {
						testDiv = addDiv(document.getElementByTagName("body")[0], "test", "");
					}
					prepareTestCase(testDiv, testCaseQueue.shift());
				}
			}, 0);
	}
	else { formatPassMessage("Test suite finished"); }
}

/**
 * AJAX requests are queued and processed one at a time to support that eval'ed
 * responses can have dependencies to already retrieved documents in the
 * request queue.
 */
function scheduleAjaxDequeue() {
	if(scheduleAjaxDQID != null) {
		clearTimeout(scheduleAjaxDQID);
		scheduleAjaxDQID = null;
	}
	if(ajaxQueue.length > 0) {
		processAjaxQID = setTimeout(
			function() {
				if(processAjaxQID != null) {
					clearTimeout(processAjaxQID);
					processAjaxQID = null;
				}
				ajaxGet(ajaxQueue.shift(),
					function(jsText) {
						try {
							eval(jsText);
						}
						catch(ex) {
							formatErrorMessage("Failed evaluating: " + ex);
						}
						scheduleAjaxDQID = setTimeout(scheduleAjaxDequeue, 0);
					}
				);
			}, 0);
	}
}

function prepareTestCase(parent, testCase) {
	if(isArray(testCase)) {
		for(var i = 0; i < testCase.length; i++) {
			ajaxQueue.push(testCase[i]);
		}
	}
	scheduleAjaxDQID = setTimeout(scheduleAjaxDequeue, 0);
}

function beginAsync(timeout) {
	if(timeout === undefined) {
		timeout = defaultTimeout;
	}
	var now = new Date().getTime();
	if(pendingAsync == 0) {
		outerMostAsyncTimeout = now + timeout;
	}
	else if(now + timeout > outerMostAsyncTimeout) {
		formatErrorMessage("Cannot recurse into asynchronous operation timing out later than outer-most asynchronous operation");
		return;
	}
	if(failAsyncID == null) {
		failAsyncID = setTimeout(
				function() {
					if(failAsyncID != null) {
						clearTimeout(failAsyncID);
						failAsyncID = null;
					}
					formatErrorMessage("aynchronous test timed out");
					while(pendingAsync > 0) {
						endAsync();
					}
				}, timeout);
	}
	pendingAsync++;
}

function endAsync() {
	if(pendingAsync == 0 || --pendingAsync == 0) {
		if(failAsyncID != null) {
			clearTimeout(failAsyncID);
			failAsyncID = null;
		}
		scheduleTestDQID = setTimeout(scheduleTestDequeue, 0);
	}
}

function runTest(name, fn, ob) {
	try {
		var tmpPendingAsync = pendingAsync;
		fn(ob);
		if(pendingAsync == 0 || tmpPendingAsync > 0) {
			formatPassMessage("Passed: '" + name + "'");
		}
	}
	catch(e) {
		formatErrorMessage("Failed: '" + name + "': " + e.message);
		if(pendingAsync > 0) {
			endAsync();
		}
	}
}

function scheduleTestDequeue() {
	if(scheduleTestDQID != null) {
		clearTimeout(scheduleTestDQID);
		scheduleTestDQID = null;
	}
	if(pendingFeatures > 0 || pendingAjaxGets > 0) {
		scheduleTestDQID = setTimeout(scheduleTestDequeue, 100);
		return;
	}
	if(processTestQID != null) {
		clearTimeout(processTestQID);
		processTestQID = null;
	}
	if(testQueue.length > 0) {
		processTestQID = setTimeout(
			function() {
				if(processTestQID != null) {
					clearTimeout(processTestQID);
					processTestQID = null;
				}
				/*
				 * In order to notify the framework that this test is returning
				 * asynchronously, call beginAsync() during runTest().
				 */
				var test = testQueue.shift();
				runTest(test.name, test.test);
				if(pendingAsync == 0) {
					/* call next test case from main context */
					scheduleTestDQID = setTimeout(scheduleTestDequeue, 0);
				}
			}, 0);
	}
	else {
		scheduleTestCaseDQID = setTimeout(scheduleTestCaseDequeue, 0);
	}
}

function runTestCaseOld(name, funcs) {
	if(isFunction(funcs)) {
		testQueue.push({"name": name, "test": funcs});
	}
	else if(isMap(funcs)) {
		for(var key in funcs) {
			testQueue.push({"name": key, "test": funcs[key]});
		}
	}
	else {
		formatErrorMessage("Test '" + name + "' argument is no function, or collection of functions or object");
	}
	scheduleTestDQID = setTimeout(scheduleTestDequeue, 0);
}

function runSuite() {
	while(testCaseQueue.length > 0) {
		formatErrorMessage("Test suite is already processing");
		return; // let existing suite run finish correctly
	}
	processTestCaseEnd = 0;
	if(tests !== undefined && tests.length > 0) {
		for(var i = 0; i < tests.length; i++) {
			testCaseQueue.push(tests[i]);
		}
		scheduleTestCaseDQID = setTimeout(scheduleTestCaseDequeue, 0);
	}
	else {
		formatErrorMessage("runTests: No test cases defined");
	}
}

function runSuites() {
	for(var i = 0; i < suites.length; i++) {
		if(document.menuform["suite" + i].checked) {
			ajaxQueue.push(suites[i]);
		}
	}
	document.getElementById("framework").innerHTML = "";
	scheduleAjaxDQID = setTimeout(scheduleAjaxDequeue, 0);
}

function chooseSuites() {
	var frameworkDiv = document.getElementById("framework");
	if(frameworkDiv === null) {
		frameworkDiv = addDiv(document.getElementByTagName("body")[0], "framework", "");
	}
	if(suites !== undefined) {
		frameworkDiv.innerHTML = "";
		for(var i = 0; i < suites.length; i++) {
			frameworkDiv.innerHTML += "<input type='checkbox' name='suite" + i + "' value='yes' checked>" + suites[i] + "</input><br/>";
		}
		frameworkDiv.innerHTML += '<input type="button" onclick="runSuites()" value="Run suites"/>';
	}
}

