/**
 * Slik base utility functions for XMS/backoffice
 * @copyright 2008-2009 Slik BV
 */


/****************************************************************************
 ****************************************************************************/

/**
 * EXCEPTION HANDLING
 * 
 * @copyright 2009 Slik BV
 */

function Exception(message) {
	this.message = message;
	this.name = "Exception";
}

Exception.prototype.toString = function() {
	return this.message;
}

window.onerror = function(message, url, line) {
	//	Firefox always adds this prefix to error messages, delete it
	message = message.replace("uncaught exception: ", "");
	
	alertMessage =  "Er is een fout opgetreden tijdens het verwerken van uw verzoek.\n\n";
	alertMessage += message;
	if (url) {
		alertMessage += "\nURL: " + url;
	}
	if (line) {
		alertMessage += "\nLine: " + line;
	}
	
	//	Log the error
	if (typeof Slik.Logger != "undefined" && Slik.Logger) {
		Slik.log(message, "error", "Exception");
	}
	
	//	Display the error on screen
	alert(alertMessage);
	
	return true;
}

/****************************************************************************
 ****************************************************************************/

/**
 * UTILS
 *
 * @copyright 2008 Slik BV
 */

/**
 * Declare namespace objects
 */
if (typeof Slik == "undefined" || !Slik) {
	Slik = new Object();
}
if (typeof Slik.Util == "undefined" || !Slik.Util) {
	Slik.Util = new Object();
}


/**
 * Constant for Slik.Util.padString: Pad to the left
 * @var string
 */
Slik.Util.padLeft = "LEFT";

/**
 * Constant for Slik.Util.padString: Pad to the right
 * @var string
 */
Slik.Util.padRight = "RIGHT";


/**
 * Returns the input string padded to the specified length.
 *
 * @param string str Input string
 * @param integer len Length
 * @param char padChar Character to pad with
 * @param string direction Slik.Util.padLeft or Slik.Util.padRight
 * @return string Padded string
 */
Slik.Util.padString = function(str, len, padChar, direction) {
	var result = null;
	if (padChar == null) {
		padChar = " ";
	}
	if (direction == null) {
		direction = Slik.Util.padRight;
	}
	str = "" + str;
	
	if (str.length < len) {
		padding = Array(len - str.length + 1).join(padChar);
		if (direction == Slik.Util.padLeft) {
			result = padding + str;
		} else {
			result = str + padding;
		}
	} else {
		result = str;
	}
	
	return result;
}


/**
 * Returns a formatted time (HH:mm:ss) for a certain Date object.
 *
 * @param Date date Date object, or nothing/null for current date
 * @return string Time string, e.g. "21:46:11"
 */
Slik.Util.getTimeString = function(date) {
	if (date == null) {
		date = new Date();
	}
	
	var timeString = Slik.Util.padString(date.getHours(),   2, "0", Slik.Util.padLeft);
	timeString += ":";
	timeString += Slik.Util.padString(date.getMinutes(), 2, "0", Slik.Util.padRight);
	timeString += ":";
	timeString += Slik.Util.padString(date.getSeconds(), 2, "0", Slik.Util.padLeft);
	return timeString;
}


Slik.Util.isEmpty = function(field) {
	if (field.type == "checkbox") {
		empty = !field.checked;
	} else {
		//	Trim text values
		value = field.value.replace(/[^a-zA-Z0-9]/g, "");
		empty = (value=="") || (value=="na") || (value=="NA");		//	"N/A" is commonly entered
	}
	return empty;	
}


/**
 * Checks if required fields (of which the CSS class contains "required") are filled.
 * If not, creates an error alert.
 *
 * @param mixed checkedForm Form object, or form id
 * @param boolean printFieldNames If true (default), print the missing field names in the error alert.
 * @return boolean True if all the required fields are non-empty
 */
