/*
 * Utility functions for aiding development with the tibet platform.
 *
 * Categories:
 * - Common utils
 * - String format
 * - IE vs Firefox fixes
 *
 * Usage:
 * <script type="text/javascript" src="/resources/ui/scripts/tibet-utils-0.1.js"></script>
 *
 * @author <a href="mailto:richard.sandstrom@tibetserver.com">Richard Sandström</a>
 * @version $Id: tibet-utils-0.1.js
 */

///////////////////////////
// Common utils
///////////////////////////

/*
* Toggle between two style.display properties for an element
*
* Valid modes are:
* none, inline, block, list-item, run-in, compact, marker,
* table, inline-table, table-row-group, table-header-group
* table-footer-group, table-row, table-column-group,
* table-column, table-cell, table-caption
*
* Tested with IE6,7 and Firefox 2
* @param id        the id of the element to toggle
* @param mode1     the first mode of the style.display property
* @param mode2     the second mode of the style.display property
* @author <a href="mailto:richard.sandstrom@tibetserver.com">Richard Sandström</a>
*/
function toggleDisplay(id, mode1, mode2) {
    var el = document.getElementById(id);

    if(el.style.display == mode1) {
        el.style.display = mode2;
    } else {
        el.style.display = mode1;
    }
}

///////////////////////////
// String format
///////////////////////////

/*
 * Formats the time field as:
 * H:mm if Hmm is entered
 * HH:mm if HHmm is entered
 * Tested with IE6,7 and Firefox 2
 * @param field     the field to be formated
 * @author <a href="mailto:richard.sandstrom@tibetserver.com">Richard Sandström</a>
 */
function formatTimeField(timeField){
    if(!isNaN(timeField.value)) {
        if(timeField.value.substring(0,1) == "0") {
            timeField.value = timeField.value;
        }

        if(timeField.value.length == 4) {
            timeField.value = timeField.value.substring(0,2) + ":" + timeField.value.substring(2,4);
        } else if(timeField.value.length == 3) {
            timeField.value = timeField.value.substring(0,1) + ":" + timeField.value.substring(1,3);
        }
    }
}

/*
 * Function for polling anything. 
 * testFunc() is repeatedly called to check whether we may run runFunc()
 * @author <a href="mailto:max.martinsson@tibetserver.com">Max Martinsson</a>
 */
function poll( testFunc, runFunc, interval ) {
	interval = interval || 50;
	var pollFunc = function() {
		if ( testFunc() ) {
			runFunc();
		} else {
			setTimeout( pollFunc, interval );
		}
	};
	setTimeout( pollFunc, interval );
}

String.prototype.startsWith = function(prefix) {
	if (this.length < prefix.length) {
		return false;
	}
	return this.substring(0, prefix.length) == prefix;
}
String.prototype.endsWith = function(suffix) {
	if (this.length < suffix.length) {
		return false;
	}
	return this.substring(this.length - suffix.length) == suffix;
}

/**
 * Trims a string, removing leading and trailing spaces and compressing inline spaces to single white space.
 *
 * @param   text        the text to trim
 * @return              the trimmed text
 */
function stringTrim(text) {
    // trim leading space
    var firstCharIndex = text.search(/\S/);
    text = text.substr(firstCharIndex);

    // trim trailing space
    var lastCharIndex = text.search(/\S([\s\f\n\r\t])+$/);
    if (lastCharIndex > 0) {
        text = text.substring(0, lastCharIndex + 1);
    }
    // trim trailing none-breaking space
    if (text.charCodeAt(text.length - 1) == 160) {
        text = text.substring(0, text.length - 1);
    }

    // trim in-line space
    text = text.replace(/(\s)+/, " ");

    return text;
}


///////////////////////////
// IE vs Firefox fixes
///////////////////////////

/*
 * Gets the x-coordinate of the mouse from the current event
 * Tested with IE6,7 and Firefox 2
 * @param event     (optional) the triggering event
 * @author <a href="mailto:richard.sandstrom@tibetserver.com">Richard Sandström</a>
 */
function mouseX(event) {
    event = getEvent(event);
    if (event.pageX) {
        return event.pageX;
    } else if (event.clientX) {
        return event.clientX + (document.documentElement.scrollLeft ?
                                document.documentElement.scrollLeft :
                                document.body.scrollLeft);
    } else {
        return null;
    }
}

