var vtility = {
	rawClone: function(source) {
		if (typeof source == "object") {
			var result = new Object();
			for (var key in source)
				result[key] = vtility.rawClone(source[key]);
		}
		
		return source;
	},
	
	assert: function(condition) {
		if (!condition) {
			if ("console" in window) {
				console.error("Assertion failed!");
				console.trace();
			}
			else {
				$("body").append("Assertion failed!");
			}
		}
	},
	
	polymorph: function(raw) {
		if (raw.phpClassName in window) {
			vtility.assert(typeof(window[raw.phpClassName]) == "function");
			return new (window[raw.phpClassName])(raw);
		}
		else {
			if ("console" in window)
				console.error("Polymorphic type", raw.phpClassName, "is not defined!");
			else
				$("body").append("Polymorphic type" + raw.phpClassName + "is not defined!");
		}
	},
	
	// Many thanks to http://www.golimojo.com/etc/js-subclass.html
	subclass: function(constructor, superConstructor) {
		function surrogateConstructor() { }
		surrogateConstructor.prototype = superConstructor.prototype;
		var prototypeObject = new surrogateConstructor();
		prototypeObject.constructor = constructor;
		constructor.prototype = prototypeObject;
		
		constructor.superConstructor = superConstructor;
		if (superConstructor.subclassConstructors === undefined)
			superConstructor.subclassConstructors = new Array();
		for (var i = superConstructor; i != undefined; i = i.superConstructor)
			i.subclassConstructors.push(constructor);
	},
	
	build: function(string) {
		for (var i = 1; i < arguments.length; i++) {
			var needle = '{$' + i + '}';
			while (string.indexOf(needle) >= 0)
				string = string.replace(needle, arguments[i]);
		}
		return string;
	},
	
	nl2br: function(string) {
		while (string.indexOf("\n") >= 0)
			string = string.replace("\n", "<br />");
		return string;
	},
	
	// Trims thanks to http://www.webtoolkit.info/javascript-trim.html
	trim: function(str, chars) {
		return vtility.ltrim(vtility.rtrim(str, chars), chars);
	},
	
	ltrim: function(str, chars) {
		chars = chars || "\\s";
		return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
	},
	
	rtrim: function(str, chars) {
		chars = chars || "\\s";
		return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
	},
	
	alert: function(message) {
		if ($("#vAlerts").length == 0) {
			$('<div id="vAlerts"></div>').appendTo("body");
		}
		
		var valert = $('<div class="vAlert"><div class="vAlertRed">' + message + '</div><span class="vAlertText">' + message + '</span></div>').
			appendTo("#vAlerts");
		
		$(".vAlertRed", valert).animate({opacity: "0"});
		
		var removeFunction = function() {
			valert.animate({marginTop: "-=2em", height: "0", opacity: "0"}, "slow", undefined, function() {
				valert.remove();
			});
		}
		
		var timeoutID = setTimeout(removeFunction, 5000);
		
		valert.
			hover(function() { clearTimeout(timeoutID); }, removeFunction).
			click(removeFunction);
	},
	
	ajaxMutex: (function() {
		var locked = false;
		
		$().ajaxStart(function() {
			locked = true;
		}).ajaxStop(function() {
			locked = false;
		});
		
		return {
			locked: function() {
				return locked;
			}
		};
	})()
};

function callback(object, method) {
	if (typeof method == "function") {
		return function() { return method.apply(object, arguments); }
	}
	else {
		return function() { return object[method].apply(object, arguments); }
	}
}

// Intercepts events, and returns false to keep the next events from seeing them.
function interceptor(object, method) {
	if (typeof method == "function") {
		return function() { method.apply(object, arguments); return false; }
	}
	else {
		return function() { object[method].apply(object, arguments); return false; }
	}
}