Slik.Util.checkRequiredFields = function(checkedForm, printFieldNames) {

	if (typeof checkedForm === "string") {
		checkedForm = document.getElementById(checkedForm);
	}
	if (typeof checkedForm === "undefined") {
		throw new Exception("uti 001 Form not found");
	}
	if (printFieldNames == null) {
		printFieldNames = true;
	}

	var emptyFieldsStr = "";
	var i;
	for (i=0; i<checkedForm.elements.length; i++) {
		field = checkedForm.elements[i];
		
		if (field.className.indexOf("required") != -1) {
			if (Slik.Util.isEmpty(field)) {
				emptyFieldsStr += "\n- " + field.name.replace(/_/g, " ").replace(/\[/g, "").replace(/\]/g, "");
			}
		}
		
		if(field.name == 'email' && field.value.indexOf('@') < 0) {
			// I know, but it's better than nothing I guess (RB)
			alert("Er is een ongeldig emailadres ingevuld");
			return false;
		}
	}
	
	if (emptyFieldsStr) {
		if (printFieldNames) {
			errorText = "Een of meer verplichte velden waren niet ingevuld:" + emptyFieldsStr + "\n\nControleer alstublieft uw invoer en probeer het opnieuw.";
		} else {
			errorText = "Een of meer verplichte velden waren niet ingevuld.\nControleer alstublieft uw invoer en probeer het opnieuw.";
		}
		alert(errorText);
		return false;
	}
	
	return true;
}


/**
 * Returns the week number for a Date.
 *
 * @param Date date The date object
 * @param boolean julian If true, use Julian calendar, otherwise (default) use Gregorian
 * @return integer Week number
 */
Slik.Util.getWeekNumber = function(date, useJulian) {
	var year = date.getFullYear();
	var month = date.getMonth();
	var day = date.getDate();
	
	month += 1; //use 1-12
	
	var a = Math.floor((14-(month))/12);
	var y = year+4800-a;
	var m = (month)+(12*a)-3;
	
	if (useJulian) {
		var jd = (day+1)+Math.Round(((153*m)+2)/5)+(365+y) + 
			Math.round(y/4)-32083;    // (julian calendar)
	} else {	
		var jd = day + Math.floor(((153*m)+2)/5) + 
			(365*y) + Math.floor(y/4) - Math.floor(y/100) + 
			Math.floor(y/400) - 32045;	// (gregorian calendar)
	}
	
	var d4 = (jd+31741-(jd%7))%146097%36524%1461;
	var L = Math.floor(d4/1460);
	var d1 = ((d4-L)%365)+L;
	var result = Math.floor(d1/7) + 1;
	return result;
}


/**
 * Returns the month name for a month.
 *
 * @param mixed month Month number (1-12) or Date object
 * @param string language Language tag, e.g. "NL" (default) or "EN"
 * @return string Month name
 */
Slik.Util.getMonthName = function(month, language) {
	if (language == null) {
		language = "NL";
	}
	
	if (typeof month === "object") {
		var monthIndex = month.getMonth();
	} else {
		var monthIndex = month -1;
	}
	if (monthIndex < 0 || monthIndex >= 12) {
		throw new Exception("uti 002 Invalid month number: " + monthIndex);
	}
	
	var monthNames = {
		"EN": ["jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec"],
		"NL": ["jan", "feb", "mrt", "apr", "mei", "jun", "jul", "aug", "sep", "okt", "nov", "dec"]
	};
	
	var result = monthNames[language][monthIndex];
	return result;
}


/**
 * Gets the value of a cookie.
 * @param string cookieName Name of cookie
 * @return string Cookie value
 */
Slik.Util.getCookie = function(cookieName) {
	var theCookie=""+document.cookie;
	var ind=theCookie.indexOf(cookieName);
	if (ind==-1 || cookieName=="") {
		return "";
	}
	var ind1=theCookie.indexOf(';',ind);
	if (ind1==-1) {
		ind1=theCookie.length;
	}
	return unescape(theCookie.substring(ind+cookieName.length+1,ind1));
}

/**
 * Sets the value of a cookie.
 * @param string cookieName Name of cookie
 * @param string cookieValue New value of cookie
 * @param integer nDays Expiration days of cookie (default is 1)
 * @return string Cookie value
 */
Slik.Util.setCookie = function(cookieName, cookieValue, nDays) {
	if (cookieValue == null) {
		cookieValue = "";
	}
	
	var today = new Date();
	var expire = new Date();
	if (nDays==null || nDays==0) {
		nDays=1;
	}
	expire.setTime(today.getTime() + 3600000*24*nDays);
	document.cookie = cookieName+"="+encodeURIComponent(cookieValue) + ";expires="+expire.toGMTString();
}

/**
 * Wait for a certain element to appear before executing a function.
 */
Slik.Util.waitFor = function(elementId, callback) {
	if (document.getElementById(elementId)) {
		//	Element found! Perform the callback and call it quits
		callback();
	} else {
		//	Wait 100ms for the element to become available
		window.setTimeout("Slik.Util.waitFor('" + elementId + "', " + callback + ")", 100);
	}
}

