/**
 * 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) {
	//	MSIE ignores the message. Chrome 10+ also ignores the message when throwing from an external script.
	//	Try to get the error message from our global error variable.
	if (message == "Exception thrown and not caught" || message == "Script error.") {
		if (Slik.err) {
			message = Slik.err;
			Slik.err = null;
		}
	}
	
	//	Firefox always adds this prefix to error messages, delete it
	message = message.replace("uncaught exception: ", "");

	//	Chrome always adds this prefix to error messages, delete it
	message = message.replace("Uncaught ", "");
	
	alertMessage = message;
	if (url) {
		alertMessage += "\nURL: " + url;
	}
	if (line) {
		alertMessage += "\nLine: " + line;
	}
	
	Slik.dispatchError(message, alertMessage);
	
	//	Notifies MSIE that the error has been caught and no error dialog is needed.
	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();
}


Slik.err = null;


/**
 * Deprecated - don't use.
 */
Slik.throwException = function(message) {
        //      MSIE does not pass an Exception object or a message to window.onerror,
        //      it always says "Exception thrown and not caught". Therefore, store the
        //      message in a dirty global.
        Slik.err = message;
        
        //      Chrome does not support window.onerror at all. So we have no global
        //      exception handler. Still, we want to use the browser's exception mechanism.
        //      We have the choice between not showing the error (and missing feedback),
        //      or showing it always (but also showing it inside a try..catch block which
        //      might be unnecessary). To ease debugging, we opt for the latter approach,
        //      and we always dispatch the error, even if it should be caught.
        var khtml = navigator.userAgent.indexOf('KHTML') != -1;
        if (khtml) {
                Slik.dispatchError(message, message);
        }
        
        //      Throw and use the runtime's exception handler.
        throw new Exception(message);
}


Slik.dispatchError = function(message, alertMessage) {
	//	Log the error
	if (typeof Slik.Logger != "undefined" && Slik.Logger) {
		Slik.log(message, "error", "Exception");
	}
	
	alertMessage = "Er is een fout opgetreden tijdens het verwerken van uw verzoek.\n\n"
		+ "Als u deze fout wilt melden, dan kunt u onderstaande melding kopieren met de PrintScreen-knop, en vervolgens in een e-mail plakken.\n\n"
		+ "Error: " + alertMessage + "\n\n"
		+ "Browser: " + navigator.userAgent;
	
	//	Display the error on screen
	alert(alertMessage);
}


/**
 * 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";

Slik.Util.confirm = function (labelText, callback) {
	Slik.Dialog.show (labelText, Slik.Dialog.CONFIRM, null, null, null, callback);
}

/**
 * Retrieves a label from db and passes it to the provided callback function
 * 
 * @param string label name of the label
 */
Slik.Util.getLabel = function (label, callBack, callBackArguments, userCallBackArguments) {
	Slik.HTTP.callHandler('getLabel', {'label' : label}, Slik.HTTP.getJSON, callBack, callBackArguments, userCallBackArguments);
}
	
/**
 * 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 version of the string with all whitespace at start/end removed.
 * @param string str Input string
 * @return string Trimmed string
 */
Slik.Util.trimString = function(str) {
	var result = str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
	return result;
}

/**
 * Appends a parameter and a value to a URL link, depending on if it already has query parameters.
 * If the URL already has the provided parameter, the value will be replaced.
 *
 * @param string url URL string
 * @param string parameter The parameter to append to the url
 * @param string value The string to append to the url
 * @return string URL string with 'param=value' parameter added
 */
