// $Revision: 2540 $
function LoadHtml(doReplace, urlstring) {
	if (doReplace) {
		location.replace(urlstring);
	} else {
		location = urlstring;
	}
}

function ReportError(message, url, exception) {
	if (url != null) {
		message += "\nURL: " + url.toString();
	}
	errorReporter.Report("LoadScript.js", message, exception);
}

function Status(httpRequest) {
	try {
		if (httpRequest.status == null || httpRequest.statusText == null || httpRequest.statusText.length == 0) {
			return "Dropped connection";
		}
		var status = httpRequest.statusText;
		if (status.match(/^\d\d\d/)) {
			return status;
		}
		return httpRequest.status + ": " + httpRequest.statusText;
	} catch  (e) {
		return "Lost connection";
	}
}

function Inaccessible(server, httpRequest, url) {
    ReportNetworkError("Cannot access server " + server + ":", httpRequest, url);
}

function Error(httpRequest, url) {
    ReportNetworkError("Error:", httpRequest, url);
}

function ReportNetworkError(message, httpRequest, url) {
	switch (httpRequest.status) {
		case IE_TIMED_OUT:
			alert(fold(message + "\nThe request has timed out\nURL: " + url.toString()));
			return;
		case IE_CANNOT_CONNECT:
			alert(fold(message + "\nCannot connect\nURL: " + url.toString()));
			return;
		case IE_CONNECTION_ERROR:
			alert(fold(message + "\nConnection error\nURL: " + url.toString()));
			return;
		case IE_DROPPED_CONNECTION:
			alert(fold(message + "\nDropped connection\nURL: " + url.toString()));
			return;
		case IE_INVALID_SERVER_RESPONSE:
			alert(fold(message + "\nInvalid server response\nURL: " + url.toString()));
			return;
	}
	alert(fold(message + "\n" + Status(httpRequest) + "\nURL: " + url.toString()));
}

// RequestQueue object
function RequestQueue() {
	this.CONCURRENT_REQUESTS = 1;
	this.currentlyRunning = 0;
	this.q = new Array();
	this.order = new Array();
}

RequestQueue.prototype.add = function(obj) {
	var i;
	for (i = 0; i < this.q.length; i++) {
		if (this.q[i] == null) {
			this.q[i] = obj;
			this.order.push(i);
			return i;
		}
	}
	this.q.push(obj);
	var n = this.q.length - 1;
	this.order.push(n);
	return n;
}

RequestQueue.prototype.get = function(i) {
	return this.q[i];
}

RequestQueue.prototype.remove = function(i) {
	delete this.q[i];
}

RequestQueue.prototype.next = function() {
	var xqentry;
	do {
		if (this.order.length == 0) {
			return null;
		}
		var n = this.order.shift();
		xqentry = this.get(n);
	} while (xqentry == null);
	return xqentry;
}

RequestQueue.prototype.length = function() {
	return this.q.length;
}

RequestQueue.prototype.size = function() {
	var r = 0;
	for (var i = 0; i < this.q.length; i++) {
		if (this.q[i] != null) {
			r++;
		}
	}
	return r;
}

RequestQueue.prototype.process = function() {
	for (var i = this.currentlyRunning; i < this.CONCURRENT_REQUESTS; i++) {
		var nextQueueEntry = this.next();
		if (nextQueueEntry == null) {
			return;
		} else {
			var url = nextQueueEntry.url;
			var method = nextQueueEntry.method;
			this.currentlyRunning++;
			try {
				if (method == "POST") {
					url.MakeDynamic();
					nextQueueEntry.httpRequest.send(url.QueryString());
				} else if (method == "GET") {
					nextQueueEntry.httpRequest.send("");
				} else {
					ReportError("Unknown method \"" + method + "\"", url);
				}
			} catch (e) {
				if (Repeats.isRepetitive(url.toString())) {
					Repeats.repeat(url.toString());
				} else {
					ReportError("Cannot load XML: " + e.message, url);
				}
			}
		}
	}
}

RequestQueue.prototype.requestDone = function() {
	if (this.currentlyRunning < 1 || this.currentlyRunning > this.CONCURRENT_REQUESTS) {
		// Should never happen
		ReportError("Currently running requests: " + this.currentlyRunning);
	}
	this.currentlyRunning--;
}

var XQueue = new RequestQueue();

// QEntry object
function QEntry(httpRequest, method, url) {
	this.httpRequest = httpRequest;
	this.method = method;
	this.url = url;
}

// RepeatIntervals object - maps url to repeat interval
function RepeatIntervals() {
	// maximum repeat interval, ms
	this.MAX_REPEAT_INTERVAL = 300000;
	// raise interval by factor
	this.RAISE_FACTOR = 2;

	// normal repeat intervals
	this.ri = new Object();
	// exponential backoff repeat intervals object - maps url to repeat interval, backed off on failure
	this.ebri = new Object();
	// currently active timeouts for next repeat
	this.timeouts = new Object();
}

