/* === Geometry =========================================================== */

function Point(x, y)
{
	if (arguments.length == 2)
	{
		if (isNaN(x)) x = 0;
		if (isNaN(y)) y = 0;

		this.x = x;
		this.y = y;
	}
	else
	{
		this.x = 0;
		this.y = 0;
	}
}

Point.prototype.clone = function()
{
	return new Point(this.x, this.y);
}


function Size(w, h)
{
	if (arguments.length == 2)
	{
		if (isNaN(w)) w = 0;
		if (isNaN(h)) h = 0;

		this.width = w;
		this.height = h;
	}
	else
	{
		this.width = 0;
		this.height = 0;
	}
}

Size.prototype.grow = function(x, y) // Size/Point
{
	var s = this.clone();

	if (arguments.length == 1)
	{
		if (typeof(x) === "object")  // x is a Size/Point
		{
			if (x.width != undefined) // x is a Size
			{
				s.width += x.width;
				s.height += x.height;
			}
			else // x is a Point
			{
				s.width += x.x;
				s.height += x.y;
			}
		}
		else
		{
			s.width *= x;
			s.height *= x;
		}
	}
	else
	{
		s.width += x;
		s.height += y;
	}

	return s;
}

Size.prototype.clone = function()
{
	return new Size(this.width, this.height);
}


function Rect(x1, y1, x2, y2)
{
	if (arguments.length == 2) // (Point, Point)
	{
		this.x1 = x1.x;
		this.y1 = x1.y;
		this.x2 = y1.x;
		this.y2 = y1.y;
	}
	else if (arguments.length == 4)
	{
		if (isNaN(x1)) x1 = 0;
		if (isNaN(y1)) y1 = 0;
		if (isNaN(x2)) x2 = 0;
		if (isNaN(y2)) y2 = 0;

		this.x1 = x1;
		this.y1 = y1;
		this.x2 = x2;
		this.y2 = y2;
	}
	else
	{
		this.x1 = 0;
		this.y1 = 0;
		this.x2 = 0;
		this.y2 = 0;
	}
}

Rect.prototype.getSize = function()
{
	return new Size(Math.abs(this.x2 - this.x1), Math.abs(this.y2 - this.y1));
}

Rect.prototype.getWidth = function()
{
	return Math.abs(this.x2 - this.x1);
}

Rect.prototype.getHeight = function()
{
	return Math.abs(this.y2 - this.y1);
}

Rect.prototype.getOrigin = function()
{
	return new Point(this.x1, this.y1);
}

Rect.prototype.getEnd = function()
{
	return new Point(this.x2, this.y2);
}

Rect.prototype.getCenter = function()
{
	return new Point((this.x1 + this.x2) / 2, (this.y1 + this.y2) / 2);
}

Rect.prototype.inflate = function(n)
{
	var r = this.clone();

	r.x1 -= n;
	r.y1 -= n;
	r.x2 += n;
	r.y2 += n;

	return r;
}

Rect.prototype.deflate = function(n)
{
	return this.inflate(-n);
}

Rect.prototype.move = function(x, y) // (int, int) / (Point)
{
	if (arguments.length == 1)
		return new Rect(x.x, x.y, x.x + this.x2 - this.x1, x.y + this.y2 - this.y1);
	else
		return new Rect(x, y, x + this.x2 - this.x1, y + this.y2 - this.y1);
}

Rect.prototype.centerIn = function(other)
{
	var dx = (other.x2 - other.x1) - (this.x2 - this.x1);
	var dy = (other.y2 - other.y1) - (this.y2 - this.y1);

	return this.move(other.x1 + dx / 2, other.y1 + dy / 2);
}

Rect.prototype.clone = function()
{
	return new Rect(this.x1, this.y1, this.x2, this.y2);
}

Rect.prototype.contains = function(other)  // normalize before calling
{
	return (this.x1 <= other.x1 && this.y1 <= other.y1 && this.x2 >= other.x2 && this.y2 >= other.y2);
}