/**
 * This function is used in v13 templates, but due to a tpl inclusion problem, 
 * it could be called in v11 too!
 */
Slik.Util.waitForElement = function (elementId, callback) {
	Slik.Util.waitFor(elementId, callback);
}

/**
 * This function appends 'content' to 'element'.
 * This function uses a weird hack to hack around the bug in Mozilla browsers
 * that reverts the dropdown lists in the existing element to their initial
 * state.
 */
Slik.Util.appendToElement = function(element, content) {
	// determine if this is a mozilla/firefox-like browser or not
	var nua = navigator.userAgent;
	var konq = (nua.indexOf('Konqueror') != -1);
	var saf = (nua.indexOf('Safari') != -1);
	var moz = (nua.indexOf('Gecko') != -1 && !saf && !konq);
	
	if (moz) {
		var rng = document.createRange();
		rng.setStartBefore(element);
		var html_fragment = rng.createContextualFragment(content);
		element.appendChild(html_fragment);
	}
	else {
		element.innerHTML += content;
	}
}

/**
 * Returns an url parameter.
 * @param string url The url
 * @param string name Name of the parameter to get
 * @return string|null Value of the parameter if found, or null if not found
 */
Slik.Util.getUrlParameter = function(url, name) {
	name = name.replace(/[\[]/,"\\\[").replace(/[\]]/,"\\\]");
	var regexp = new RegExp("[\\?&]"+name+"=([^&#]*)");
	var regexpMatches = regexp.exec(url);
	
	var result = null;
	if (regexpMatches != null) {
		result = regexpMatches[1];
	}
	return result;
}

/**
 * Equivalent of php's html_entity_decode
 * @var string string The HTML string to decode
 * @return string The decoded string
 */
Slik.Util.decodeHtmlEntities = function(string) {
	var textarea = document.createElement("textarea");
	textarea.innerHTML = string.replace(/</g, "&lt;").replace(/>/g, "&gt;");
	var result = textarea.value;
	return result;
}

/****************************************************************************
 ****************************************************************************/

/**
 * LOGGER
 *
 * Inline logger depends on YAHOO.widget.LogReader. See also: 
 * - http://developer.yahoo.com/yui/docs/YAHOO.util.History.html
 * - http://developer.yahoo.com/yui/examples/history/history-navbar.html
 *
 * @copyright 2008 Slik BV
 */
 
 
/**
 * Declare namespace objects
 */
if (typeof Slik == "undefined" || !Slik) {
	Slik = new Object();
}
if (typeof Slik.Logger == "undefined" || !Slik.Logger) {
	Slik.Logger = new Object();
}
if (typeof Slik.Logger.Alert == "undefined" || !Slik.Logger.Alert) {
	Slik.Logger.Alert = new Object();
}
if (typeof Slik.Logger.Firebug == "undefined" || !Slik.Logger.Firebug) {
	Slik.Logger.Firebug = new Object();
}
if (typeof Slik.Logger.Inline == "undefined" || !Slik.Logger.Inline) {
	Slik.Logger.Inline = new Object();
}


/**
 * Active logger object, or null if none is chosen
 * @var object
 */
Slik.Logger.currentLogger = null;

/**
 * Supported logger types
 * @var array
 */
Slik.Logger.loggers = ["Alert", "Firebug", "Inline"];


/**
 * Augment Slik object with Slik.log function which types a lot faster.
 *
 * @param string text Text to log
 * @param string priority Priority: "debug", "info", "warn", "error"
 * @param string component Component name, e.g. "Slik.Workspace"
 * @return void 
 */
Slik.log = function(text, priority, component) {
	Slik.Logger.log(text, priority, component);
}


/**
 * Log something to the console.
 *
 * @param string text Text to log
 * @param string priority Priority: "debug", "info", "warn", "error"
 * @param string component Component name, e.g. "Slik.Workspace"
 * @return void
 */
Slik.Logger.log = function(text, priority, component) {
	if (!Slik.Logger.currentLogger) {
		return;
	}
	
	if (priority == null) {
		priority = "debug";
	}
	if (component == null) {
		component = "global";
	}
		
	Slik.Logger.currentLogger.log(text, priority, component);
}


/**
 * Initializes the Logger with a chosen logger name, or (with no parameters) use the user's
 * default logger.
 *
 * @param object Optional logger object, e.g. Slik.Logger.Alert, Slik.Logger.Inline, Slik.Logger.Firebug
 * @return void
 */