RepeatIntervals.prototype.setInterval = function(urlstring, interval) {
	urlstring = this.nonDynamic(urlstring);
	this.ri[urlstring] = interval;
	delete this.ebri[urlstring];
}

RepeatIntervals.prototype.clearInterval = function(urlstring) {
	if (urlstring != null) {
		urlstring = this.nonDynamic(urlstring);
		delete this.ri[urlstring];
		delete this.ebri[urlstring];
		clearTimeout(this.timeouts[urlstring]);
		delete this.timeouts[urlstring];
	}
}

RepeatIntervals.prototype.restoreInterval = function(urlstring) {
	urlstring = this.nonDynamic(urlstring);
	delete this.ebri[urlstring];
}

RepeatIntervals.prototype.raiseInterval = function(urlstring) {
	urlstring = this.nonDynamic(urlstring);
	var interval = this.ebri[urlstring];
	if (interval == null) {
		interval = this.ri[urlstring];
	}
	if (interval == null) {
		return;
	}
	this.ebri[urlstring] = Math.min(this.MAX_REPEAT_INTERVAL, (interval * this.RAISE_FACTOR));
}

RepeatIntervals.prototype.repeat = function(urlstring) {
	urlstring = this.nonDynamic(urlstring);
	var interval = this.ebri[urlstring];
	if (interval == null) {
		interval = this.ri[urlstring];
	}
	if (interval != null && !isNaN(interval)) {
		var timeout = setTimeout("DoLoadScript(new Url('" + urlstring + "'));", interval);
		this.timeouts[urlstring] = timeout;
	}
}

RepeatIntervals.prototype.isRepetitive = function(urlstring) {
	urlstring = this.nonDynamic(urlstring);
	var interval = this.ri[urlstring];
	return (interval != null && !isNaN(interval));
}

RepeatIntervals.prototype.size = function() {
	var n = 0;
	for (var urlstring in this.ri) {
		n++;
	}
	return n;
}

RepeatIntervals.prototype.nonDynamic = function(urlstring) {
	return urlstring.replace(/[?&]dynamic=[01].[0-9]+$/, "");
}

RepeatIntervals.prototype.report = function() {
	var s = "";
	for (var urlstring in this.ri) {
		s += "  urlstring = " + urlstring + "\n    ri[urlstring] = " + this.ri[urlstring] + "\n    ebri[urlstring] = " + this.ebri[urlstring] + "\n";
	}
	return s;
}

var Repeats = new RepeatIntervals();

// CancelScripts object
function CancelScripts() {
	this.scripts = new Array();
}

CancelScripts.prototype.Add = function(script) {
	this.scripts.push(script);
}

CancelScripts.prototype.RemoveAll = function() {
	this.scripts = new Array();
}

CancelScripts.prototype.Execute = function() {
	for (var i = 0; i < this.scripts.length; i++) {
		eval(this.scripts[i]);
	}
	this.RemoveAll();
}

var cancelScripts = new CancelScripts();

var _serverInaccessible = false;

var IE_ERROR_SERVER_INACCESSIBLE = -2146697211;
var IE_ERROR_NO_OUTPUT = -1072896680;
var IE_TIMED_OUT = 12002;
var IE_CANNOT_CONNECT = 12029;
var IE_CONNECTION_ERROR = 12030;
var IE_DROPPED_CONNECTION = 12031;
var IE_INVALID_SERVER_RESPONSE = 12152;

function _handleScript(xqentry) {
	handleScript(xqentry.url, xqentry.httpRequest, true);
}
	