Slik.Util.urlAppendParameter = function(url, key, value) {
	
	var new_params = new Array();	
	var query_string = "";
	var url_parts = url.split("?");
	var new_url = url_parts[0];
	
	if (url_parts.length > 1) {	
		query_string = url_parts[1];
	}
	
	var existing_params = query_string.split("&");
	var counter = 0;
	if (existing_params.length > 0) {
		for (var y in existing_params) {
			if (existing_params[y]) {
				var splitted = existing_params[y].split("=");
				
				// 	If the current key has the same name as the new parameter, don't add it
				if (splitted[0] != key) {
					new_params[counter] = existing_params[y];
					counter++;
				}
			}
		}
	}
	
	// Add the new parameter
	new_params[counter] = key+"="+encodeURIComponent(value);
	if (new_params.length > 0) {
		new_url += "?"+new_params.join("&");
	}
	return new_url;
}

/**
 * Appends multiple parameters and their value to a URL link, depending on if it already has query parameters.
 * If the URL already has one the provided parameters, itīs value will be replaced.
 *
 * @param string url URL string
 * @param array parameters The parameters and their values to append to the url, format: {"param1" : "value", "param2" : "value"}
 * @return string URL string with 'param1=value&param2=value' parameter added
 */
Slik.Util.urlAppendParameters = function(url, parameters) {

	for (x in parameters) {
		url = Slik.Util.urlAppendParameter(url, x, parameters[x]);
	}
	return url;
}

/**
 * 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(Slik.err="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(Slik.err="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 DOM element to appear before executing a function.
 * @param string   elementId Id of the element
 * @param callback callback  The function to call
 */
Slik.Util.waitForElement = function(elementId, callback) {
	if (document.getElementById(elementId)) {
		callback();
	} else {
		//	Wait 100ms for the element to become available
		window.setTimeout("Slik.Util.waitForElement('" + elementId + "', " + callback + ")", 100);
	}
}


/**
 * Wait for a certain object to appear before executing a function.
 * @param string   objectName Name of the object in the global scope
 * @param callback callback   The function to call
 */
Slik.Util.waitForObject = function(objectName, callback) {
	if (Slik.Util.objectExists(objectName)) {
		callback();
	} else {
		//	Wait 100ms for the element to become available
		window.setTimeout("Slik.Util.waitForObject('" + objectName + "', " + callback + ")", 100);
	}
}

/**
 * XXX Deprecated function, but re-introduced because of a smarty rendering bug 
 * (it seems to mix up compiled tpl's from different dzeta versions)
 * 
 * Remove when above is fixed!
 */
Slik.Util.waitFor = function(elementId, callback) {
	Slik.Util.waitForElement(elementId, callback);
}


/**
 * Checks whether an object exists in the global scope
 * @param  string   objectName Name of the object in the global scope
 * @return boolean
 */
Slik.Util.objectExists = function(objectName) {	
	try {
		if (typeof eval(objectName) !== "undefined") {
			return true;
		}
	} catch (error) {
	}
	return false;
}


/**
 * 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 khtml = (nua.indexOf('KHTML') != -1);
	var saf = (nua.indexOf('Safari') != -1);
	var moz = (nua.indexOf('Gecko') != -1 && !saf && !khtml);
	
	if (moz || khtml) {
		var rng = document.createRange();
		rng.setStartBefore(element);
		var htmlFragment = rng.createContextualFragment(content);
		element.appendChild(htmlFragment);
	}
	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 Encode::html()
 * @param string string The string to HTML-encode
 * @return string The encoded string
 */
Slik.Util.encodeHtmlEntities = function(string) {
   var div = document.createElement("div");
   var text = document.createTextNode(string);
   div.appendChild(text);
   var result = div.innerHTML;
   result = result.replace(/\"/g, "&quot;");
   return result;
}; 

/**
 * Equivalent of php's html_entity_decode
 * @param 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(Slik.err="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(Slik.err="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)
 * @param boolean  non_critical		 True if http failure should not throw an exception
 * @return void
 */
Slik.HTTP.get = function(requestUrl, requestParameters, callbackFunction, callbackArguments, non_critical) {
	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, null, null, null, non_critical);
}


/**
 * 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)
 * @param boolean  non_critical	 	 True if http failure should not throw an exception
 * @return void
 */