/*
 * Gets the y-coordinate of the mouse from the current event
 * Tested with IE6,7 and Firefox 2
 * @param event     (optional) the triggering event
 * @author <a href="mailto:richard.sandstrom@tibetserver.com">Richard Sandström</a>
 */
function mouseY(event) {
    event = getEvent(event);
    if (event.pageY) {
        return event.pageY;
    } else if (event.clientY) {
        return event.clientY + (document.documentElement.scrollTop ?
                                document.documentElement.scrollTop :
                                document.body.scrollTop);
    } else {
        return null;
    }
}

/*
 * Gets the current event
 * Tested with IE6,7 and Firefox 2
 * @param event     (optional) the triggering event
 * @author <a href="mailto:richard.sandstrom@tibetserver.com">Richard Sandström</a>
 */
function getEvent(event) {
    if (!event) {
        return window.event;
    }
    return event;
}

/*
 * Gets the taget of the current event
 * Tested with IE6,7 and Firefox 2
 * IE uses event.srcElement
 * Firefox uses event.target
 * @param event     (optional) the triggering event
 * @author <a href="mailto:richard.sandstrom@tibetserver.com">Richard Sandström</a>
 */ 
function getEventTarget(event) {
    event = getEvent(event);
    if (!event.target) {  //IE fix
        return event.srcElement;
    }
    return event.target;
}



/*
 * getIframeDocument() has been moved to Tibet.Util (in Static.js)
 */


/**
 * Returns the hash part of the location (the string after #)
 * @return location hash or empty string if none.
 */
function getHash() {
	var url = window.location.href;
	var index = url.indexOf("#")+1; // indexOf returns -1 so this is 0 if # is not found.
	return url.substring( index || url.length );
}

///////////////////////////
// Validation
///////////////////////////

/**
* checkName
*
* performs the validateFileName check after extracting the filename from the upload path
*
* @param file		the name of the file being checked
*/
function checkName(file) {
    isNixFile = file.indexOf("/") != -1;
    if (isNixFile) {
        fileName = file.substring(file.lastIndexOf("/") + 1, file.length);
    } else {
        fileName = file.substring(file.lastIndexOf("\\") + 1, file.length);
    }
    validateFileName(fileName);
}

/**
* validateFileName
*
* checks that the file has a valid extension
*
* checks that the selected file has a valid name, not including any characters other than a-z, A-Z, 0-9 and _.
* Any characters other than the given will be replaced according to the following schema:
* 						->	a
* 						->	a
* 						->	o
* 				others	->	_
* The cleaned up value is then placed in the fileName field,
* while the original value is placed in the description field.
*
* @param fileName		the name of the file being checked
* todo: make more general
*/
function validateFileName(fileName) {
    var re = new RegExp("[^a-zA-Z0-9\.]" ,"gi");

    // check that the file has a valid extension
    if (fileName != "") {
        var selFile = document.forms["frmUpload"].elements["file"].value;
        var ext = selFile.substring(selFile.lastIndexOf('.'), selFile.length).toLowerCase();
        var extLen = ext.length;

        if (fileName.indexOf('.') == -1) {
            fileName = fileName + ext;
        } else {
            var currentExt = fileName.substr(fileName.length - extLen, extLen + 1);
            if (currentExt != ext) {
                fileName = fileName.substring(0, fileName.length - currentExt.length) + ext;
            }
        }
    }

    // replace forbidden characters
    if (fileName.search(re) > -1) {
        // ,
        var reDef1 = "[" + String.fromCharCode(229) + String.fromCharCode(228) + "]";
        re = new RegExp(reDef1 ,"gi");
        cleanFileName = fileName.replace(re, "a");

        //
        var reDef2 = "[" + String.fromCharCode(246) + "]";
        re = new RegExp(reDef2 ,"gi");
        cleanFileName = cleanFileName.replace(re, "o");

        // others
        re = new RegExp("[^a-z0-9\.]" ,"gi");
        cleanFileName = cleanFileName.replace(re, "_");
    } else {
        cleanFileName = fileName;
    }

    document.forms["frmUpload"].elements["fileName"].value = cleanFileName;
    if (document.forms["frmUpload"].elements["description"].value == "") {
        document.forms["frmUpload"].elements["description"].value = fileName;
    }
}