function handleScript(url, httpRequest, doProcessQueue) {
	var urlString = url.toString();
	var xmlDoc = httpRequest.responseXML;
	var server = window.location.host;
	var isRepetitive = Repeats.isRepetitive(urlString);

	if (_serverInaccessible) {
		if (AYT(url)) {
			_serverInaccessible = false;
			if (isRepetitive) {
				Repeats.restoreInterval(urlString);
			}
		} else {
			if (isRepetitive) {
				Repeats.raiseInterval(urlString);
				Repeats.repeat(urlString);
			} else {
				Inaccessible(server, httpRequest, url)
				cancelScripts.Execute();
			}
			finishHandleXml();
			return;
		}
	}
	if (httpRequest.status != 200) {
		if (AYT(url)) {
			Error(httpRequest, url);
			if (!isRepetitive) {
				cancelScripts.Execute();
			}
		} else {
			_serverInaccessible = true;
			if (isRepetitive) {
				Repeats.raiseInterval(urlString);
				Repeats.repeat(urlString);
			} else {
				Inaccessible(server, httpRequest, url);
				cancelScripts.Execute();
			}
		}
		finishHandleXml();
		return;
	}
	if (httpRequest.responseText == null || httpRequest.responseText.length == 0)
	{
		ReportError("No XML output", url);
		if (!isRepetitive) {
			cancelScripts.Execute();
		}
		finishHandleXml();
		return;
	}
	var contentType = httpRequest.getResponseHeader("Content-Type");
	if (contentType != null && contentType.length > 0 && contentType.indexOf("text/xml;") != 0) {
		ReportError("Expected content type \"text/xml\", but received \"" + contentType + "\"", url);
		if (!isRepetitive) {
			cancelScripts.Execute();
		}
		finishHandleXml();
		return;
	}

	if (xmlDoc.parseError) {
		// IE cases
		if (xmlDoc.parseError.errorCode != 0) {
			if (xmlDoc.parseError.errorCode == IE_ERROR_SERVER_INACCESSIBLE) {
				if (!AYT(url)) {
					_serverInaccessible = true;
					if (isRepetitive) {
						Repeats.raiseInterval(url.toString());
						Repeats.repeat(urlString);
					} else {
						Inaccessible(server, httpRequest, url);
						cancelScripts.Execute();
					}
				}
			} else if (xmlDoc.parseError.errorCode == IE_ERROR_NO_OUTPUT) {
				ReportError("No XML output", url);
				if (!isRepetitive) {
					cancelScripts.Execute();
				}
			} else {
				ReportError("XML error: " + xmlDoc.parseError.reason + "\nscript: \"" + httpRequest.responseText + "\"", url);
				if (!isRepetitive) {
					cancelScripts.Execute();
				}
			}
			finishHandleXml();
			return;
		}
		// otherwise, go on and process
	} else {
		// Gecko cases
		if (xmlDoc.documentElement == null) {
			if (AYT(url)) {
				ReportError("Expected XML, but received HTML", url);
			} else {
				_serverInaccessible = true;
				if (isRepetitive) {
					Repeats.raiseInterval(urlString);
					Repeats.repeat(urlString);
				} else {
					Inaccessible(server, httpRequest, url);
					cancelScripts.Execute();
				}
			}
			finishHandleXml();
			return;
		} else if (xmlDoc.documentElement.tagName == "parsererror") {
			ReportError(xmlDoc.documentElement.firstChild.data, url);
			if (!isRepetitive) {
				cancelScripts.Execute();
			}
			finishHandleXml();
			return;
		}
		// otherwise, go on and process
	}

	var jsNode = xmlDoc.getElementsByTagName("javascript")[0];
	if (jsNode == null) {
		ReportError("XML with no javascript node:\n" + httpRequest.responseText, url);
		if (!isRepetitive) {
			cancelScripts.Execute();
		}
		finishHandleXml();
		return;
	}
	
	if (doProcessQueue) {
		XQueue.requestDone();
		XQueue.process();
	}

	if (!jsNode.hasChildNodes()) {
		return;
	}
	var jsChild = jsNode.childNodes[0];
	if (jsChild.nodeType != 4) {
		jsChild = jsChild.nextSibling;
	}
	var script = jsChild.nodeValue;
	handlerDebug(isRepetitive, url, script);

	if (script && script.length > 0) {
		if (script.match(/Repeats\.clearInterval\(\)/) != null) {
			// Request to clear the repeat interval for this URL.
			// We catch it here because we know the URL here.
			// Repeats.clearInterval() with no argument actually does nothing.
			Repeats.clearInterval(urlString);
		}
		try {
			eval(script);
		}
		catch (e) {
			ReportError(e.name + ": " + e.message + "\nscript: \"" + script + "\")");
			if (!isRepetitive) {
				cancelScripts.Execute();
			}
		}
	}

	// Explicitly check again because script may have contained Repeats.clearInterval().
	if (Repeats.isRepetitive(urlString)) {
		Repeats.repeat(urlString);
	}
}

function finishHandleXml() {
	_cancelCurrentRequest = false;
	XQueue.requestDone();
	XQueue.process();
}

var _handlerDebugRepetitive = false;
var _handlerDebugNonRepetitive = false;
var _everyHowMany = 10;

var _handlerCount = 0;
var _lastTime = (new Date()).valueOf();