Rect.prototype.fitRatio = function(bounds)  // normalize before
{
	var res = this.clone();

	var maxWidth = bounds.x2 - bounds.x1;
	var maxHeight = bounds.y2 - bounds.y1;

	var width = this.x2 - this.x1;
	var height = this.y2 - this.y1;

	var r = Math.min(maxWidth / width, maxHeight / height);

	res.x2 = res.x1 + width * r;
	res.y2 = res.y1 + height * r;

	return res;
}

Rect.prototype.toString = function()
{
	return "[ " + this.x1 + ", " + this.y1 + ", " + this.x2 + ", " + this.y2 + " ]";
}


/* === HTMLUtils ========================================================== */

function HTMLUtils()
{
}

HTMLUtils.setElementPos = function(elt, pos)
{
	var x = pos.x, y = pos.y;

	if (isNaN(x)) x = 0;
	if (isNaN(y)) y = 0;

	elt.style.left = Math.round(x) + "px";
	elt.style.top = Math.round(y) + "px";
}

HTMLUtils.getElementSize = function(elt)
{
	var w = parseInt(elt.offsetWidth);
	var h = parseInt(elt.offsetHeight);

	if ((isNaN(w) && isNaN(h) || (!w && !h)) && elt.style)
	{
		w = parseInt(elt.style.width);
		h = parseInt(elt.style.height);
	}

	if (isNaN(w)) w = 0;
	if (isNaN(h)) h = 0;

	return new Size(w, h);
}

HTMLUtils.setElementSize = function(elt, size)
{
	var w = size.width;
	var h = size.height;

	if (isNaN(w) || w < 0) w = 0;
	if (isNaN(h) || h < 0) h = 0;

	elt.style.width = Math.round(w) + "px";
	elt.style.height = Math.round(h) + "px";
}

HTMLUtils.getWindowInnerSize = function()
{
	var x = 0, y = 0;

	if (self.innerHeight) // all except Explorer
	{
		x = self.innerWidth;
		y = self.innerHeight;
	}
	else if (document.documentElement && document.documentElement.clientHeight) // Explorer 6 Strict Mode
	{
		x = document.documentElement.clientWidth;
		y = document.documentElement.clientHeight;
	}
	else if (document.body) // other Explorers
	{
		x = document.body.clientWidth;
		y = document.body.clientHeight;
	}

	return new Size(x, y);
}

HTMLUtils.getDocumentSize = function()
{
	var w = 0, h = 0;

	if (document.documentElement)
	{
		w = parseInt(document.documentElement.scrollWidth);
		h = parseInt(document.documentElement.scrollHeight);
	}

	if (isNaN(w)) w = 0;
	if (isNaN(h)) h = 0;

	return new Size(w, h);
}

HTMLUtils.getScrollPos = function()
{
	var scrollX = 0, scrollY = 0;

	if (self.pageYOffset) // all except Explorer
	{
		scrollX = self.pageXOffset;
		scrollY = self.pageYOffset;
	}
	else if (document.documentElement && document.documentElement.scrollTop) // Explorer 6 Strict
	{
		scrollX = document.documentElement.scrollLeft;
		scrollY = document.documentElement.scrollTop;
	}
	else if (document.body) // all other Explorers
	{
		scrollX = document.body.scrollLeft;
		scrollY = document.body.scrollTop;
	}

	return new Point(scrollX, scrollY);
}

HTMLUtils.setElementOpacity = function(elt, op)  // op in [0, 100]
{
	elt.style.opacity = op / 100.0; // Standard
	elt.style["-moz-opacity"] = op / 100.0; // Mozilla
	elt.style.filter = "progid:DXImageTransform.Microsoft.Alpha(opacity=" + Math.round(op); + ")";
}

HTMLUtils.setElementVisible = function(elt, show)
{
	elt.style.visibility = (show ? "visible" : "hidden");
}

HTMLUtils.isElementVisible = function(elt)
{
	return elt.style.visibility != "hidden";
}

HTMLUtils.setElementDisplay = function(elt, show)
{
	elt.style.display = (show ? "block" : "none");
}

HTMLUtils.isElementDisplayed = function(elt)
{
	return HTMLUtils.getCurrentStyle(elt, "display") != "none";
}

