/**
 * This module contains general utility functions.
 */
var log = new Log(Log.ERROR, Log.consoleLogger);
//Behaviour.addUnloadEvent(function() { log.warn('Setting ignoreErrors to true in unload'); ignoreErrors = true; });
//var log = new Log(Log.DEBUG, Log.consoleLogger);

var ERROR_HANDLING_IGNORE = 0;
var ERROR_HANDLING_LOG = 1;
var ERROR_HANDLING_SHOW = 2;

var currentZIndex = 199;

/* Use to stop timed Ajax calls */
var ajaxRequestErrors = 0;

/**
 * Reload the current page.
 * TODO: There seem to be a lot of different ways to do this;
 * verify that this is the best way.
 */
function reloadPage() {
	window.location.reload(true);
}

function go(url, newWindow) {
	if (newWindow) {
		var aNewWindow = window.open(url,"_blank");
		aNewWindow.focus();
	}
	else {
		window.location.href = url;
	}
}

/**
 * Given a function, return its name.
 */
function funcname(f) {
	var s = f.toString().match(/function (\w*)/)[1];
	if ((s == null) || (s.length==0)) return "anonymous";
	return s;
}


/**
 * See if a JavaScript function by a given name actually exists.
 */
function functionExists(func) {
	try {
		return typeof(eval(func)) == 'function';
	} catch (ex) {
		return false;
	}
}


/**
 * Calculate the height of the browser window in a nice cross-platformy way.
 */
function getWindowHeight() {
	var height = 0;
	if (typeof(window.innerWidth) == "number") {
		height = window.innerHeight;
	} else {
		if (document.documentElement && document.documentElement.clientHeight) {
			height = document.documentElement.clientHeight;
		} else {
			if (document.body&&document.body.clientHeight) {
				height = document.body.clientHeight;
			}
		}
	}
	return height;
}

/**
 * Calculate the width of the browser window in a nice cross-platformy way.<b> 
 */
function getWindowWidth() {
	var width = 0;
	if (typeof(window.innerWidth) == "number") {
		width = window.innerWidth;
	} else {
		if (document.documentElement && document.documentElement.clientWidth) {
			width = document.documentElement.clientWidth;
		} else {
			if (document.body&&document.body.clientWidth) {
				width = document.body.clientWidth;
			}
		}
	}
	return width;
}

/**
 * Like Element.show in Prototype, but always sets display to block.
 */
/** For some reason in the new prototype library, doing the Object.extend thing isn't working **/
Element['showBlock'] = 
	function() {
	    for (var i = 0; i < arguments.length; i++) {
	      var element = $(arguments[i]);
	      element.style.display = 'block';
	    }
	};

/**
 * Like Element.toggle in Prototype, but always sets to block.
 */
Element['toggleBlock'] = 
	function() {
	    for (var i = 0; i < arguments.length; i++) {
	      var element = $(arguments[i]);
	      if (element.style.display != 'block') {
		      element.style.display = 'block';
		  } else {
		  	element.style.display = 'none';
		  }
	    }
	};
	
/**
 * Return the given string with leading and trailing whitespace removed.
 */
function trim(str) {
	return str.replace(/^\s*|\s*$/g,"");
}

/**
 * Given an element and a tag, find the closest ancestor
 * that is of the type specified by the tag, for example:
 * <div id="1"><span id="2"><input id="3"/></span></div>
 * findParent('3','div') would return the div
 * findParent('3','span') would return the span
 * findParent('3','p') would return null
 */
function findParent(element, tag) {
	element = $(element);
	var parentNode = element.parentNode;
	while (parentNode) {
		if (parentNode.tagName && parentNode.tagName.toLowerCase() == tag.toLowerCase()) {
			return parentNode;
		}
		parentNode = parentNode.parentNode;
	}
	return null;
}

/**
 * Given an element and a tag, find the closest ancestor
 * that is of the specified class, for example:
 * <div id="1" class="c1"><div id="2" class="c2"><input id="3" class="c3"/></div></div>
 * findParent('3','c2') would return div 2
 * findParent('3','c1') would return div 1
 * findParent('3','c0') would return null
 */
function findParentByClass(element, className) {
	element = $(element);
	var parentNode = element.parentNode;
	while (parentNode) {
		if (Element.hasClassName(parentNode, className)) {
			return parentNode;
		}
		parentNode = parentNode.parentNode;
	}
	return null;
}

/**
 * Determine if the given parent is an ancestor
 * of the given child.
 */
function hasParent(child, parent) {
	child = $(child);
	parent = $(parent);
	if (child == parent) return true;
	
	var parentNode = child.parentNode;
	while (parentNode) {
		if (parentNode == parent) {
			return true;
		}
		parentNode = parentNode.parentNode;
	}
	return false;
}

function getNextObject(element) {
	var n = element;
	do n = n.nextSibling;
	while (n && n.nodeType != 1);
	return n;
}