/**
 * checkDate
 *
 * validates and completes a datestring
 */
function checkDate(elmnt) {
    var dateOK = false;
    var theDate = elmnt.value;
    var re = new RegExp("[1-2][90][0-9][0-9]");				// check that the date has the proper form of YYYY
    var res = theDate.search(re);
    if (res == 0 && theDate.length == 4) {
        dateOK = true;
    }
    re = new RegExp("[1-2][90][0-9][0-9]-[01][0-9]");				// check that the date has the proper form of YYYY-MM
    res = theDate.search(re);
    if (res == 0 && theDate.length == 7) {
        dateOK = true;
    }
    re = new RegExp("[1-2][90][0-9][0-9][01][0-9]");				// check that the date has the proper form of YYYYMM
    res = theDate.search(re);
    if (res == 0 && theDate.length == 6) {
        elmnt.value = theDate.substring(0,4) + "-" + theDate.substring(4,6);
        dateOK = true;
    }
    re = new RegExp("[1-2][90][0-9][0-9]-[01][0-9]-[0-3][0-9]");				// check that the date has the proper form of YYYY-MM-DD
    res = theDate.search(re);
    if (res == 0 && theDate.length == 10) {
        dateOK = true;
    }
    re = new RegExp("[1-2][90][0-9][0-9][01][0-9][0-3][0-9]");				// check that the date has the proper form of YYYYMMDD
    res = theDate.search(re);
    if (res == 0 && theDate.length == 8) {
        elmnt.value = theDate.substring(0,4) + "-" + theDate.substring(4,6) + "-" + theDate.substring(6,8);
        dateOK = true;
    }
    return dateOK;
}


/**
 * Validates and reformats a date string, taking number of days in a month and leap years into account.
 * @param   element       	Form element reference. The form element containing the date to be validated.
 * @param   update       	True / False. Whether to update the form field with a formatted date or not
 * 							formated like this: 2007-08-02
 * 							Default: true
 * @return					True if the form field contains a valid date. False if not.
 */
function validateDate(element, update) {
	if (update == undefined) update = true;
	var regexp = new RegExp("(\\d{4})\\s*-?\\s*(\\d{1,2})\\s*-?\\s*(\\d{1,2})");
	match = regexp.exec(elmnt.value);
	if (match) {
		var y = parseInt(match[1], 10);
		var m = parseInt(match[2], 10);
		var d = parseInt(match[3], 10);

		if (m < 1 || m > 12) {
			//month out of range
			return false;
		}

		switch (m) {
		case 2:
			d_max = (y % 4 == 0 && (y % 100 != 0 || y % 400 == 0)) ? 29 : 28;
			break;
		case 4:
		case 6:
		case 9:
		case 11:
			d_max = 30;
			break;
		default:
			d_max = 31;
		}
		if (d > d_max) {
			//day out of range
			return false;
		}

		if (match[2].length == 1) match[2] = "0"+match[2];
		if (match[3].length == 1) match[3] = "0"+match[3];
		if (update) {
			elmnt.value = match[1]+"-"+match[2]+"-"+match[3];
		}
		return true;
	} else {
		//invalid date format, should be 2007-05-20 or 20070520
		return false;
	}

}




/**
 * Shows only one element at a time. When a new element is shown the old one is hidden.
 * If the element to be shown is already shown it will also be hidden.
 * 
 * @param   element       	Form element reference. The form element containing the date to be validated.
 * @param   group       	Optional. Only one element in each display group can be shown at a time.
 * @param   display       	Optional. The display mode of the element if it is shown. This is passed
 * 							on to CSS so typically "block" and "inline" should work.
 * @return					True if the element was shown. False if not.
 */
var toggleOne_element = new Object();
function toggleOneShown(element, group, display) {
	if (group == undefined) {
		group = "_default";
	}
	//hide the previously shown element
	if (toggleOne_element[group]) {
		toggleOne_element[group].style.display = "none";
	}
	//if element is a string and not a reference we look it up
	if (typeof(element) == "string") {
		element = document.getElementById(element);
	}
	if (element != undefined && element != toggleOne_element[group]) {
		//if no display mode was passed "block" is the default
		element.style.display = (display == undefined) ? "block" : display;
		toggleOne_element[group] = element;
		return true;
	} else {
		toggleOne_element[group] = false;
		return false;
	}
}