HTMLUtils.removeAllChildren = function(root)
{
	root.innerHTML = "";
}

HTMLUtils.setElementText = function(elt, text)
{
	HTMLUtils.removeAllChildren(elt);
	elt.appendChild(document.createTextNode(text));
}

HTMLUtils.getElementText = function(node)
{
	var children = node.childNodes;
	var text = "";

	for (var i = 0 ; i < children.length ; ++i)
	{
		var child = children[i];

		if (child.nodeValue)
			text += child.nodeValue;
	}

	return text;
}

HTMLUtils.getFirstChild = function(node, tagName /* = undefined */, className /* = undefined */)
{
	var children = HTMLUtils.getChildrenElements(node, tagName, className);
	return (children.length != 0 ? children[0] : null);
}

HTMLUtils.getChildrenElements = function(node, tagName /* = undefined */, className /* = undefined */)
{
	var children = node.childNodes;
	if (!children) return new Array();

	var res = new Array();

	if (tagName)
		tagName = tagName.toLowerCase();

	for (var i = 0 ; i < children.length ; ++i)
	{
		var child = children[i];

		if (child.tagName &&
		    (!tagName || child.tagName.toLowerCase() == tagName))
		{
			if (className === undefined || JSUtils.arrayContains(StringUtils.explode(" ", child.className), className))
				res[res.length] = child;
		}
	}

	return res;
}

HTMLUtils.setPNGBackground = function(elt, url)
{
	if (window.ActiveXObject && !window.XMLHttpRequest) // IE<=6
	{
		elt.style.backgroundImage = "none";
		elt.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader"
			+ "(src=\"" + url + "\", sizingMethod=\"crop\")";
	}
	else
	{
		elt.style.backgroundImage = "url(" + url + ")";
	}
}

HTMLUtils.redirect = function(doc, url)
{
	if (doc === null || doc === undefined)
		doc = document;

	doc.location.href = url;
}

HTMLUtils.removeElement = function(elt)
{
	elt.parentNode.removeChild(elt);
}

HTMLUtils.animateFading = function(elt, type /* "show" or "hide" */, callback /* called when animation is finished */)
{
	var duration = 800;
	var steps = 10;

	setTimeout(JSUtils.makeCallback(null, HTMLUtils.animateFadingImpl, type, elt, duration, steps, 0, callback), duration / steps);
}

HTMLUtils.animateFading_setOpacity = function(elt, op)
{
	op = Math.max(0, Math.min(100, op));

	if (op == 0)
		elt.style.visibility = "hidden";
	else
		elt.style.visibility = "visible";

	HTMLUtils.setElementOpacity(elt, op);
}

HTMLUtils.animateFadingImpl = function(type, elt, duration, steps, step, callback)
{
	if (type == "show")
	{
		if (JSUtils.isArray(elt))
		{
			for (var i = 0, n = elt.length ; i < n ; ++i)
				HTMLUtils.animateFading_setOpacity(elt[i], (100 * step) / steps);
		}
		else
		{
			HTMLUtils.animateFading_setOpacity(elt, (100 * step) / steps);
		}
	}
	else if (type == "hide")
	{
		if (JSUtils.isArray(elt))
		{
			for (var i = 0, n = elt.length ; i < n ; ++i)
				HTMLUtils.animateFading_setOpacity(elt[i], 100 - (100 * step) / steps);
		}
		else
		{
			HTMLUtils.animateFading_setOpacity(elt, 100 - (100 * step) / steps);
		}
	}

	if (step < steps)
	{
		setTimeout(JSUtils.makeCallback
			(null, HTMLUtils.animateFadingImpl, type, elt, duration,
			 steps, step + 1, callback), duration / steps);
	}
	else if (callback)
	{
		callback();
	}
}

/* === StringUtils ======================================================== */

function StringUtils()
{
}

StringUtils.formatString = function(str)
{
	for (var i = 1 ; i < arguments.length ; ++i)
		str = StringUtils.replaceString(str, "%" + i, JSUtils.toString(arguments[i]));

	return str;
}