function getPreviousObject(element) {
	var p = element;
	do p = p.previousSibling;
	while (p && p.nodeType != 1);
	return p;
}

/**
 * Returns the current cursor position.
 * TODO: this is a little flaky.
 */
function getCursorPosition(e) {
    e = e || window.event;
    var cursor = {x:0, y:0};
    if (e.pageX || e.pageY) {
        cursor.x = e.pageX;
        cursor.y = e.pageY;
    } 
    else {
        cursor.x = e.clientX + 
            (document.documentElement.scrollLeft || 
            document.body.scrollLeft) - 
            document.documentElement.clientLeft;
        cursor.y = e.clientY + 
            (document.documentElement.scrollTop || 
            document.body.scrollTop) - 
            document.documentElement.clientTop;
    }
    return cursor;
}

/**
 * Adds a parameter to a URL with a random value to prevent
 * the URL from being cached. Ajax calls seems susceptible to
 * caching by some proxies and that won't do.
 * The onlyParameter parameter indicates if the URL will contain
 * other parameters or not; if false, the return parameter will
 * have a leading ?, otherwise a leading &.
 */
function randomizeURL(onlyParameter) {
	var param = '';
	if (! onlyParameter) {
		param = '&';
	}
	return param + 'r1337=' + Math.random();
}

/**
 * Given a query string, return a map or associative array
 * containing the attributes and their values.
 */
function parseQueryParams(params) {
	var firstChar = params.substring(0,1);
	if (firstChar == '?' || firstChar == '&' || firstChar == '#') {
		params = params.substring(1);
	}
	params = params.replace("&amp;","&");
	var split = params.split('&');
	var map = new Array();
	for (var i=0; i < split.length; ++i) {
		var param = split[i];
		var split2 = param.split('=');
		map[split2[0]] = split2[1];
	}
	return map;
}

/**
 * Given a JSON string, return a query string, for example:
 * { "foo" : "bar", "baz" : "bla" }
 * would return ?foo=bar&baz=bla
 */
String.prototype.JSONToQueryString = function () {
	var map = this.parseJSON();
	var queryString = '';
	var first = true;
	for (var entry in map) {
		if (entry == 'extend' || entry == 'toJSONString') {
			continue;
		}
		if (! first) {
			queryString = queryString + '&';
		} else {
			first = false;
		}
		queryString = queryString + entry + '=' + escape(map[entry]);
	}
	return queryString;
}

/**
 * Set a user preference with an async ajax call.
 * TODO: use asyncAjaxCall
 */
function setUserOption(option, value) {

	var myAjax = new Ajax.Request(buildSetUserOptionLink(),
		{
			method: 'get', 
			parameters: 'option=' + option + '&value=' + value + randomizeURL(false),
			onComplete : function(req) { 
				log.debug('setUserOption: complete: ' + req.responseText); 
			} 
		}
	);
}

/**
 * Set the title of an html element to the contents and the contents to blank.
 * This is used a lot for image replacement where we have <a href="...">Text</a>
 * and we want the anchor to show an image with the mouseover showing the original
 * text. 
 * TODO: we can do this on the server side and use a style of text-heigh 0 or something
 * to hide the inner part; should be faster and avoid seeing the text on slow machines
 * before it gets moved
 */
function innerToTitle(element) {
	/*
	var spans = document.getElementsByClassAndTag('translation', 'span',element);
	if (spans && spans.length > 0) {
		element.title = trim(spans[0].innerHTML);
		element.innerHTML = '';
	} else 
	*/
	if (! Element.empty(element)) {
		element.title = trim(element.innerHTML.stripTags());
		//element.innerHTML = '';
	}
}

/**
 * Given a popup form display value, which consists of
 * an anchor and a JSON string wrapped in a span called
 * 'data', return the JSON string as an associative array.
 */
function getPopupFormMetaData(element) {
	return document.getElementsByClassAndTag('data', 'span', findParent(element, 'span', 'configure_field'))[0].innerHTML.parseJSON();
}

/**
 * See above. TODO: this may not be used.
 */
function getPopupWindowMetaData(element) {
	return document.getElementsByClassAndTag('data','span',findParent(element, 'span', 'popup_window'))[0].innerHTML.parseJSON();
}

/**
 * The reverse of getPopupFormMetaData; given the element and the
 * map, build the JSON string and populate the data element.
 */
function setPopupFormMetaData(element, data) {
	return document.getElementsByClassAndTag('data','span',findParent(element, 'span', 'configure_field'))[0].innerHTML = data.toJSONString();
}

/**
 * Escape an html string at least enough to avoid xml conflicts with < and >.
 */
function escapeHTML(text) {
	var newText = text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
	return newText;
}

/**
 * Make an asynchronous Ajax call.
 *
 * name - a descriptive name to help with debugging
 * path - the URL
 * parameters - the query string, starting with '?'
 * method - get or post
 * completeFunc - the function to call when the request completes successfully; parameter
 * is the data part of the return value, which should be something like <ajax-response>data</ajax-response>
 * errorFunc - the function to call when the request fails; parameter is the full request object
 * since we may need the status code
 * errorHandling - one of the error handling constants above indicating how to log/display any errors
 */