/**
 * This is just like toggleOneShown  but without a return value so that this can be 
 * used in hyperlinks like so:
 * <a href="javascript: toggleOne(element)">
 * 
 * Shows only one element at a time. When a new element is shown the old one is hidden.
 * If the element to be shown is already shown it will also be hidden.
 * 
 * @param   element       	Form element reference. The form element containing the date to be validated.
 * @param   group       	Optional. Only one element in each display gorup can be shown at a time.
 * @param   display       	Optional. The display mode of the element if it is shown. This is passed
 * 							on to CSS so typically "block" and "inline" should work.
 */
function toggleOne(element, group, display) {
	toggleOneShown(element, group, display);
}


/**
 * Returns the complement color. This is useful for getting a color of a text that will show against a
 * given background.
 * @param   color       	The color to which a complement is desired. If the input format is #FFFFFF
 * 							the output will reflect this by default. If the input format is 
 * 							rgb(12,34,56) this will also be reflected.
 * @param	format_out_hex	Whether or not to output the complementary color in hex format (#FF00FF).
 * 							Default is to use the format provided in the input.
 * @return					The complement color. False on error.
 */
function computeComplementColor(color, format_out_hex) {
	var format_in_hex = (color[0] == "#");
	if (format_out_hex == undefined) format_out_hex = format_in_hex;
	
	var r,g,b;
	if (format_in_hex) {
		if (color.length > 4) {
			//parse color in the format "#00CCFF"
			r = parseInt(color.substring(1, 3), 16);
			g = parseInt(color.substring(3, 5), 16);
			b = parseInt(color.substring(5, 7), 16);
		} else {
			//parse color in the format "#0CF"
			r = parseInt(color.substring(1, 2), 16) * 17;
			g = parseInt(color.substring(2, 3), 16) * 17;
			b = parseInt(color.substring(3, 4), 16);
		}
	} else {
		//parse color in the format "rgb(12, 34, 234)"
		var regexp = new RegExp("\\(\\s*(\\d+)\\s*,\\s*(\\d+)\\s*,\\s*(\\d+)\\s*\\)");
		match = regexp.exec(color);
		if (match) {
			r = parseInt(match[1], 10);
			g = parseInt(match[2], 10);
			b = parseInt(match[3], 10);
		} else {
			return false;
		}
	}

	//check that values are valid
	if (r < 0 || r > 255 || g < 0 || g > 255 || b < 0 || b > 255) {
		return false;
	}

	//compute the complimentary color
	var _r = 255 - r;
	var _g = 255 - g;
	var _b = 255 - b;
	/*
	var threshold = 40; //gray threshold
	if (Math.abs(r - 128) + Math.abs(b - 128) + Math.abs(b - 128) < threshold) {
		//if the color is too gray we take measures
		_r = (_r + 128) % 255;
		_g = (_g + 128) % 255;
		_b = (_b + 128) % 255;
	}
	 */
	//just select the most opposite color, white or black, simple and beautiful 
	if ((r + g + b) / 3 > 128) {
		_r = 0;
		_g = 0;
		_b = 0;
	} else {
		_r = 255;
		_g = 255;
		_b = 255;
	}
	
	//format the resulting color
	if (format_out_hex) {
		return "#"+_r.toString(16)+_g.toString(16)+_b.toString(16);
	} else {
		return "rgb("+_r+","+_g+","+_b+")";
	}	
}



/**
 * Returns a XMLHttpRequest object for the current browser. The first step in cross browser AJAX.
 * Use tibetAjaxRequest instead for convenience. If you want to go ahead anyway do it like this:
 * var ajax = new tibetAjaxObject();
 * 
 * @return			an XMLHttpRequest object
 */
function tibetAjaxObject() {
	//activeX versions to check for in IE
	var activexmodes = ["Msxml2.XMLHTTP", "Microsoft.XMLHTTP"]; 
	if (window.ActiveXObject) {
		//Test for support for ActiveXObject in IE first (as XMLHttpRequest in IE7 is broken)
		for (var i = 0; i < activexmodes.length; i++){
			try {
				return new ActiveXObject(activexmodes[i]);
			} catch (e) {
				//suppress error
			}
		}
		return false;
	} else if (window.XMLHttpRequest) {
		// if Mozilla, Safari etc
		return new XMLHttpRequest();
	} else {
		return false;
	}
}