StringUtils.padLeft = function(str, len, paddingChar)
{
	str = "" + str;

	if (str.length >= len)
		return str;

	for (var r = len - str.length ; r > 0 ; r--)
		str = paddingChar + str;

	return str;
}

StringUtils.escapeXML = function(str)
{
	str = StringUtils.replaceString(str, "&", "&amp;");
	str = StringUtils.replaceString(str, "<", "&lt;");
	str = StringUtils.replaceString(str, ">", "&gt;");
	str = StringUtils.replaceString(str, '"', "&quot;");

	return str;
}

StringUtils.startsWith = function(str, startStr)
{
	return str.substring(0, startStr.length) == startStr;
}

StringUtils.replaceStringRegExp = function(str, find, replaceWith)  // find is String/RegExp
{
	if (JSUtils.isString(find))
		find = new RegExp(find, "g");

	str = new String(str);

	return str.replace(find, replaceWith);
}

StringUtils.replaceString = function(str, find, replaceWith)
{
	find = new String(find);
	str = new String(str);

	var pos = -replaceWith.length;

	while ((pos = str.indexOf(find, pos + replaceWith.length)) != -1)
		str = str.substring(0, pos) + replaceWith + str.substring(pos + find.length);

	return str;
}

StringUtils.trim = function(str)
{
	if (str === undefined || str === null)
		return "";

	str = str.replace(/^\s+/g, ""); // leading
	return str.replace(/\s+$/g, ""); // trailing
}

StringUtils.encodeUTF8 = function(rohtext)
{
	// From http://aktuell.de.selfhtml.org/artikel/javascript/utf8b64/utf8.htm
	// dient der Normalisierung des Zeilenumbruchs
	rohtext = rohtext.replace(/\r\n/g,"\n");
	var utftext = "";

	for (var n=0; n<rohtext.length; n++)
	{
		// ermitteln des Unicodes des  aktuellen Zeichens
		var c=rohtext.charCodeAt(n);

		// alle Zeichen von 0-127 => 1byte
		if (c<128)
			utftext += String.fromCharCode(c);
		// alle Zeichen von 127 bis 2047 => 2byte
		else if((c>127) && (c<2048))
		{
			utftext += String.fromCharCode((c>>6)|192);
			utftext += String.fromCharCode((c&63)|128);
		}
		// alle Zeichen von 2048 bis 66536 => 3byte
		else
		{
			utftext += String.fromCharCode((c>>12)|224);
			utftext += String.fromCharCode(((c>>6)&63)|128);
			utftext += String.fromCharCode((c&63)|128);
		}
	}

	return utftext;
}

StringUtils.contains = function(str, what)
{
	return ("" + str).indexOf(what) != -1;
}

StringUtils.explode = function(separators, inputString, includeEmpties)
{
	inputString = new String(inputString);
	separators = new String(separators);

	if (!separators)
		separators = " :;";

	var fixedExplode = new Array(1);
	var currentElement = "";
	var count = 0;

	for (var x = 0 ; x < inputString.length ; ++x)
	{
		var chr = inputString.charAt(x);

		if(separators.indexOf(chr) != -1)
		{
			fixedExplode[count] = currentElement;
			count++;
			currentElement = "";
		}
		else
		{
			currentElement += chr;
		}
	}

	fixedExplode[count] = currentElement;

	if (!includeEmpties)
	{
		var tmp = new Array();

		for (var i = 0 ; i < fixedExplode.length ; ++i)
		{
			if (("" + fixedExplode[i]).length != 0)
				tmp[tmp.length] = fixedExplode[i];
		}

		fixedExplode = tmp;
	}

	return fixedExplode;
}


/* === JSUtils ============================================================ */

function JSUtils()
{
}

JSUtils.isObject = function()
{
	return typeof arguments[0] == 'object';
}

JSUtils.isArray = function()
{
	if (typeof arguments[0] == 'array')
	{
		return true;
	}
	else if (typeof arguments[0] == 'object')
	{
		try
		{
			var criterion = arguments[0].constructor.toString().match(/array/i);
			return (criterion != null);
		}
		catch (e)
		{
			return (arguments[0].length !== undefined);
		}
	}

	return false;
}