Slik.Logger.initialize = function(loggerName) {
	if (loggerName == null) {
		loggerName = Slik.Logger.getDefault();
	}
	Slik.Logger.select(loggerName);
	
	Slik.log("Initialized with logger: " + loggerName, "info", "Slik.Logger");
}


/**
 * Selects a Logger by name (e.g. "Slik.Logger.Firebug"), and optionally makes it default
 *
 * @param string loggerName Name of logger, e.g. "Slik.Logger.Firebug"... null or "null" for no logging
 * @param boolean makeDefault Set to true if this logger must be the default for subsequent page loads
 * @return void
 */
Slik.Logger.select = function(loggerName, makeDefault) {
	if (!loggerName || loggerName == "null") {
		Slik.Logger.selectLogger(null);
		loggerName = "null";
		
	} else if (loggerName.match(/^Slik.Logger.[A-Za-z]+$/)) {
		eval("Slik.Logger.selectLogger(" + loggerName + ")");
		
	} else {
		throw new Exception("log 004 Cannot select logger, invalid name: " + loggerName);
	}
	
	Slik.log("Logger selected: " + loggerName, "debug", "Slik.Logger");
	
	if (makeDefault) {
		Slik.Logger.setDefault(loggerName);
	}
}


/**
 * Switch to a different Logger object.
 *
 * @param object Logger object, e.g. Slik.Logger.Alert, Slik.Logger.Inline, Slik.Logger.Firebug
 */
Slik.Logger.selectLogger = function(logger) {
	if (Slik.Logger.currentLogger != null) {
		Slik.log("Logger destroyed", "warn", "Slik.Logger");
		Slik.Logger.currentLogger.destroy();
	}
	
	Slik.Logger.currentLogger = logger;
	if (logger == null) {
		return;
	}
	var initializeResult = Slik.Logger.currentLogger.initialize();
	if (!initializeResult) {
		Slik.Logger.currentLogger = null;
		alert("log 003 Requested logger failed to initialize");
	}
}


Slik.Logger.getDefault = function() {
	var result = Slik.Util.getCookie("Slik.Logger.defaultLogger");
	return result;
}


Slik.Logger.setDefault = function(loggerName) {
	Slik.Util.setCookie("Slik.Logger.defaultLogger", loggerName);
	Slik.log("Logger stored as default: " + loggerName, "debug", "Slik.Logger");
}


/*************************************************************************************
 * ALERT LOGGER
 */


/**
 * Initializes the Alert logger. This is a no-op.
 *
 * @return boolean True if successful
 */
Slik.Logger.Alert.initialize = function() { 
	return true;
}


/**
 * Logs a message through the Alert logger.
 *
 * @param string text Text to log
 * @param string priority Priority: "debug", "info", "warn", "error"
 * @param string component Component name, e.g. "Slik.Workspace"
 * @return void
 */
Slik.Logger.Alert.log = function(text, priority, component) {
	var alertMessage = priority.toUpperCase() + " [" + component + "] " + text;
	alert(alertMessage);
}


/**
 * Destroys the Alert logger. This is a no-op.
 *
 * @return boolean True if successful
 */
Slik.Logger.Alert.destroy = function() {}


/*************************************************************************************
 * FIREBUG LOGGER
 */


/**
 * Initializes the Firebug logger. Firebug must be enabled for the (sub)domain
 * running this script. If not, we fail to initialize.
 *
 * @return boolean True if successful
 */
Slik.Logger.Firebug.initialize = function() {
	if (typeof console == "object") {
		result = true;
	} else {
		result = false;
	}
	return result;
}


/**
 * Logs a message through the Firebug logger.
 *
 * @param string text Text to log
 * @param string priority Priority: "debug", "info", "warn", "error"
 * @param string component Component name, e.g. "Slik.Workspace"
 * @return void
 */
Slik.Logger.Firebug.log = function(text, priority, component) {
	var logString = "%s [%s] %s";
	var date = Slik.Util.getTimeString(new Date());	
	switch (priority) {
		case "info":
			console.info(logString, date, component, text);
			break;
		case "warn":
			console.warn(logString, date, component, text);
			break;
		case "error":
			console.error(logString, date, component, text);
			break;
		default:
			console.debug(logString, date, component, text);
			break;
	}
}


/**
 * Destroys the Firebug logger. This is a no-op.
 *
 * @return void
 */