/**
 * Parses all the non-empty form fields and returns a Object map with the names and values.
 * @param aForm		the Form object to retrieve field values from
 * @return			an Object map with the names and values
 * TODO: bug: doesn't handle multiple form elements with same name, as required when submitting lists of values for a given parameter
 */
function parseFormParams(aForm) {
    var paramMap = new Object();
    if (aForm.nodeName == "FORM") {
        for (var i = 0; i < aForm.elements.length; i++) {		           
			//alert(aForm.elements[i].name);
			if (aForm.elements[i].name != undefined && aForm.elements[i].name != "") {
                paramMap[aForm.elements[i].name] = aForm.elements[i].value;
            }
        }
    }
    return paramMap;
}


/**
* Makes an AJAX request as soon as it is called. In its simplest form it can be called like this:
* var req = new tibetAjaxRequest("/path/to/your/screen?action=your.action");
* alert(req.responseText);
* 
* A more elaborate example:
* var req = new tibetAjaxRequest("/path/to/your/screen?action=your.action",
* 	{
* 		siteId: $siteId,
* 		anotherParam: 123,
* 		text: "blabla"
* 	}, 
* 	{
* 		onComplete: function(ajax) { alert(ajax.responseText); }, 
* 		onFail: function(ajax) { alert("ERROR"); }
* 	}
* );
* 
* @param url		The URL for the request. This will be changed
* @param params		Optional. Parameters for the request as an object. 
* @param callbacks	Optional. An object containing callback functions that are called 
* 					when the request finishes. Currently onComplete and onFail are supported.
* 					These are only fired if async is true.
* @param async		Optional. Whether to perform the request asynchronically or not. Default: true
* @param timeout	Optional. How much time the request is allowed to take. Default: 15 seconds.
* 
* @return  			the XMLHttpRequest object used for the request
*/
function tibetAjaxRequest(url, params, callbacks, method, async, timeout, waitMessage) {
	if (typeof(method) != "string") method = 'POST';
	if (typeof(async) != "boolean") async = true;
	if (typeof(timeout) != "number") timeout = 15000;	
	/*if (waitMessage == undefined) waitMessage = new Object();
	if (!waitMessage.message) waitMessage.message = "Please wait ..."; 
	if (!waitMessage.delay) waitMessage.delay = 400; 
	if (!waitMessage.element) {
		waitMessage.element = document.getElementById("ajaxWaitMessage");
		if (!waitMessage.element) {
			waitMessage.element = document.createElement("div");
			waitMessage.element.style.fontSize = "36px";
			waitMessage.element.style.color = "black";
			waitMessage.element.style.display = "none";
			waitMessage.element.id = "ajaxWaitMessage";
			waitMessage.element.innerHTML = waitMessage.message;
			document.body.appendChild(waitMessage.element);
		}
	}*/
	
	var ajax = new tibetAjaxObject();
	var ajaxContainer = this;
	ajaxContainer.tibetCallbacks = callbacks;

	if (async == true && typeof(callbacks) == "object") {
		//we only use callbacks if it is an asynchronous call
		ajax.onreadystatechange = function() {
			//here "this" refers to the ajax-object which is browser dependent and has been 
			//supplied with tibetCallbacks and tibetTimeoutFunc by us
			switch (ajax.readyState) {
			case 4: //complete
				if (waitMessage) {
					//hide the "Please wait ..." message
					showProgressOverlay();
				}
				clearInterval(ajaxContainer.tibetTimeoutFunc);
				if (ajax.status >= 200 && ajax.status <= 299) {
					//great success
					if (ajaxContainer.tibetCallbacks.onComplete) ajaxContainer.tibetCallbacks.onComplete(ajax);
				} else {
					//failed: server error or timeout
					if (ajaxContainer.tibetCallbacks.onFail) ajaxContainer.tibetCallbacks.onFail(ajax);
				}
				break;
			}
		}
	}
	
	var data = null;
	if (typeof(params) == "object") {
		data = "";
		for (name in params) {
			data += encodeURIComponent(name)+"="+encodeURIComponent(params[name])+"&";
		}
		
		//remove the last "&" from data
		data = data.substring(0, data.length - 1);
		if (method == "GET") {
			//append the url with params and set data to null
			url += ((url.indexOf("?") == -1) ? "?" : "&") + data;
			data = null;
		}
		
	}
	
	//url += "?tibet_characterEncoding=utf-8";
	
	//data = stupidEncode(data); //Dont know why this doesnt work? :(
	
	ajaxContainer.tibetTimeoutFunc = setTimeout(
		function() {
			//upon timeout we abort the request
			ajax.abort();
			//if (ajax.tibetCallbacks && ajax.tibetCallbacks.onTimeout) ajax.tibetCallbacks.onTimeout(ajax);
		}, timeout
	);
	
	//send the request
	ajax.open(method, url, async);
	if (method == "POST") {
		ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded; charset=utf-8");
	}
	ajax.send(data);
	
	if (waitMessage) {
		setTimeout(function() {
			if (ajax.readyState != 4) {
				showProgressOverlay(waitMessage.element);
			}
		}, waitMessage.delay);
	}
	
	return ajax;
}