function asyncAjaxCall(name, path, parameters, method, completeFunc/*(xml)*/, errorFunc/*(req[, exception])*/, errorHandling) {
	log.debug('asyncAjaxCall: ' + name + ' path: ' + path + ' parameters: ' + parameters);
	if (! method) {
		method = 'post';
	}
	
	if (parameters && parameters.length > 0) {
		parameters = parameters + randomizeURL(false);
	} else {
		parameters = randomizeURL(true);
	}
	
	var onFailure = function(req) {
		log.debug('asyncAjaxCall: onFailure');
		processAjaxError(errorHandling, 'error', name, new Array('ajax.failure'), dumpAjaxResponse(req));
		if (errorFunc) {
			errorFunc(req);
		}
	}
	
	var onException = function(req, exception) {
		log.debug('asyncAjaxCall: onFailure');
		processAjaxError(errorHandling, 'error', name, new Array('ajax.exception'), dumpAjaxResponse(req.transport, exception));
		if (errorFunc) {
			errorFunc(req, exception);
		}
	}	
	
	var onComplete = function(req) {
		if (req.status != 200) {
			ajaxRequestErrors++;
			return;
		} else {
			ajaxRequestErrors = 0;
		}
		var xml = convertAjaxResponseToXml(req.responseText);
		log.debug('asyncAjaxCall: converted xml: ' + displayXmlText(printXml(xml)));
	
		if (! xml) {
			log.debug('asyncAjaxCall: processing bad xml error');
			processAjaxError(errorHandling, 'error', name, new Array('ajax.bad.xml'), dumpAjaxResponse(req));
			if (errorFunc) {
				errorFunc(req);
			}
			return;
		}
	
		if (processXmlErrors(errorHandling, xml, name, req) && errorHandling == ERROR_HANDLING_SHOW) {
			if (errorFunc) {
				errorFunc(req);
			}
		} else {
			if (completeFunc) {
				completeFunc(xml);
			}
		}
	}
	
	log.debug('asyncAjaxCall: path: ' + path + '?' + parameters);
	var myAjax = new Ajax.Request(path,
		{
			method: method,
			asynchronous: true,		
			parameters: parameters,
			onComplete: onComplete,
			onFailure: onFailure,
			onException: onException
		});

}

function asyncAjaxCallText(name, path, parameters, method, completeFunc/*(text)*/, errorFunc/*(req[,exception])*/, errorHandling) {
	log.debug('asyncAjaxCallText: ' + name + ' path: ' + path + ' parameters: ' + parameters);
	if (! method) {
		method = 'post';
	}
	
	if (parameters && parameters.length > 0) {
		parameters = parameters + randomizeURL(false);
	} else {
		parameters = randomizeURL(true);
	}
	
	var onFailure = function(req) {
		log.debug('asyncAjaxCall: onFailure');
		processAjaxError(errorHandling, 'error', name, new Array('ajax.failure'), dumpAjaxResponse(req));
		if (errorFunc) {
			errorFunc(req);
		}
	}
	
	var onException = function(req, exception) {
		log.debug('asyncAjaxCall: onFailure');
		processAjaxError(errorHandling, 'error', name, new Array('ajax.exception'), dumpAjaxResponse(req.transport, exception));
		if (errorFunc) {
			errorFunc(req, exception);
		}
	}	
	
	var onComplete = function(req) {
		if (req.status != 200) {
			ajaxRequestErrors++;
			return;
		} else {
			ajaxRequestErrors = 0;
		}
		if (completeFunc) {
			completeFunc(req.responseText);
		}
	}
	
	log.debug('asyncAjaxCallText: path: ' + path + '?' + parameters);
	var myAjax = new Ajax.Request(path,
		{
			method: method,
			asynchronous: true,		
			parameters: parameters,
			onComplete: onComplete,
			onFailure: onFailure,
			onException: onException
		});

}

/**
 * Make a synchronous Ajax call.  The parameters are similar to asyncAjaxCall with
 * the addition of rootNodeName, which indicates the top level xml element to
 * extract and return.  There is no completeFunc or errorFunc because this will
 * either return the xml snippet or null if there is an error.
 */