Slik.HTTP.post = function(requestUrl, requestParameters, callbackFunction, callbackArguments, non_critical) {
	Slik.log("post called; requestUrl='" + requestUrl + "'", "debug", "Slik.HTTP");
	
	var postData = Slik.HTTP.encodeAsQueryString(requestParameters);
	Slik.HTTP._performRequest("POST", requestUrl, postData, callbackFunction, callbackArguments, null, null, null, non_critical);
}


/**
 * 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!
 * @param boolean  non_critical			  True if http failure should not throw an exception
 * @return void
 */
Slik.HTTP.callHandler = function(requestHandler, requestParameters, callbackFunction, callbackArguments, userCallbackArguments, handlerUrl, non_critical) {
	//	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);
	}
	
	// Append handler method to url
	handlerUrl = Slik.Util.urlAppendParameter(handlerUrl, "method", requestHandler);
	
	var postData = Slik.HTTP.encodeAsQueryString(requestParameters);
	
	Slik.log("callHandler called; handlerUrl='" + handlerUrl + "', callbackArguments=" + callbackArguments + ", userCallbackArguments=" + userCallbackArguments + ", non_critical=" + non_critical, "debug", "Slik.HTTP");
	Slik.HTTP._performRequest("POST", handlerUrl, postData, callbackFunction, callbackArguments, userCallbackArguments, null, null, non_critical);
}


/**
 * 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
 * @param boolean 	  non_critical			True if http failure should not throw an exception
 */ 