Slik.Logger.Firebug.destroy = function() { }


/*************************************************************************************
 * INLINE LOGGER (using YAHOO.widget.LogReader)
 */
 

/**
 * A reference to the LogReader widget
 * @var object
 */
Slik.Logger.Inline.logReaderWidget = null;


/**
 * Initializes the inline logger (using YUI LogReader widget). 
 *
 * @return boolean True if successful
 * @todo Why does class "yui-skin-sam" change the default fonts? YUI LogReader wants this, but ugh...
 */
Slik.Logger.Inline.initialize = function() {
	document.body.className += " yui-skin-sam";
	
	if (Slik.Logger.Inline.logReaderWidget) {
		//	Revive a hidden log reader
		Slik.Logger.Inline.logReaderWidget.show();
		return true;
	}
	
	if (!YAHOO.util.YUILoader) {
		throw new Exception("Inline logger requires yui/build/yuiloader/yuiloader-min.js");
	}
	
	var loader = new YAHOO.util.YUILoader();
	loader.insert({
		require: ['fonts','dragdrop','logger'],
		base: 'algemeen/js/yui/build/',			
		onSuccess: function(loader) {
			logReader = new YAHOO.widget.LogReader("compact", {verboseOutput:false, newestOnTop:false});
			logReader.setTitle("Inline Logger");
			Slik.Logger.Inline.logReaderWidget = logReader;
		}
	});
	return true;
}


/**
 * Logs a message through the inline logger (using YUI logger widget).
 *
 * @param string text Text to log
 * @param string priority Priority: "debug", "info", "warn", "error"
 * @param string component Component name, e.g. "Slik.Workspace"
 * @return void
 */
Slik.Logger.Inline.log = function(text, priority, component) {
	if (Slik.Logger.Inline.logReaderWidget) {
		YAHOO.log(text, priority, component);
	}
}


/**
 * Destroys the inline logger. This is a no-op.
 *
 * @return void
 * @todo Clears all CSS classes from the body, should really only delete the class "yui-skin-sam"
 */
Slik.Logger.Inline.destroy = function() {
	if (Slik.Logger.Inline.logReaderWidget) {
		Slik.Logger.Inline.logReaderWidget.hide();
	}
	document.body.className = "";
}


/****************************************************************************
 ****************************************************************************/

/**
 * HTTP request functions
 *
 * @copyright 2008 Slik BV
 */


/**
 * Declare namespace objects
 */
if (typeof Slik == "undefined" || !Slik) {
	Slik = new Object();
}
if (typeof Slik.HTTP == "undefined" || !Slik.HTTP) {
	Slik.HTTP = new Object();
}


/**
 * Static variable: Number of active HTTP requests
 * @var integer
 */
Slik.HTTP._activeRequests = 0;


/**
 * Performs a GET request to a certain url.
 *
 * @param string   requestUrl        URL to request
 * @param array    requestParameters Associative array of query parameters to append to the URL
 * @param function callbackFunction  Function to call when the request completes
 * @param mixed    callbackArguments Variable to pass to the callback function when the request completes (can be omitted)
 * @return void
 */
Slik.HTTP.get = function(requestUrl, requestParameters, callbackFunction, callbackArguments) {
	Slik.log("get called; requestUrl='" + requestUrl + "'", "debug", "Slik.HTTP");
	
	//	Determine the full URL, which is requestUrl with a query-string from requestParameters
	var fullUrl = requestUrl;
	var encodedParameters = Slik.HTTP.encodeAsQueryString(requestParameters);
	if (encodedParameters) {
		if (fullUrl.indexOf("?") == -1) {
			fullUrl += "?";
		} else {
			fullUrl += "&";
		}
		fullUrl += encodedParameters;
	}
	
	Slik.HTTP._performRequest("GET", fullUrl, null, callbackFunction, callbackArguments);
}


/**
 * Performs a POST request to a certain url.
 *
 * @param string   requestUrl        URL to request
 * @param array    requestParameters Associative array of query parameters to send as POST-data
 * @param function callbackFunction  Function to call when the request completes
 * @param mixed    callbackArguments Variable to pass to the callback function when the request completes (can be omitted)
 * @return void
 */
Slik.HTTP.post = function(requestUrl, requestParameters, callbackFunction, callbackArguments) {
	Slik.log("post called; requestUrl='" + requestUrl + "'", "debug", "Slik.HTTP");
	
	var postData = Slik.HTTP.encodeAsQueryString(requestParameters);
	Slik.HTTP._performRequest("POST", requestUrl, postData, callbackFunction, callbackArguments);
}