var ajaxProgressOverlay, ajaxProgressOverlayObj;
function showProgressOverlay(obj) {
	var width = document.body.offsetWidth;
	var height = document.body.offsetHeight;
	if (ajaxProgressOverlay == undefined) {
		ajaxProgressOverlay = document.createElement("div");
		ajaxProgressOverlay.style.position = "absolute";
		ajaxProgressOverlay.style.left = "0px";
		ajaxProgressOverlay.style.top = "0px";
		ajaxProgressOverlay.style.width = width+"px";
		ajaxProgressOverlay.style.height = height+"px";
		ajaxProgressOverlay.style.backgroundColor = "white";
		ajaxProgressOverlay.style.zIndex = 10000;
		ajaxProgressOverlay.style.filter = "alpha(opacity=70)";
		ajaxProgressOverlay.style.opacity = 0.7;
		document.body.appendChild(ajaxProgressOverlay);
	}
	if (typeof(obj) == "string") {
		obj = document.getElementById(obj);
	}
	if (ajaxProgressOverlayObj != undefined) {
		ajaxProgressOverlayObj.style.display = "none";
	}
	if (obj) {
		obj.style.display = "block";
		obj.style.left = ((width - obj.offsetWidth) / 2)+"px";
		obj.style.top = ((height - obj.offsetHeight) / 2)+"px";
		obj.style.zIndex = 10001;
		obj.style.position = "absolute";
		ajaxProgressOverlay.style.display = "block";
		obj.style.display = "block";
		ajaxProgressOverlayObj = obj;
	} else { 
		ajaxProgressOverlay.style.display = "none";
		ajaxProgressOverlayObj = undefined;
	}
}



/**
 * Sets a cookie, a name value pair that is stored in the web browser. 
 * This is passed to the server every time the user loads a new page as long as 
 * the cookie is valid and the path matches the URL.
 * 
 * To remove a cookie just set it to expire on a date in the past:
 * setCookie("myCookie", "", -1);
 * 
 * NOTE: Some users disable the cookie functionality in their web browsers and in some 
 * countries the law says you have to declare what you use cookies for on a web page.
 *  
 * @param name		The name of the cookie.
 * @param value		The value of the cookie.
 * @param expire	Optional. How many days into the future the cookie expires. Default: 28 days later
 * 
 */
function setCookie(name, value, expire) {
	var today = new Date();
	if (expire == undefined) expire = 28;
	var expiration = new Date(today.getTime() + expire * 24 * 60 * 60 * 1000);
	if (value != null && value != "") { 
		document.cookie=name + "=" + escape(value) + "; path=/; expires=" + expiration.toGMTString();
	}
}



function getCookie(name) {
	var index = document.cookie.indexOf(name + "=");
	if (index == -1) {
		return null;
	}
	index = document.cookie.indexOf("=", index) + 1;
	var endstr = document.cookie.indexOf(";", index);
	if (endstr == -1) {
		endstr = document.cookie.length;
	}
	return unescape(document.cookie.substring(index, endstr));
}



/**
 * Shows a debug "window" with the given msg and timestamp. 
 * @param msg		Message string to display along with timestamp.
 * @param id		Optional. The DIV to which the msg should be appended.
 */