Slik.HTTP._performRequest = function(method, url, postData, callbackFunction, callbackArguments, userCallbackArguments, form, upload, non_critical) {
	if (method!="get" && method!="GET" && method!="post" && method!="POST") {
		throw new Exception(Slik.err="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++;
	
	if (!non_critical) {
		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(Slik.err=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(Slik.err=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();

				if (typeof non_critical == 'undefined' || !non_critical) {
					throw new Exception(Slik.err="htt 001 " + o.statusText + " (" + url + ")");
				}
			},
			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") {
		var elementId = element;
		element = document.getElementById(elementId);
	}
	if (!element) {
		Slik.log("htt 002 Element not found: '" + elementId + "'");
		return false;
		// Very annoying popup when element no longer exists: (for example, when navigating away from page)
		// throw new Exception(Slik.err="htt 002 Element not found: '" + elementId + "'");
	}
	
	var html = response.responseText;
	
	element.innerHTML = html;
	Slik.HTTP._executeScripts(html);
}


Slik.HTTP.appendElement = function(response, elementId) {
	var 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
	var 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(Slik.err="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(Slik.err="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");
};


/**
 * Callback handler that performs a server-directed task.
 *
 * @param object   response              Response object from _performRequest
 * @return void
 */
Slik.HTTP.performCommand = function(response) {
	Slik.log("performCommand: responseText=" + response.responseText, "debug", "Slik.HTTP");
	
	var commandResponse = null;
	try {
		commandResponse = YAHOO.lang.JSON.parse(response.responseText);
	} catch (e) {
		throw new Exception(Slik.err="htt 004 Server did not return a JSON response: " + response.responseText);
	}
	
	//	Redirect or reload the workspace
	if (commandResponse.redirect_url) {
		Slik.Workspace.navigateTo(commandResponse.redirect_url);
	} else if (commandResponse.reload) {
		Slik.Workspace.reload();
	}
	
	//	Display message
	if (commandResponse.message) {
		Slik.Dialog.show(commandResponse.message, commandResponse.message_type, null, null, null, null);
	}
};


/**
 * 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;
}


/**
 * Show dialog
 */
Slik.Dialog = {
	
	CONFIRM: "Confirm",
	ERROR:   "Error",
	INFO:    "Info",
	NOTIFICATION: "Notification",
	SUCCESS: "Success",
	WARNING: "Warning",
	
	OK:      "OK",
	CANCEL:  "Cancel",
	
	/**
	 * Opens a modal dialog.
	 * @param string   message   Body text for the dialog
	 * @param string   type      Type of dialog: any of Slik.Dialog.INFO, Slik.Dialog.WARNING, Slik.Dialog.ERROR, Slik.Dialog.CONFIRM
	 * @param string   title     Optional title for the dialog
	 * @param callback callback  Function to call when the user clicks a button or dismisses the dialog
	 * @param array    buttons   Associative array of buttonId => buttonTitle. The buttonId is passed to the callback. Default is only Slik.Dialog.OK and optionally Slik.Dialog.CONFIRM.
	 * @param boolean  autoClose When true, auto-close the dialog after 5 seconds. Or pass a number of seconds
	 * @return void
	 */
	show: function(message, type, title, autoClose, buttons, callback) {
		//	Set defaults
		if (!type) {
			type = Slik.Dialog.INFO;
		}
		if (!title) {
			title = type;
		}
		if (!buttons) {
			buttons = new Array();
			buttons[Slik.Dialog.OK] = Slik.Dialog.OK;
			if (type == Slik.Dialog.CONFIRM) {
				buttons[Slik.Dialog.CANCEL] = Slik.Dialog.CANCEL;
			}
		}
		
		//	Set callback
		Slik.Dialog._currentCallback = callback;
		
		//	Create YUI dialog
		var body = Slik.Dialog._buildBody(message, type, buttons);
		
		Slik.Dialog._currentDialog = new YAHOO.widget.Panel("Slik_Dialog", {
			width:       "300px",
			fixedcenter: true,
			close:       false,
			draggable:   false,
			zindex:      1000000,
			modal:       true,
			visible:     false
		});
		Slik.Dialog._currentDialog.setHeader(title);
		Slik.Dialog._currentDialog.setBody(body);
		Slik.Dialog._currentDialog.render(document.body);
		Slik.Dialog._currentDialog.show();
		
		//	Schedule auto close
		if (autoClose === true) {
			autoClose = 5;
		}
		if (autoClose) {
			Slik.Dialog._currentTimer = setTimeout("Slik.Dialog._hide()", autoClose * 1000);
		}
	},
	
	/**
	 * Hide the dialog and perform the callback
	 */
	_hide: function(arg) {
		if (!Slik.Dialog._currentDialog) {
			return;
		}
		
		//	Cancel a pending auto close
		if (Slik.Dialog._currentTimer) {
			clearTimeout(Slik.Dialog._currentTimer);
			Slik.Dialog._currentTimer = null;
		}
		
		//	Hide dialog
		Slik.Dialog._currentDialog.hide();
		Slik.Dialog._currentDialog = null;
		
		//	Call the callback
		if (Slik.Dialog._currentCallback) {
			if (arg === undefined) {
				arg = null;
			}
			Slik.Dialog._currentCallback(arg);
		}
		Slik.Dialog._currentCallback = null;
	},
	
	_buildBody: function(message, type, buttons) {
		var result = "<div id=\"dialog\" class=\"dialog " + type + "\">\n";
		result += "<p class=\"message\">" + message + "</p>\n";
		if (buttons) {
			result += "<div class=\"buttons\">\n";
			for (var buttonId in buttons) {
				var buttonName = buttons[buttonId];
				result += "<input class=\"button\" type=\"button\" value=\"" + buttonName + "\" onclick=\"Slik.Dialog._hide('" + buttonId + "')\" />\n";
			}
			result += "</div>\n";
		}
		result += "</div>\n";
		return result;
	},
	
	/**
	 * Reference to the currently active dialog
	 */
	_currentDialog: null,
	
	/**
	 * Reference to callback function for the currently active dialog
	 */
	_currentCallback: null,
	
	/**
	 * Current timeout
	 */
	_currentTimeout: null
	
};