function syncAjaxCall(name, path, parameters, method, errorHandling, rootNodeName) {
	log.debug('syncAjaxCall: ' + name);
	if (! method) {
		method = 'post';
	}
	
	if (parameters && parameters.length > 0) {
		parameters = parameters + randomizeURL(false);
	} else {
		parameters = randomizeURL(true);
	}
	
	log.debug('syncAjaxCall: path: ' + path + '?' + parameters);
	var myAjax = new Ajax.Request(path,
		{
			method: method,
			asynchronous: false,		
			parameters: parameters
		});
		
	var req = myAjax.transport;
	if (req.status != 200) {
		ajaxRequestErrors++;
		log.debug('syncAjaxCall: processing ' + req.status + ' error');
		processAjaxError(errorHandling, 'error', name, new Array('ajax.bad.response'), dumpAjaxResponse(req));
		return null;
	} else {
		ajaxRequestErrors = 0;
	}
	
	log.debug('syncAjaxCall: responseText: ' + displayXmlText(req.responseText));
	
	var xml = convertAjaxResponseToXml(req.responseText, rootNodeName);
	log.debug('syncAjaxCall: converted xml: ' + displayXmlText(printXml(xml)));
	
	if (! xml) {
		log.debug('syncAjaxCall: processing bad xml error');
		processAjaxError(errorHandling, 'error', name, new Array('ajax.bad.xml'), dumpAjaxResponse(req));
		return null;
	}
	
	if (processXmlErrors(errorHandling, xml, name, req) && errorHandling == ERROR_HANDLING_SHOW) {
		return null;
	} else {
		return xml;
	}
}

/**
 * Make synchronous Ajax call and return the responseText value of the request object.
 * This is useful when you're not sure if valid xml will be returned.
 */
function syncAjaxCallText(name, path, parameters, method, errorHandling) {
	log.debug('syncAjaxCallText: ' + name);
	if (! method) {
		method = 'post';
	}
	
	if (parameters && parameters.length > 0) {
		parameters = parameters + randomizeURL(false);
	} else {
		parameters = randomizeURL(true);
	}
	
	log.debug('syncAjaxCallText: path: ' + path + '?' + parameters);
	var myAjax = new Ajax.Request(path,
		{
			method: method,
			asynchronous: false,		
			parameters: parameters
		});
		
	var req = myAjax.transport;
	if (req.status != 200) {
		ajaxRequestErrors++;
		log.debug('syncAjaxCallText: processing ' + req.status + ' error');
		processAjaxError(errorHandling, 'error', name, new Array('ajax.bad.response'), dumpAjaxResponse(req));
		return null;
	} else {
		ajaxRequestErrors = 0;
	}
	
	return req.responseText;
}

/**
 * Look through the given xml document for tags like <error>
 * and display/log them based on the errorHandling parameter.<b> 
 * Name is for debugging and req allows the full request object
 * to be dumped if there are any errors.
 */
function processXmlErrors(errorHandling, xml, name, req) {
	try {
		log.debug('processXmlErrors: checking for errors');
		var errors = xml.getElementsByTagName('error');
		if (errors && errors.length > 0) {
			log.debug('Found ' + errors.length + ' errors.');
			var list = new Array();
			for (var i=0; i < errors.length; ++i) {
				var error = trim(getNodeTextValue(errors[i]));
				if (! error || error == '') {
					continue;
				}
				list.push(getNodeTextValue(errors[i]));
			}
			log.debug('processXmlErrors: found ' + list.length + ' errors');
			if (list.length > 0) {
				log.debug('processXmlErrors: no errors to process');
				processAjaxError(errorHandling, 'error', name, list, dumpAjaxResponse(req));
				return true;
			}
		}
	} catch (ex) {}
	
	return false;
}

/**
 * Given a text representation of an xml response, return a real xml document
 * starting with the rootNodeName. This is necessary because some firewalls
 * or anti-virus utilities will inject their own scripts and stuff into our
 * nice valid xml responses, which turn them into non-valid xml, which cause
 * big problems if we try to treat them as xml. So this will take:
 *    <script>stupidNortonFunction() { do nasty thing; }</script>
 *       <ajax-response>our nice response</ajax-response>
 *    <script>moreNastyStuff()</script>
 * and return an xml document just the middle part or a subset of it if
 * rootNodeName is different.
 */
function convertAjaxResponseToXml(text, rootNodeName) {
	log.debug('convertAjaxResponseToXml: ' + displayXmlText(text) + ' - ' + rootNodeName);
	if (! rootNodeName) {
		rootNodeName = 'ajax-response';
	}
	var idx = text.indexOf('<' + rootNodeName + '>');
	if (idx < 0) {
		return null;
	}
	var xmlText = text.substring(idx);
	idx = xmlText.indexOf('</' + rootNodeName + '>');
	if (idx < 0) {
		return null;
	}
	xmlText = xmlText.substring(0, idx+rootNodeName.length + 4);
	log.debug('convertAjaxResponseToXml:  xml after cleanup: ' + displayXmlText(xmlText));
	
	// Parse out newlines because the FF parser treats those as nodes
	//xmlText = xmlText.replace(/\n/g, "");
	try {
		var parser = new ActiveXObject('Microsoft.XMLDOM');
		log.debug('convertAjaxResponseToXml: found MS Parser');
		parser.async = false;
		parser.preserveWhiteSpace = true; 
		var loaded = parser.loadXML(xmlText);
		var xml;
		if (loaded) {
		    xml = parser;
		}
		log.warn('convertAjaxResponseToXml: parsed xml: ' + displayXmlText(printXml(xml)));
		return xml;
	} catch (ex) {}

	try {
		var parser = new DOMParser();
		log.debug('convertAjaxResponseToXml: found FF parser');
		var xml =  parser.parseFromString(xmlText, "text/xml");
		xml.normalize();
		log.debug('convertAjaxResponseToXml: parsed xml: ' + displayXmlText(printXml(xml)));
		return xml;
	} catch (ex) {
	}
	
	return null;
}