/**
 * Calls a Dzeta/XMS handler.
 *
 * @param string   handlerName            Handler name to request
 * @param array    requestParameters      Associative array of query parameters to send as POST-data
 * @param function callbackFunction       Function to call when the request completes
 * @param mixed    callbackArguments      Variable to pass to the callback function when the request completes (can be omitted)
 * @param mixed    userCallbackArguments  Variable to pass to the user callback function (only for getJSON)
 * @param string   handlerUrl             Override url to call (default is current url). Normally this should be left empty!
 * @return void
 */
Slik.HTTP.callHandler = function(requestHandler, requestParameters, callbackFunction, callbackArguments, userCallbackArguments, handlerUrl) {
	//	A handler url starts with the current page url, determine it.
	if (!handlerUrl) {
		if (typeof Slik.Workspace != "undefined" && Slik.Workspace) {
			handlerUrl = Slik.Workspace.getCurrentUrl();
		} else {
			handlerUrl = document.location.href;
		}
	}
	
	//	Remove everything from the url after #
	if ((p=handlerUrl.indexOf("#")) != -1) {
		handlerUrl = handlerUrl.substring(0, p);
	}
	
	//	Add method= GET parameter to the url
	if (handlerUrl.indexOf("?") == -1) {
		handlerUrl += "?";
	} else {
		handlerUrl += "&";
	}
	handlerUrl += "method=" + encodeURIComponent(requestHandler);
	//	XXX: remove an existing 'method' parameter from parameters
	
	var postData = Slik.HTTP.encodeAsQueryString(requestParameters);
	
	Slik.log("callHandler called; handlerUrl='" + handlerUrl + "', callbackArguments=" + callbackArguments + ", userCallbackArguments=" + userCallbackArguments, "debug", "Slik.HTTP");
	Slik.HTTP._performRequest("POST", handlerUrl, postData, callbackFunction, callbackArguments, userCallbackArguments);
}


/**
 * Submits a form to the server.
 *
 * @param FormElement form              Form to submit
 * @param string      method            Method, default is "post"
 * @param boolean     upload            True if form contains a file upload 
 * @param array       requestParameters Associative array of extra query parameters to send as POST-data
 * @param function    callbackFunction  Function to call when the request completes
 * @param mixed       callbackArguments Variable to pass to the callback function when the request completes (can be omitted)
 * @return void
 */
Slik.HTTP.sendForm = function(form, method, upload, requestParameters, callbackFunction, callbackArguments) {
	if (!method) {
		method = "post";
	}
	Slik.log("sendForm called; form='" + form.id + "'", "debug", "Slik.HTTP");
	Slik.HTTP._performRequest(method, form.action, requestParameters, callbackFunction, callbackArguments, null, form, upload);
}


/**
 * Returns the number of currently pending HTTP requests.
 *
 * @return integer number of currently pending HTTP requests
 */
Slik.HTTP.getActiveRequests = function() {
	return Slik.HTTP._activeRequests;
}


/**
 * Internal function to give the document a waiting cursor during requests.
 *
 * @return void
 */
Slik.HTTP._setWaitingCursor = function() {
	document.body.style.cursor = (Slik.HTTP.getActiveRequests() > 0) ? "wait" : "default";
}


/**
 * Internal function to perform a HTTP request.
 *
 * @param string      method                Method, i.e. "GET" or "POST"
 * @param string      url                   URL to request
 * @param string      postData              POST data to send
 * @param function    callbackFunction      Function to call when the request completes
 * @param mixed       callbackArguments     Variable to pass to the callback function when the request completes (can be omitted)
 * @param mixed       userCallbackArguments Variable to pass to the user callback function when the request completes (only with getJSON)
 * @param FormElement form                  Form to submit (can be omitted)
 * @param boolean     upload                True if form contains a file upload
 */ 