function handlerDebug(isRepetitive, url, script) {
	var s = "";
	if (_handlerDebugRepetitive && isRepetitive) {
		_handlerCount++;
		if (_handlerCount % _everyHowMany == 0) {
			var now = (new Date()).valueOf();
			s = "url = " + url.toString() + "\n";
			s += "total " + _handlerCount + " times\n";
			s += _everyHowMany + " times in " + ((now - _lastTime) / 1000) + " sec.: " + script + "\n";
			s += "Queue length = " + XQueue.length() + " size = " + XQueue.size() + "\n";
			s += "Repeats = \n" + Repeats.report();
			alert(fold(s));
			// must follow alert call, so that time measurement does not include alert.
			_lastTime = (new Date()).valueOf();
		}
	} else if (_handlerDebugNonRepetitive && !isRepetitive) {
		s = "url = " + url.toString() + "\n";
		s += "script = " + script + "\n";
		s += "Queue length = " + XQueue.length() + " size = " + XQueue.size();
		alert(fold(s));
	}
}

function AYT(url) {
	if (LOADSCRIPT_URL == null) {
		errorReporter.Report("LoadScript.js", "AYT(\"" + url.toString() + "\"): LOADSCRIPT_URL is not set.");
		return;
	}
	var ayt_url =(new Url(LOADSCRIPT_URL)).Append("command", "areyouthere");
	var httpRequest = createHttpRequest();
	try {
		httpRequest.open("GET", ayt_url, false);
		httpRequest.send("");
		if (httpRequest.status != 200) {
			return false;
		}
	} catch (e) {
		return false;
	}
	// no errors occurred, server must be OK
	return true;
}

function DelayLoadScript(urlstring, delay) {
	setTimeout("LoadScript(new Url('" + urlstring + "');", delay);
}

function LoadScriptSynchronously(urlstring) {
	var url = new Url(urlstring);
	url.MakeDynamic();
	var httpRequest = createHttpRequest();
	try {
		httpRequest.open("GET", url.toString(), false);
		httpRequest.send("");
		if (httpRequest.status != 200) {
			ReportError("LoadScriptSynchronously: " + Status(httpRequest), url)
		}
		handleScript(url, httpRequest, false);
	} catch (e) {
		ReportError("LoadScriptSynchronously", url, e)
	}
}

function LoadScript(urlstring, interval) {
	var already = Repeats.isRepetitive(urlstring);
	if (interval && !isNaN(interval)) {
		Repeats.setInterval(urlstring, interval);
	}
	if (!already) {
		DoLoadScript(new Url(urlstring));
	}
	return null;
}

// Short requests are sent using GET, longer ones using POST, for the following reasons:
// 1. GET is intended for URLs that always return the same output; POST is intended for
//    dynamically generated output.
// 2. There is a practical limit of approximately 4kb on the length of a URL.
// 3. POST cannot always be used, because if IE6 is simultaneously loading many images
//    (IMG tags), it sometimes sends the dynamic update POST with no data.
//    See also "BUG: Internet Explorer Stops Responding When You Download Images"
//    http://support.microsoft.com/default.aspx?scid=kb;en-us;269802
// This is heuristic, and we cannot always prevent the IE failure described in 3.

var MAX_GET_URL = 512;

function DoLoadScript(url) {
	var n;
	var xqentry;
	var httpRequest = createHttpRequest();
	url.MakeDynamic();
	if (url.length > MAX_GET_URL) {
		httpRequest.open("POST", url.Uri(), true);
		httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
		xqentry = new QEntry(httpRequest, "POST", url);
	} else {
		httpRequest.open("GET", url.toString(), true);
		xqentry = new QEntry(httpRequest, "GET", url);
	}
	n = XQueue.add(xqentry);
	var f = "var xqentry = XQueue.get(" + n + ");" +
			"if (xqentry.httpRequest.readyState == 4) {" +
			"XQueue.remove(" + n + ");" +
			"_handleScript(xqentry);" +
			"}";
	httpRequest.onreadystatechange = new Function(f);
	try {
		XQueue.process();
    } catch (e) {
        ReportError("DoLoadScript", null, e);
    }
}

function createHttpRequest() {
	var httpRequest = null;
    try {
	    if (window.XMLHttpRequest) {
	        // Mozilla and IE 7
		        httpRequest = new XMLHttpRequest();
	    } else if (window.ActiveXObject) {
		    // IE 6
		    httpRequest = new ActiveXObject("Microsoft.XMLHTTP");
	    } else {
	        ReportError("Cannot create XMLHttpRequest in this browser.");
	        return null;
        }
    } catch (e) {
        ReportError("createHttpRequest", null, e);
    }
	return httpRequest;
}

function LoadScriptWithFormParams(urlstring, formId, formParams, interval) {
	var form = getElement(unEscape(formId));
	LoadScriptWithFormParamsAndForm(urlstring, form, formParams, interval);
}

function LoadScriptWithFormParamsAndForm(urlstring, form, formParams, interval) {
	var url = new Url(urlstring);
	url.AppendFormParams(form, formParams);
	LoadScript(url.toString(), interval);
}