/**
 * Display or log an Ajax error based on the errorHandling (see constants
 * at the top of this file), level of error, name for debugging, list of
 * error strings, and whatever debugInfo is passed in.
 */
function processAjaxError(errorHandling, level, name, errorList, debugInfo) {
	log.debug('processAjaxError');
	if (ERROR_HANDLING_IGNORE == errorHandling) {
		// TODO: temporary for debugging
		//errorHandling = ERROR_HANDLING_SHOW;
	}
	var message = '';
	if (name) {
		var loc = translate(name);
		if (loc.indexOf('ajax.') < 0) {
			message = translate('ajax.error.in') + loc + '\n';
		}
	}
	
	if (errorList) {
		for (var i=0; i < errorList.length; ++i) {
			if (i > 0) {
				message += '\n';
			}
			message += translate(errorList[i]);
		}
	}
	if (message.indexOf('LoggedInException') >= 0) {
		alert(message);
	} else if (errorHandling == ERROR_HANDLING_IGNORE) {
		return;
	} else {
		log.debug('Logging ajax error');
		if (! message.indexOf('LoggedInException')) {
			var parameters = 'level=' + (level ? level : '') + '&message=' + escape(message) + '&exception=' + (debugInfo ? escape(debugInfo) : '');
			var myAjax = new Ajax.Request(buildAddAjaxLogMessageLink(),
			{
				method: 'get',
				asynchronous: true,		
				parameters: parameters
			});
		}
		if (errorHandling == ERROR_HANDLING_SHOW) {
			// TODO: put this in a special place on the screen or something
			// This prevents page reloads from interrupting Ajax calls and displaying
			// errors for every call; the timer will die if the page reloads so
			// the alert won't display
			setTimeout(
				function() {  
					alert(message); 
				}, 2000);
		}
	}
	
	if (debugInfo) {
		//alert('(Temporary) Debug:\n' + debugInfo  + '\n' + stacktrace());
	}
}

/**
 * Given an xml document and a tagname, return the text of the first node that
 * matches the tag name.  For example, given:
 *  <ajax-response><gauge><!CDATA[[<div class="whatever"></div>]]></gauge><ajax-response>
 * then getFirstXmlElement(xml, 'gauge') will return the div.  It works without
 * CDATA too, this just illustrates that it will work with it. 
 */
function getFirstXmlElement(xmlDoc, tagName, noTrim) {
	try {
	 	try {
	 		// Takes care of 4096 limit on text nodes; concats adjacent nodes
	 		// back together 
	 		//xmlDoc.normalize();
	    } catch (ex) {}
		var list = xmlDoc.getElementsByTagName(tagName);
		if (! list || list.length == 0) {
			log.debug('getFirstXmlElement: ' + tagName + ' not found.');
			return null;
		}
		log.debug('getFirstXmlElement: node = ' + displayXmlText(printXml(list[0])));
		var text = getNodeTextValue(list[0]);
		if (text == null) {
			log.debug('getFirstXmlElement: text is null');
			text = printXml(list[0].firstChild);
		}
		log.debug('getFirstXmlElement: text = ' + displayXmlText(text));
		
		if (noTrim) {
			return text;
		} else {
			return trim(text);
		}
	} catch (ex) {
		log.debug('getFirstXmlElement: ' + tagName + ' not found or other error: ' + ex.description);
	}
	
	return null;
}

/**
 * Given an xml document, return it as a string
 */
function printXml(xmlDoc) {
	try {
		var parser = new ActiveXObject('Microsoft.XMLDOM');
		log.debug('printXml: found MS Parser');
		var markup = xmlDoc.xml;
		return markup;
	} catch (ex) {}

	try {
		var xmlSerializer = new XMLSerializer();
		log.debug('printxml: found FF serializer');
		var markup = xmlSerializer.serializeToString(xmlDoc);
		return markup;
	} catch (ex) {}
	
	return '';
}

/**
 * Given text that is formatted like xml, return an escaped
 * string.
 */
function displayXmlText(xml) {
	if (! xml) return '';
	return xml.replace(/</g,"&lt;").replace(/>/g,"&gt;");
}