JSUtils.isString = function()
{
	return (typeof arguments[0] == 'string');
}

JSUtils.toString = function(x)
{
	return "" + x;
}

// See http://www.codeproject.com/jscript/leakpatterns.asp
JSUtils.makeCallback = function(target, func)  // order: [owner args, caller args]
{
	var args = new Array();

	for (var i = 2 ; i < arguments.length ; ++i) // owner args
		args[args.length] = arguments[i];

	return function()  // <-- caller
	{
		var actualArgs = new Array();

		for (var i = 0, n = args.length ; i < n ; ++i)
			actualArgs[i] = args[i];

		for (var i = 0 ; i < arguments.length ; ++i)
			actualArgs[actualArgs.length] = arguments[i];

		return func.apply(target, actualArgs);
	}
}

JSUtils.invokeLater = function(callback, timeout)
{
	if (!timeout || timeout < 0)
		timeout = 100;

	var func = function() { callback(); }
	setTimeout(func, timeout);
}

JSUtils.currentTimeMillis = function()
{
	return (new Date()).getTime();
}

JSUtils.arrayContains = function(arr, what)
{
	for (var i = 0 ; i < arr.length ; ++i)
		if (arr[i] == what) return true;

	return false;
}


/* Avoid memory leaks in IE */
function NodeRef(elt)
{
	//if (elt.NodeRefed !== true)
	if (elt.id === null || elt.id == window.undefined || elt.id === "")
	{
		elt.id = "NodeRef_" + NodeRef.sCurrentIndex;
		elt.NodeRefed = true;

		NodeRef.sCurrentIndex++;
	}

	this.m_id = elt.id;
}

NodeRef.sCurrentIndex = 0;

NodeRef.create = function(element)
{
	return new NodeRef(element);
}

NodeRef.acquire = function(element)
{
	element.NodeRefed = true;  // keep id
	return new NodeRef(element);
}

NodeRef.acquireOrCreate = function(element)
{
	if (element.id && element.id.length > 0)
		return NodeRef.acquire(element);
	else
		return NodeRef.create(element);
}

NodeRef.prototype.getElement = function()
{
	return document.getElementById(this.m_id);
}


function getHTTPObject()
{
	// From http://www.quirksmode.org/
	var XMLHttpFactories =
	[
		function () { return new XMLHttpRequest() },
		function () { return new ActiveXObject("Msxml2.XMLHTTP") },
		function () { return new ActiveXObject("Msxml3.XMLHTTP") },
		function () { return new ActiveXObject("Microsoft.XMLHTTP") }
	];

	var xmlhttp = null;

	for (var i = 0, n = XMLHttpFactories.length ; i < n ; ++i)
	{
		try { xmlhttp = XMLHttpFactories[i](); }
		catch (e) { continue; }

		break;
	}

	return xmlhttp;
}

function keepAlive(url)
{
	var http = getHTTPObject();
	http.open("GET", url, true);
	http.send(null);
}

function onLoad(keepAliveURL)
{
	// Setup keep alive
	setInterval(JSUtils.makeCallback(null, keepAlive, keepAliveURL), 60000);
}

function initAutoCompleteField(eltId, url, multi)
{
	var dataSource = new YAHOO.util.XHRDataSource(url);
	dataSource.responseType = YAHOO.util.XHRDataSource.TYPE_TEXT;
	dataSource.responseSchema = { recordDelim: "\n", fieldDelim: "\t" };
	dataSource.maxCacheEntries = 0;

	var autoComp = new YAHOO.widget.AutoComplete(eltId, eltId + "-autocomplete-list", dataSource);

	autoComp.maxResultsDisplayed = 15;
	autoComp.minQueryLength = 2;
	autoComp.autoHighlight = true;
	autoComp.allowBrowserAutocomplete = false;
	autoComp.typeAhead = true;
	autoComp.animHoriz = false;
	autoComp.animVert = false;
	autoComp.queryDelay = 0.1;

	if (multi)
		autoComp.delimChar = [ ",", ";" ];
}