var debug = function(msg, id) {
	if (!id) {
		id = "debug_window";
	}
	var debug_window = document.getElementById(id);
	if (!debug_window) {
		var newdiv = document.createElement("div");
		newdiv.setAttribute("id", id);
		var newtext = document.createTextNode("Debug log (dblclick to hide)");
		newdiv.appendChild(newtext);
		document.getElementsByTagName("body")[0].appendChild(newdiv);

		//set some style
		debug_window = document.getElementById(id);
		debug_window.style.position = "absolute";
		debug_window.style.left = "200px";
		debug_window.style.top = "500px";
		debug_window.style.width = "300px";
		debug_window.style.height = "200px";
		debug_window.style.padding = "10px";
		debug_window.style.border = "1px solid black";
		debug_window.style.color = "black";
		debug_window.style.backgroundColor = "#FF6666";
		debug_window.style.overflow = "auto";
		debug_window.style.textAlign = "left";
		debug_window.style.fontFamily = "Arial, Helvetica, sans-serif";
		debug_window.style.fontSize = "10pt";
		debug_window.style.fontWeight = "normal";
		debug_window.style.zIndex = 99999;
		debug_window.ondblclick = function() { this.style.display = "none"; }
		debug_window.onmousedown = function(e) {
			var pos = mouseCoords(e);
			var objPos = getPosition(debug_window);
			debug_window.mouseOffset = {x: pos.x - objPos.x, y: pos.y - objPos.y};

			this.old_mousemove = document.body.onmousemove;
			document.body.onmousemove = function(e) {
				//drag window
				var pos = mouseCoords(e);
				debug_window.style.left = (pos.x - debug_window.mouseOffset.x) + "px";
				debug_window.style.top = (pos.y - debug_window.mouseOffset.y) + "px";
			}
			this.old_mouseup = document.body.onmouseup;
			document.body.onmouseup = function(e) {
				//restore old onmouseup & onmousemove
				var debug_window = document.getElementById(id);
				document.body.onmouseup = debug_window.old_mouseup;
				document.body.onmousemove = debug_window.old_mousemove;
			}
		}
	}

	var t = new Date();
	var time = (t.getHours() < 10 ? "0" : "") + t.getHours() + ":";
	time += (t.getMinutes() < 10 ? "0" : "") + t.getMinutes() + ":";
	time += (t.getSeconds() < 10 ? "0" : "") + t.getSeconds() + "." + t.getMilliseconds();
	debug_window.innerHTML += "<br />[" + time + "] <span style='font-weight: bold;'>" + msg+"</span>";
	debug_window.scrollTop = debug_window.scrollHeight;
	debug_window.style.display = "block";
}

function trim(str, chars) {
    return ltrim(rtrim(str, chars), chars);
}

function ltrim(str, chars) {
    chars = chars || "\\s";
    return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
}

function rtrim(str, chars) {
    chars = chars || "\\s";
    return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
}

function mouseCoords(event) {
	if (!event) var event = window.event;
	if (event.pageX || event.pageY) {
		return {x:event.pageX, y:event.pageY};
	}
	return {
		x:event.clientX + document.body.scrollLeft - document.body.clientLeft,
		y:event.clientY + document.body.scrollTop  - document.body.clientTop
	};
}

//adapted from quirksmode.org
function getPosition(obj) {
	var curleft = 0, curtop = 0;
	if (obj.offsetParent) {
		while (obj.offsetParent) {
			curleft += obj.offsetLeft
			curtop += obj.offsetTop
			obj = obj.offsetParent;
		}
	} else if (obj.x) {
		curleft += obj.x;
		curtop += obj.y;
	}
	return {x: curleft, y: curtop};
}


/**
 * Returns the given <div> object
 *
 * @param divName   the id/name of the <div> tag
 */
function getDiv(divName) {
    return document.getElementById ?
        document.getElementById(divName) :
        document.all ?
            document.all[divName] :
            null;
}