function logException(exc, level, message) {
	var dump;
	if (exc) {
		dump = 'Exception: ' + exc + '\n';
		for (var i in exc) {
			try {
				if (i == 'extend' || i == 'toJSONString' || typeof exc[i] == 'function') continue;
				dump += '   ' + i + ': ' + exc[i] + '\n';
				dump += '\n';
			} catch (ex) {
			}
		}
		
		var parameters = 'level=' + (level ? level : '') + '&message=' + escape(message) + '&exception=' + dump;
		var myAjax = new Ajax.Request(buildAddAjaxLogMessageLink(),
		{
			method: 'get',
			asynchronous: true,		
			parameters: parameters
		});		
		
		setTimeout(
			function() {  
				alert(dump); 
			}, 2000);
	}
}
/**
 * Return some debug info for an Ajax response.
 */
function dumpAjaxResponse(req, exc) {
	var dump = 'Request: ' + req.url + "\n";
	if (exc) {
		dump += 'Exception: ' + exc.toString();
		dump += "\n";
		if (typeof(exc) != 'string') {
			for (var i in exc) {
				try {
					if (i == 'extend' || i == 'toJSONString' || typeof exc[i] == 'function') continue;
					dump += '   ' + i + ': ' + exc[i] + "\n";
					dump += "\n";
				} catch (ex) {
				}
			}
		}
	}
	dump += 'Ajax Response:' + "\n";
	try {
		dump += '	readyState: ' + req.readyState + "\n";
	} catch (ex) {}
	try {
		dump += '	responseText: ' + displayXmlText(req.responseText) + "\n";
	} catch (ex) {}
	try {
		dump += '	status: ' + req.status + "\n";
	} catch (ex) {}
	try {
		dump += '	statusText: ' + req.statusText + "\n";
	} catch (ex) {}
	try {
		dump += '	ajaxRequestErrors: ' + ajaxRequestErrors;
	} catch (ex) {}
	log.debug('Dump: ' + dump);
	return dump;
}


/**
 * Get the text of a node, for example <node>foo</node> would return 'foo';
 * TODO: FireFox handles xml differently when there are newlines; double-check
 */
function getNodeTextValue(xml) {
	if (xml.firstChild) {
		return xml.firstChild.nodeValue;
	}
	return '';
}

/**
 * Attempt to return a stacktrace of a JavaScript exception.
 */
function stacktrace() {
	var s = "";
	for (var a = arguments.caller; a !=null; a = a.caller) {
		s += "->" + funcname(a.callee) + "\n";
		if (a.caller == a) { s+= "*"; break; }
	}
	return s;
}

/**
 * Return the translated value given the key.  This assumes that
 * the translations are stored in a JSON/Map variable called
 * translations.
 */
function translate(key) {
	if (translations && translations[key]) {
		return translations[key];
	}
	return key;
}


/**
 * Given some xml-ish looking stuff, return the first
 * text.  So <foo><bar><baz>nit</baz</foo></bar>
 * would return 'nit'.
 */
function findText(element) {
	if (! element.childNodes || element.childNodes.length == 0) {
		return;
	}
	
	for (var i=0; i < element.childNodes.length; ++i) {
		var child = element.childNodes[i];
		if (child.nodeType == 3) {
			return element.innerHTML;
		} else if (child.nodeType == 1) {
			return findText(child);
		} else {
			return null;
		}
	}
}


/**
 * Much faster version of prototype's getElementsByClassName; should
 * always be used when you know the tag you are looking for because it
 * filters out all other tags in one fell swoop instead of checking
 * every one of them for the class.  And always include the 3rd parameter
 * which is the scope of the search - the narrower the better.
 */
document.getElementsByClassAndTag = function(className, tagName, parentElement) {
  var children = ($(parentElement) || document.body).getElementsByTagName(tagName);
  var x = $A(children).inject([], function(elements, child) {
    if (child.className.match(new RegExp("(^|\\s)" + className + "(\\s|$)")))
      elements.push(Element.extend(child));
    return elements;
  });
  return x;
}

function showProperties(item) {
	var s = '';
	for (var i in item) {
	try {
		s += i + ': ' + item[i];
		s += '\n';
	} catch (ex) {}
	}
	alert(s);
	showTextArea(s);
}

function showTextArea(text) {
	var ta = $('debug_textarea');
	if (! ta) {
		ta = document.createElement('textarea');
		ta.id = 'debug_textarea';
		Element.setStyle(ta, {'position':'absolute','left':0,'right':0});
		document.body.appendChild(ta);
	}
	ta.value += text;
}

function sortListBox(listBox) {
	listBox = $(listBox);
	
	var sortFunc = function(a,b) 
		{ 
			return (a.text.toLowerCase() < b.text.toLowerCase() ) ? -1 : 1; 
		};
	
	var unsortedList = $A(listBox.options);
	var sortedList = unsortedList.sort(sortFunc);
	var assignFunc = function(o,i) 
	{ 
		listBox.options[i] = new Option(o.text, o.value);
		if (o.title && o.title != '') {
		listBox.options[i].title = o.title;
		} else {
			listBox.options[i].title = o.text;
		}
	};
	sortedList.each(assignFunc);
}