Slik.HTTP._performRequest = function(method, url, postData, callbackFunction, callbackArguments, userCallbackArguments, form, upload) {
	if (method!="get" && method!="GET" && method!="post" && method!="POST") {
		throw new Exception("htt 014 Invalid method");
	}
	
	Slik.log("_performRequest called, url='" + url + "', postData='" + postData + "', method=" + method + ", callbackArguments=" + callbackArguments + ", userCallbackArguments=" + userCallbackArguments + ", form=" + form + ", upload=" + upload, "debug", "Slik.HTTP");
	if (form) {
		//	When uploading files in applications over SSL and using IE, 
		//	set the third argument to true to prevent IE from throwing domain security errors. 
		upload = upload ? true : false;
		YAHOO.util.Connect.setForm(form, upload, false);
	}
	
	Slik.HTTP._activeRequests++;
	Slik.HTTP._setWaitingCursor();
	
	YAHOO.util.Connect.asyncRequest(method, url, 
		{
			success: function(o) {
				Slik.log("_performRequest: request finished successfully", "debug", "Slik.HTTP");
				Slik.HTTP._activeRequests--;
				Slik.HTTP._setWaitingCursor();
				
				var ResponseError = null;
				if (responseError = Slik.HTTP.getResponseError(o.responseText)) {
					throw new Exception(responseError);
				}
				
				if (callbackFunction) {
					callbackFunction(o, callbackArguments, userCallbackArguments);
				}
			},
			upload: function(o) {
				Slik.log("_performRequest: upload finished successfully", "debug", "Slik.HTTP");
				Slik.HTTP._activeRequests--;
				Slik.HTTP._setWaitingCursor();
				
				//	XXX: Errors are not detected!
				var ResponseError = null;
				if (responseError = Slik.HTTP.getResponseError(o.responseText)) {
					throw new Exception(responseError);
				}
				
				if (callbackFunction) {
					callbackFunction(o, callbackArguments, userCallbackArguments);
				}
			},
			failure: function(o) {
				Slik.log("_performRequest: failure: statusText='" + o.statusText + "'", "info", "Slik.HTTP");
				Slik.HTTP._activeRequests--;
				Slik.HTTP._setWaitingCursor();
				throw new Exception("htt 001 " + o.statusText);
			},
			argument: [callbackFunction, callbackArguments, userCallbackArguments]
		},
		postData);
}


/**
 * Callback handler that writes response HTML to an element on the page.
 *
 * @param object response Response object from _performRequest
 * @param mixed  element  Element object on the page, or element id pointing to an element
 * @return void
 */
Slik.HTTP.writeElement = function(response, element) {
	Slik.log("writeElement called, response=" + response + ", element=" + element, "info", "Slik.HTTP");
	
	if (typeof element == "string") {
		elementId = element;
		element = document.getElementById(elementId);
	}
	if (!element) {
		throw new Exception("htt 002 Element not found: '" + elementId + "'");
	}
	
	var html = response.responseText;
	
	element.innerHTML = html;
	Slik.HTTP._executeScripts(html);
}


Slik.HTTP.appendElement = function(response, elementId) {
	element = document.getElementById(elementId);
	Slik.Util.appendToElement(element, response.responseText);
}

/**
 * Finds all <script> tags in a HTML fragment and executes them.
 * This is necessary when writing HTML fragments to the page dynamically, because Javascript
 * in the HTML is not executed automatically by the browser.
 * @param string scripts HTML text possibly containing script tags
 * @return void
 */