/*
function getDimensions(obj) {
	if (typeof(obj) == "string") {
		obj = document.getElementById(obj);
	}
    var display = obj.getStyle('display');
    if (display != 'none' && display != null) // Safari bug
      return {width: element.offsetWidth, height: element.offsetHeight};

    // All *Width and *Height properties give 0 on elements with display none,
    // so enable the element temporarily
    var els = obj.style;
    var originalVisibility = els.visibility;
    var originalPosition = els.position;
    var originalDisplay = els.display;
    els.visibility = 'hidden';
    els.position = 'absolute';
    els.display = 'block';
    var originalWidth = element.clientWidth;
    var originalHeight = element.clientHeight;
    els.display = originalDisplay;
    els.position = originalPosition;
    els.visibility = originalVisibility;
    return {width: originalWidth, height: originalHeight};
  }
}
*/


/**
 * Shows the status message panel
 */
function showStatusMessage(messageStatus, messageTitle, message) {
	var titleObj = document.getElementById("statusMessage_title");
	var headerObj = document.getElementById("statusMessage_header");
	var messageObj = document.getElementById("statusMessage_message");
	if (headerObj) {
		if (messageStatus == "INFO") {
			headerObj.className = "boxHeaderGreen";
			setTimeout("closeStatusMessage()", 3000);
		} else {
			headerObj.className = "boxHeaderRed";
		}
	
		titleObj.innerHTML = messageTitle + ":";
		messageObj.innerHTML = message;
	
	    var msgDiv = document.getElementById("statusMessage");
	    msgDiv.style.visibility = "visible";
	    msgDiv.style.left = 400;
	}
}

/**
 * Shows the status message panel with the information from an action xml tag
 */
function showStatusMessageWithActionTag(actionTag) {
	if (!actionTag) {
		return;
	}
	var status = actionTag.getAttribute('status');
	var message = actionTag.firstChild.nodeValue;
	var title = actionTag.getAttribute('statusTitle');

	showStatusMessage(status, title, message);
}

/**
 * Shows the status message panel with the information from the AJAX response
 */
function showStatusMessageFromAjaxResponse(ajaxResponse) {
    var actionTag = ajaxResponse.responseXML.documentElement.getElementsByTagName('Site')[0].getElementsByTagName('Action')[0];
    showStatusMessageWithActionTag(actionTag);
}

/**
 * OPens an overlay panel with the given header, body and footer.
 * @param 	header	the html for the header
 * @param	body	the html for the body
 * @param	footer	the html for the footer
 * @param	close	whether or not to allow closing of the panel
 * @returns			the object reference use to close the panel
 * @requires		YUI Container with dependencies:
 *                      <link rel="stylesheet" type="text/css" href="/resources/ui/scripts/yui/build/container/assets/skins/sam/container.css" />
 *                      <script type="text/javascript" src="/resources/ui/scripts/yui/build/yahoo-dom-event/yahoo-dom-event.js"></script>
 *                      <script type="text/javascript" src="/resources/ui/scripts/yui/build/element/element-min.js"></script>
 *                      <script type="text/javascript" src="/resources/ui/scripts/yui/build/container/container_core-min.js"></script>
 *                      <script type="text/javascript" src="/resources/ui/scripts/yui/build/container/container-min.js"></script>
 */
openOverlayPanel = function(header, body, footer, close) {
	if ("undefined" == typeof close) {
        close = true;
    }

	var currentPanel = new YAHOO.widget.Panel("tibetPanel",
		{   fixedcenter:true,
		    close:false,
		    draggable:true,
		    zindex:10000,
		    modal:true,
		    visible:false
		}
	);

	var closeHTML = "";
	if (close) {
		closeHTML = "<div style=\"position: absolute; top: 0px; right: 1px;\">" +
				"<a href=\"javascript: void(0);\" onclick=\"Screen.Utils.Panel.close();\" " +
				"onmouseover=\"this.style.backgroundPosition = 'bottom left';\" " +
				"onmouseout=\"this.style.backgroundPosition = 'top left';\" " +
				"style=\"background: transparent url('/resources/ui/skins/admin/images/button_closePopup.png'); display:block; width: 14px; height: 14px; font-size: 1px; line-height: 1px;\">" +
				"</a>" +
				"</div>";
	}
	header = "<div style=\"position: relative;\">" + header + "&nbsp;" + closeHTML + "</div>";
	currentPanel.setHeader(header);
	if (body) {
		currentPanel.setBody(body);
	}
	if (footer) {
		currentPanel.setFooter(footer);
	}

	currentPanel.render(document.body);
	currentPanel.show();

	return currentPanel;
}