function runIfEnterPressed(e, func) { 
	if (!e) { e = window.event; }
	if (e.keyCode == 13) {
		if (func) {
			func();
		}
		return false; 
	}
	return true;
}


function showCursorBusy(element) {
	document.body.style.cursor = 'wait';
	if (element) {
		element.style.cursor = 'wait';
	}
}

function showCursorNormal(element) {
	document.body.style.cursor = 'auto';
	if (element) {
		element.style.cursor = 'pointer';
	}
}

function chooseSelectOption(selectInput, newValue) {
	selectInput = $(selectInput);
	for (var i=0; i < selectInput.options.length; ++i) {
		if (selectInput.options[i].value == newValue) {
			selectInput.options[i].selected = true;
		} else {
			selectInput.options[i].selected = false;
		}
	}
}


var showProgressTimer = null;
function showInProgressFunc(element) {
	var x, y;
	var dims = Element.getDimensions('in_progress_popup');
	if (element) {
		var pos = Position.cumulativeOffset(element);
		var size = Element.getDimensions(element);
		x = pos[0] + (size.width/2) - (dims.width/2);
		y = pos[1] + (size.height/2) - (dims.height/2);
	} else {
		var windowHeight = getWindowHeight();
		var windowWidth = getWindowWidth();
		x = (windowWidth/2) - (dims.width/2);
		y = (windowHeight/2) - (dims.height/2);
	}
	showProgressOverlay();
	Element.setStyle('in_progress_popup', {'top':y+'px','left':x+'px'});
	Element.setStyle('in_progress_popup', {'zIndex':500});
	Element.setStyle('in_progress_popup', {'display':'block', 'visibility':'visible','opacity':1});
}

function showInProgress(element, timeout) {
	if (timeout) {
		showProgressTimer = setTimeout(function() { showInProgressFunc(element); }, timeout);
	} else {
		showInProgressFunc(element);
	}
}

function hideInProgress() {
	if (showProgressTimer) {
		clearTimeout(showProgressTimer);
	}
	hideProgressOverlay();
	Element.hide('in_progress_popup');
}

function hideInProgressSlow() {
	setTimeout(function() { hideInProgress(); }, 100);
}

var hideInProgressFunc = function() { hideInProgress(); }


var overlayZIndexes = new Array();

/**
 * The overlay is a mostly transparent iframe that is used by popups
 * to prevent links underneath from being activated while the popup is
 * showing.  This function indicates if the overlay is there, which is
 * mostly used to determine if a popup is showing.
 */
function isOverlayShowing() {
	return overlayZIndexes.length > 0;
	var overlay = $('modal_overlay');
	if (! overlay) return false;
	var displayStyle = overlay.style.display;
	return displayStyle == 'block';
}

/**
 * Hide the overlay iframe.
 */
function hideOverlay() {
	if (overlayZIndexes.length == 0) {
		return;
	}
	overlayZIndexes.pop();
	var overlay = $('modal_overlay');
	if (overlay) {
		if (overlayZIndexes.length == 0) {
			// that was the last one; just hide it
			Element.setStyle(overlay, {'display':'none'});
		} else {
			var zIndex = overlayZIndexes[overlayZIndexes.length-1];
			Element.setStyle(overlay, {'zIndex':zIndex});
		}
	} else {
		overlayZIndexes.clear();
	}
}

function hideProgressOverlay() {
	var overlay = $('progress_overlay');
	if (overlay) {
		Element.setStyle(overlay, {'display':'none'});
	}
}

/**
 * Show the overlay iframe.
 */
function showOverlay() {
	
	if (overlayZIndexes.length > 0) {
		// It's already showing
		Element.setStyle('modal_overlay', {'zIndex':currentZIndex});
		overlayZIndexes.push(currentZIndex);
		return;
	}
	var overlay = $('modal_overlay');
	if (window.innerHeight) {
		height = window.innerHeight;
		width = window.innerWidth;
	} else {
		height = document.body.clientHeight;
		width = document.body.clientWidth;
	}
	var scrollTop = document.body.scrollTop;
	height += scrollTop;
	
	width -= 5;
	height -= 5;
	
	// IE already has the vertical scrollbar at all times
	// others will add in a scrollbar and we need to take that
	// into account or the overlay will cause a horizontal
	if (! ie) {
		width -= 20;
	}
	
	if (! overlay || overlay == null) {
		overlay = document.createElement('iframe');
		overlay.id = 'modal_overlay';
		document.body.appendChild(overlay);
	}
	Element.setStyle(overlay, {'zIndex':currentZIndex});
	overlayZIndexes.push(currentZIndex);
	Element.setStyle(overlay, {'display':'block',
							   'width':width+'px',
							   'height':height+'px'
							  });
	
}