Slik.HTTP._executeScripts = function(scripts) {
	
	var statements = new Array();	//	string that contains all contents of script tags
	var files = new Array();		//	array containing all JS references to load
	
	//	Find script tags in html, and distill them to statements and files
	statementsHaveWaitFor = false;
	scripts = scripts.replace(/<script([^>]*)>([\s\S]*?)<\/script>/gi, function() {
		if (scripts !== null) {
			var scriptAttributes = arguments[1];	// e.g.: type="text/javascript" src="js2/listView.js"
			var scriptContents   = arguments[2];	// e.g.: alert('hi');
			if (scriptAttributes != "") {
				var srcRegexp = new RegExp('[sS][rR][cC]=[\'"]?([a-zA-Z0-9_.:/~\?=\-]+)');
				var srcMatches = srcRegexp.exec(scriptAttributes);
				if (srcMatches !== null) {
					var srcFile = srcMatches[1];
					files[files.length] = srcFile;
					Slik.log("_executeScripts: Content references a Javascript file: " + srcFile, "debug", "Slik.HTTP");
				}
			}
			if (scriptContents != "") {
				//	Replace comments
				scriptContents = scriptContents.replace(/<!--/g, "");
				scriptContents = scriptContents.replace(/-->/g, "");
				statements[statements.length] = scriptContents;
				Slik.log("_executeScripts: Content contains inline Javascript: " + scriptContents, "debug", "Slik.HTTP");
				if (scriptContents.indexOf("Slik.Util.waitFor") > -1) {
					statementsHaveWaitFor = true;
					Slik.log("_executeScripts: Content has waitFor", "debug", "Slik.HTTP");
				}
			}
		}
		return '';});
	
	/**
	 * Refuse to process content that contains SCRIPT as well as SCRIPT SRC tags.
	 * Normally, SCRIPT tags are interpreted by the browser in succession. Remote sources are loaded fully
	 * before script execution proceeds. However, adding script elements dynamically, as we do here,
	 * does not exhibit this property. Therefore it is possible that when executing script statements,
	 * we call into scripts that are not loaded yet. To prevent these hard to debug problems, we disallow
	 * this use case entirely.
	 */
	if (files.length>0 && statements.length>0 && !statementsHaveWaitFor) {
		//throw new Exception("htt 013 Dynamic content cannot include both <script> and <script src> unless waitFor is used");
	}
	
	//	Add external JS references to the head of the document
	if (files.length > 0) {
		for (var i in files) {
			var scriptElement = document.createElement("script");
			scriptElement.setAttribute("src", files[i]);
			scriptElement.setAttribute("type", "text/javascript");
			document.getElementsByTagName("head")[0].appendChild(scriptElement); 
		}
	}
	
	//	Execute any literal code that was embedded in the html
	if (statements.length > 0) {
		var statementsString = statements.join("\n");
		if (window.execScript) {
			window.execScript(statementsString);
		} else {
			window.setTimeout(statementsString, 100);
		}
	}
}


/**
 * Callback handler that parses JSON and passes it to a user function.
 *
 * @param object   response              Response object from _performRequest
 * @param function callbackFunction      Function to call
 * @param mixed    userCallbackArguments Optional arguments to pass to the user function as a second parameter
 * @return void
 */
Slik.HTTP.getJSON = function(response, callbackFunction, userCallbackArguments) {
	Slik.log("getJSON: responseText=" + response.responseText, "debug", "Slik.HTTP");
	
	var parsedResponse = null;
	try {
		parsedResponse = YAHOO.lang.JSON.parse(response.responseText);
	} catch (e) {
		throw new Exception("htt 003 Server did not return a JSON response: " + response.responseText);
	}
	
	Slik.log("trying to call callback", "debug", "Slik.HTTP");
	callbackFunction(parsedResponse, userCallbackArguments);
	Slik.log("returned from callback", "debug", "Slik.HTTP");
}


/**
 * Returns an error if the response contained one, or null if the response is not an error.
 *
 * @param string responseText Response body
 * @return string A string describing the remote error, or null if the response is not an error
 */
Slik.HTTP.getResponseError = function(responseText) {
	var serverErrorToken = "Slik.HTTP.getResponseError(";	//	string to find
	
	var result = null;	
	if (responseText == "") {
		//	Empty response from server
		result = "htt 010 Empty response";
		
	} else if (responseText.indexOf("<b>Fatal error:</b>") != -1) {
		//	Response text contained an uncaught PHP fatal error
		result = "htt 011 Fatal server error";
		
	} else if ((p=responseText.indexOf(serverErrorToken)) != -1) {
		//	Dzeta server returned an error message, capture the error text
		q=responseText.indexOf(")", p + serverErrorToken.length);
		if (q == -1) {
			//	Weird, the ending ) did not appear in the response
			result = "htt 012";
		} else {
			result = responseText.substring(p + serverErrorToken.length, q);		
		}
	}
	
	if (result != null) {
		Slik.log("getResponseError: Response contained an error: " + result, "warn", "Slik.HTTP");
	}
	
	return result;
}


/**
 * Utility method to convert an associative array to a query string.
 *
 * @param array array Associative array of parameter name => parameter value
 * @return string URL-encoded string
 */
Slik.HTTP.encodeAsQueryString = function(array) {
	if (array == null) {
		return null;
	}
	if (typeof array != "object") {
		return null;
	}
	
	//	Get a list of "key=value" parameters
	var parameters = new Array();
	var count = 0;
	for (key in array) {
		value = array[key];
		parameters[count] = encodeURIComponent(key) + "=" + encodeURIComponent(value);
		count++;
	}
	
	//	Join them together in one string
	var result = parameters.join("&");
	
	Slik.log("encodeAsQueryString: array=" + array + ", result='" + result + "'", "debug", "Slik.HTTP");
	return result;
}