function showProgressOverlay() {
	var overlay = $('progress_overlay');
	if (window.innerHeight) {
		height = window.innerHeight;
		width = window.innerWidth;
	} else {
		height = document.body.clientHeight;
		width = document.body.clientWidth;
	}
	var scrollTop = document.body.scrollTop;
	height += scrollTop;
	width -= 5;
	height -= 5;
	// IE already has the vertical scrollbar at all times
	// others will add in a scrollbar and we need to take that
	// into account or the overlay will cause a horizontal
	if (! ie) {
		width -= 20;
	}
	if (! overlay || overlay == null) {
		overlay = document.createElement('iframe');
		overlay.id = 'progress_overlay';
		document.body.appendChild(overlay);
	}
	Element.setStyle(overlay, {'zIndex':500});
	Element.setStyle(overlay, {'display':'block',
		'width':width+'px',
		'height':height+'px'
	});
}

/**
 * My all-time favorite hack. Select boxes show through normal divs because they
 * don't obey the z order.  But if you put an iframe under the div, but over the
 * select, it does some kind of quantum polarity filtering thing and lets the div
 * appear on top.  Everything has to be lined up exactly so the iframe doesn't stick
 * out.  It's only on needed on IE of course.  Google for iframe shim to get all
 * the gory details.
 */
function showIframeBlocker(popup, force) {
	if ((ie7 || ! ie) && ! force) return;
	var z = currentZIndex;
	popup = $(popup);
	var dims = Element.getDimensions(popup);
	var width = dims.width;
	var height = dims.height; // not sure where the -5 came from; looks bad on more gauge icons; presumably not somewhere ese - (ie ? 5 : 0);
	var pos = Position.cumulativeOffset(popup);
	var left = pos[0];
	var top = pos[1];
	var iframe = document.createElement('iframe');
	iframe.frameborder = 0;
	iframe.id = popup.id + '_iframe';
	Element.setStyle(iframe, {	'border':'0px', 
								'zIndex':z, 
								'position':'absolute',
								'width':width+'px',
								'height':height+'px',
								'top':top,
								'left':left,
								'display':'block',
								'visibilility':'visible'
							  });	
	document.body.appendChild(iframe);
}

/**
 * Hide the iframe blocker.
 */
function hideIframeBlocker(popup, force) {
	if ((ie7 || ! ie) && ! force) return;
	popup = $(popup);
	var iframeId = popup.id + '_iframe';
	var iframe = $(iframeId);
	if (! iframe) return;
	Element.setStyle(iframe, {'display':'none','visibility':'hidden'});
	iframe.parentNode.removeChild(iframe);
}

function showPopup(popup, forceBlocker) {
	popup = $(popup);
	showOverlay();
	currentZIndex++;
	showIframeBlocker(popup, forceBlocker);
	currentZIndex++;
	Element.setStyle(popup, {'zIndex':currentZIndex});
	Element.setStyle(popup, {'display':'block', 'visibility':'visible','opacity':1});
	currentZIndex++;
	resetLinkZIndexes(popup);
}

function showPopupNoOverlay(popup, forceBlocker) {
	popup = $(popup);
	showIframeBlocker(popup, forceBlocker);
	currentZIndex++;
	Element.setStyle(popup, {'zIndex':currentZIndex});
	Element.setStyle(popup, {'display':'block', 'visibility':'visible','opacity':1});
	currentZIndex++;
}

function resetLinkZIndexes(popup) {
	popup = $(popup);
	var links = document.getElementsByClassAndTag('configure_field_link','a',popup);
	for (var i=0; i < links.length; ++i) {
		Element.setStyle(links[i], {'zIndex':currentZIndex});
	}
}

function hidePopup(popup) {
	popup = $(popup);
	if (useAnimation) {
		new Rico.Effect.FadeTo(id, 0, .6, 3, {complete:function() { Element.hide(popup); }});
	} else {
		Element.hide(popup);
	}
	currentZIndex--;
	hideIframeBlocker(popup);
	currentZIndex--;
	hideOverlay();
	currentZIndex--;
}

function hidePopupNoOverlay(popup) {
	popup = $(popup);
	if (useAnimation) {
		new Rico.Effect.FadeTo(id, 0, .6, 3, {complete:function() { Element.hide(popup); }});
	} else {
		Element.hide(popup);
	}
	currentZIndex--;
	hideIframeBlocker(popup);
	currentZIndex--;
}

function resizeIframe(popup) {
	var popup = $(popup);
	var iframe = $(popup.id+'_iframe');
	if (! iframe) {
		return;
	}
	var idims = Element.getDimensions(iframe);
	var dims = Element.getDimensions(popup);
	var width = dims.width;
	var height = dims.height;
	var pos = Position.cumulativeOffset(popup);
	var left = pos[0];
	var top = pos[1];
	Element.setStyle(iframe, {	'width':width+'px',
								'height':height+'px',
								'top':top,
								'left':left
							  });
}

function tooltipize(s, max) {
	if (s) {
		if (s.length > max) {
			s = '<span class="has_tooltip" title="' + s + '">' + s.substring(0, max-3) + '...</span>';
		}
	}
	return s;
}

