/**
 * navigator.userAgent
 * @type String
 */
var agt = navigator.userAgent.toLowerCase();
/**
 * @type Boolean
 */
window.is_opera  = (agt.indexOf("opera") != -1);
/**
 * @type Boolean
 */
window.is_safari = (agt.indexOf('safari') != -1);
/**
 * @type Boolean
 */
window.is_ie  = !window.is_opera && !window.is_safari && window.showModalDialog != null && 0 <= agt.indexOf("msie");
/**
 * @type Boolean
 */
window.is_ie7 = window.is_ie && agt.indexOf("msie 7") != -1;
/**
 * @type Boolean
 */
window.is_ie6 = window.is_ie && !window.is_ie7 && agt.indexOf("msie 6") != -1;
/**
 * @type Boolean
 */
window.is_ie_lt7 = window.is_ie && parseInt(agt.substr(agt.indexOf("msie") + 5, 1)) < 7;
/**
 * @type Boolean
 */
window.is_moz = (
	(agt.indexOf('mozilla/5')  != -1) && (agt.indexOf('spoofer') == -1) &&
	(agt.indexOf('compatible') == -1) && (agt.indexOf('opera')   == -1) &&
	(agt.indexOf('webtv')      == -1) && (agt.indexOf('hotjava') == -1));

window.dom = new Object;
window.dom.html = function ()
{
	return document.getElementsByTagName("html")[0];
}

window.dom.getLeft = function (elem)
{
	var node = elem;
	var left = 0;

	while (node && node.nodeName)
	{
		var bl = parseInt(dom.currentStyleProp(node, "borderLeftWidth")) || 0;
		var br = parseInt(dom.currentStyleProp(node, "borderRightWidth")) || 0;

		left += node.offsetLeft - node.scrollLeft;
		left += bl;
		if (node != elem)
			left += br;

		node = node.offsetParent;
	}
	return left;
}

window.dom.getTop = function (elem)
{
	var node = elem;
	var top = 0;

	while (node && node.nodeName)
	{
		var bt = parseInt(dom.currentStyleProp(node, "borderTopWidth")) || 0;
		var bt = parseInt(dom.currentStyleProp(node, "borderBottomWidth")) || 0;

		top += node.offsetTop - node.scrollTop;
		top += bt
		if (node != elem)
			top + bt;

		node = node.offsetParent;
	}
	return top;
}


window.dom.getParentByTagName = function (refObject, tagName)
{
	if (refObject == null || tagName == null)
		return null;

	tagName = String(tagName).toUpperCase();
	var objParent = refObject;
	while(objParent = objParent.parentNode)
	{
		if (objParent.tagName == tagName)
			return objParent;
	}
	return null;
}

window.dom.currentStyle = function (elementObj)
{
	if (elementObj == null)
		return null;

	if (window.is_ie)
		return elementObj.currentStyle;
	else
		return getComputedStyle(elementObj, null);
}

window.dom.currentStyleProp = function (elementObj, propName)
{
	if (elementObj == null)
		return null;
	var style = dom.currentStyle(elementObj);
	return style == null ? null : style[propName];
}

window.dom.elementContains = function (parent, child)
{
	if (parent == null || child == null)
		return false;
	else
	{
		if (window.is_ie)
			return parent.contains(child);
		else
			return (parent == child ||
				dom.elementContains(parent, child.parentNode));
	}
}

window.log = new Object;
window.log.group = function (groupName)
{
	if (isFunction(console.group))
		console.group(groupName);
}
window.log.groupEnd = function ()
{
	if (isFunction(console.groupEnd))
		console.groupEnd();
}
window.log.message = function (messageText)
{
	if (isFunction(console.log))
		console.log(messageText);
}
window.log.info = function (messageText)
{
	if (isFunction(console.info))
		console.info.apply(messageText);
}


var evt = new Object;
evt.srcElement = function (evt)
{
	var event = util.getEvent(evt);
	if (event == null)
		return null;

	if (window.is_ie)
		return event.srcElement;
	else
	{
		if (event.target.nodeType == 1)
			return event.target;
		else
			return event.target.parentNode;
	}
}


var util = {};
util.getEvent = function (e)
{
	if (e != null)
		return e;
	else
		return window.event;
}
util.querystring = function (url)
{
	this.address = null;
	this.pagepath = null;
	this.pagehash = null;
	this.param = {};

	this.getItem = util.querystring.getItem;
	this.parse = util.querystring.parse;
	this.toString = util.querystring.toString;

	if (url != null)
		this.parse(url);
	else
		this.parse(location.href);
}
util.querystring.getItem = function (itemKey)
{
	return this.param[itemKey];
}
util.querystring.parse = function (url)
{
	if (url == null)
		return;

	this.url = new String(url);
	if (this.url.match(/^(((https?|ftp):\/\/([a-z-A-Z0-9.]+))(\/?[^\?]*)?)?(?:\?([^#]+))?(?:#(.*))?$/))
	{
		var m1 = RegExp.$1;
		var m2 = RegExp.$2;
		var m3 = RegExp.$3;
		var m4 = RegExp.$4;
		var m5 = RegExp.$5;
		var m6 = RegExp.$6;
		var m7 = RegExp.$7;

		this.scriptnamefull = m1;
		this.servernamefull = m2;
		this.protocol = m3;
		this.servername = m4;
		this.scriptname = m5;
		this.query = m6;
		this.hash = m7;
	}
	else
	{
		this.scriptnamefull = "";
		this.servernamefull = "";
		this.protocol = "";
		this.servername = "";
		this.scriptname = "";
		this.query = this.url;
		this.hash = "";
	}

	var param = this.query.split("&");
	for (var i = 0; i < param.length; i++)
	{
		if (param[i].length == 0)
			continue;

		var pair = param[i].split("=");
		var itemKey = pair[0];
		var itemValue = pair[1];

		if (this.param[itemKey] != null)
		{
			if (!isArray(this.param[itemKey]))
				this.param[itemKey] = [this.param[itemKey]];

			this.param[itemKey].push(itemValue);
		}
		else
			this.param[itemKey] = itemValue;
	}
}
util.querystring.toString = function (full)
{
	var param = new Array();
	for (var name in this.param)
	{
		if (this.param[name] && this.param[name].constructor == Array)
		{
			for (var i = 0; i < this.param[name].length; i++)
				param.push(escape(name) + "=" + escape(this.param[name][i]));
		}
		else
		{
			var itemValue = String(this.param[name]).match(/\{.*\}/) ? this.param[name] : escape(this.param[name]);
			param.push(escape(name) + "=" + itemValue);
		}
	}

	var query = param.join("&");
	if (full == true)
		var result = (param.length ? this.scriptnamefull + "?" + query : this.scriptnamefull) + this.hash;
	else
		var result = query;

	return result;
}


Number.random = function (randTop)
{
	var top = Math.abs(randTop);
	var random = Math.round((top) * Math.random());
	return random;
}

Date.today = function ()
{
	var now = new Date();
	var today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
	return today;
}

Date.parse = function (dateValue)
{
	if (dateValue == null)
		return null;

	else if (dateValue instanceof Date)
		return dateValue.getTime();

	else if (dateValue instanceof Array)
	{
		if (dateValue.length == 3)
		{
			var currentDate = new Date(dateValue[2], dateValue[1]-1, dateValue[0]);
			if (isNaN(currentDate))
				return null;
			else
				return currentDate.getTime();
		}
		else
			return null;
	}
	else
	{
		var dateString = new String(dateValue);
		if (dateString.length == 0)
			return null;

		var date = null;

		// xsl-short: YYYY-MM-DD or general date: YYYY/MM/DD
		if ((arrElem = dateString.match(/^(\d{4})(?:-|\/)(\d{1,2})(?:-|\/)(\d{1,2})$/)))
			date = new Date(arrElem[1], arrElem[2]-1, arrElem[3]);
		// xsl-long:  YYYY-MM-DDTHH:NN:SS
		else if ((arrElem = dateString.match(/^(\d{4})-(\d{1,2})-(\d{1,2})T(\d{1,2}):(\d{1,2}):(\d{1,2})/)))
			date = new Date(arrElem[1], arrElem[2]-1, arrElem[3], arrElem[4], arrElem[5], arrElem[6]);
		// js-string: www MMM dd hh:mm:ss ZON+dddd yyyy
		else if (dateString.match(/^\w{3}\s\w{3}\s\d{1,2}\s\d{2}:\d{2}:\d{2}\s\w{3}\+\d{4}\s\d{4}$/))
			date = new Date(dateString);
		// js-milliseconds date
		else if (dateString.match(/^-?\d+$/))
			date = new Date(parseInt(dateString));
		// lets generalize a bit: [dd-mm-yyyy] or [dd/mm/yyyy] or [dd.mm.yyyy]
		else if ((arrElem = dateString.match(/^(\d{1,2})[\.\/\-](\d{1,2})[\.\/\-](\d{4})$/)))
			date = new Date(arrElem[3], arrElem[2]-1, arrElem[1]);
		// more generalization:  [yyyy-mm-dd] or [yyyy.mm.dd] or [yyyy/mm/dd]
		else if ((arrElem = dateString.match(/^(\d{4})[\.\/\-](\d{1,2})[\.\/\-](\d{1,2})$/)))
			date = new Date(arrElem[1], arrElem[2]-1, arrElem[3]);
		// and some more: [dd-mm-yyyy hh:nn:ss] or [dd/mm/yyyy hh:nn:ss] or [dd.mm.yyyy hh:nn:ss]
		else if ((arrElem = dateString.match(/^(\d{1,2})[\.\/\-](\d{1,2})[\.\/\-](\d{4}) (\d{1,2}):(\d{1,2})(?::(\d{1,2}))?/)))
			date = new Date(arrElem[3], arrElem[2]-1, arrElem[1], arrElem[4], arrElem[5], arrElem[6] || 0);
		// and more:  [yyyy-mm-dd hh:nn:ss] or [yyyy.mm.dd hh:nn:ss] or [yyyy/mm/dd hh:nn:ss]
		else if ((arrElem = dateString.match(/^(\d{4})[\.\/\-](\d{1,2})[\.\/\-](\d{1,2}) (\d{1,2}):(\d{1,2})(?::(\d{1,2}))?$/)))
			date = new Date(arrElem[1], arrElem[2]-1, arrElem[3], arrElem[4], arrElem[5], arrElem[6] || 0);

		return date;
	}
}

Date.offset = function (dateInstance, timeFormat)
{
	var timeMultiplier =  { s: 1000, n: 1000*60, h: 1000*60*60, d: 1000*60*60*24, m: 1000*60*60*24*30, y: 1000*60*60*24*365 }
	var timeOffset = 0;

	if (timeFormat != undefined)
		if (String(timeFormat).match( /^([+-]?(?:\d+|\d*\.\d*))([mhdMy]?)$/ ))
			timeOffset = parseInt(timeMultiplier[RegExp.$2] * RegExp.$1);

	dateInstance.setTime(dateInstance.getTime() + timeOffset);
	return dateInstance;
}

Date.dayNames = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
Date.monthNames = ["January","February","March","April","May","June","July","August","September","October","November","December"];
Date.AM = "AM";
Date.PM = "PM";

Date.prototype.getWeekDay = function ()
{
	return Date.dayNames[this.getDay()];
}

Date.prototype.toString = function (format)
{
	if (isNaN(this))
		return "NaN";

	var f = {};
	f.s     = this.getSeconds();
	f.ss    = this.getSeconds().toString().padLeft(2, 0);
	f.h     = this.getHours();
	f.hh    = this.getHours().toString().padLeft(2, 0);
	f.n     = this.getMinutes();
	f.nn    = this.getMinutes().toString().padLeft(2, 0);
	f.d     = this.getDate();
	f.dd    = this.getDate().toString().padLeft(2, 0);
	f.www   = Date.dayNames[this.getDay()].substr(0, 3);
	f.wwww  = Date.dayNames[this.getDay()];
	f.m     = (this.getMonth() + 1);
	f.mm    = (this.getMonth() + 1).toString().padLeft(2, 0);
	f.mmm   = Date.monthNames[this.getMonth()].substr(0, 3);
	f.mmmm  = Date.monthNames[this.getMonth()];
	f.yyyy  = this.getFullYear();
	f.yy    = this.getFullYear().toString().substr(2, 4);
	f.y     = f.yy;

	f.date       = f.dd + "." + f.mm + "." + f.yyyy;
	f.time       = f.hh + ":" + f.nn + ":" + f.ss;
	f.dbdate     = f.yyyy + "/" + f.mm + "/" + f.dd;
	f.dbtime     = f.hh + ":" + f.nn + ":" + f.ss;
	f.dbdatetime = f.dbdate + " " + f.dbtime;
	f.xsl_short  = f.yyyy + "-" + f.mm + "-" + f.dd;
	f.xsl_long   = f.yyyy + "-" + f.mm + "-" + f.dd + "T" + f.hh + ":" + f.nn + ":" + f.ss;

	var offset = this.getTimezoneOffset();
	var offset_hours = Math.floor(Math.abs(offset) / 60);
	var offset_minutes = Math.abs(offset) - (offset_hours * 60);
	oh = offset_hours.toString().padLeft(2, 0);
	om = offset_minutes.toString().padLeft(2, 0);
	f.classic = [f.www, f.mmm, f.d, f.time, "UTC" + (offset > 0 ? "-" : "+") + oh + om, f.yyyy].join(" ");

	if (format)
	{
		var a = format.split(/\W/);
		for (var i in a)
		{
			if (f[a[i]])
			{
				format = format.replace(a[i], f[a[i]]);
			}
		}
	}
	else format = f.classic;
	return format;
}

Date.prototype.offset = function (timeFormat)
{
	return Date.offset(this, timeFormat);
}

Date.prototype.getUTCTime = function ()
{
	return this.getTime() + (this.getTimezoneOffset() * 60000)
}

Array.prototype.contains = function(value)
{
	for (var i = 0; i < this.length; i++)
	{
		if (this[i] == value)
			return true;
	}

	return false;
}

/**
 * Returns a new array with the supplied values removed from it
 * @param String value The values to remove (can be any number or arguments)
 * @return Array A new array with the value removed from it
 */
Array.prototype.remove = function()
{
	var list = new Array;
	var remove = false;
	for (var i = this.length - 1; i >= 0; i--)
	{
		remove = false;
		for (var j = 0; j < arguments.length; j++)
		{
			if (this[i] == arguments[j])
			{
				remove = true;
				break;
			}
		}
		if (remove == true)
			list.push(i);
	}
	for (var i = 0; i < list.length; i++)
	{
		this.splice(list[i], 1);
	}

	return this;
}

String.prototype.trim = function(expression)
{
	if (expression == null) expression = "\\s";
	var re = new RegExp("^[" + expression + "]*([\\s\\S]*?)[" + expression + "]*$");
	return this.replace(re, "$1");
}

String.prototype.format = function ()
{
	var arrArguments = (arguments.length == 1 && arguments[0] && isArray(arguments[0])) ? arguments[0] : arguments;

		function applyPadding(string, count, character, direction) /**/
		{
			var string = '' + string;
			var diff = count - string.length;
			var output = new String();
			while (output.length < diff)
				output += character;

			return (direction == 2 ? string + output : output + string);
		}
		function applyFormat() /**/
		{
			try
			{
				var strValue = new String(arrArguments[arguments[1]]);
			}
			catch(e) { strValue = ""; }

			if (arguments[2] != "" && arguments[3] != "")
			{
				var direction = arguments[4] == "+" ? 2 : 1;
				strValue = applyPadding(strValue, arguments[3], arguments[2] == "#" ? "0" : " ", direction);
			}
			return strValue;
		}
		return (this.replace(/\{(\d+)([$#])?(\d+)?([+-])?\}/g, applyFormat));
}
String.prototype.padLeft = function (length, padChar)
{
	if (padChar == undefined)
		padChar = " ";

	var string = this.toString();
	while(string.length < length)
		string = padChar + string;
	return string;
};
String.prototype.padRight = function (length, padChar)
{
	if (padChar == undefined)
		padChar = " ";

	var string = this.toString();
	while(string.length < length)
		string = string + padChar;
	return string;
};
Function.prototype.callStack = function (point, maxIterCount)
{
	var fxCaller = point || arguments.callee.caller;
	var callStack = new Array();
	var functions = new Array();

	var currentCount = 0;
	while (fxCaller != null && fxCaller.getName)
	{
		if (maxIterCount && currentCount >= maxIterCount)
			break;

		// unfortunatelly this script goes into an infinite loop
		// if there is a recursion going on in the function call stack :(
		else if (functions.contains(fxCaller)) /**/
			break;

		var fxName = fxCaller.getName();
		var fxArgs = new Array();
		var argValues = fxCaller.arguments;

		for (var i = 0; i < argValues.length; i++)
		{
			if (isAlien(argValues[i]))
				fxArgs.push("[foreign-object]");

			else if (isFunction(argValues[i])) /**/
				fxArgs.push("function: " + argValues[i].getName(true));

			else if (isArray(argValues[i]))
				fxArgs.push("array[" + argValues[i].length + "]");

			else if (isObject(argValues[i]))
				fxArgs.push("object{..}");

			else if (isNull(argValues[i]))
				fxArgs.push("null");

			else if (isString(argValues[i]))
				fxArgs.push((argValues[i].length < 200 ? "\"" + argValues[i] + "\"" : "[string(" + argValues[i].length + " chars)]"));

			else if (isUndefined(argValues[i]))
				fxArgs.push("undefined");

			else
				fxArgs.push(argValues[i]);
		}

		functions.push(fxCaller); /**/
		callStack.unshift(fxCaller.getName() + "(" + fxArgs.join(", ") + ")");
		fxCaller = fxCaller.caller;
		currentCount++;
	}
	callStack.toString = function (joinString) /**/
	{
		return this.join(joinString || "\n");
	}
	return callStack;
}

function isArray(object)
{
	return isObject(object) && object.constructor == Array;
}
function isBoolean(object)
{
	return typeof object == 'boolean';
}
function isFunction(object)
{
	return typeof object == 'function';
}
function isAlien(object)
{
	return isObject(object) && typeof object.constructor != 'function';
}
function isNull(object)
{
	return typeof object == 'object' && !object;
}
function isNumber(object)
{
	return typeof object == 'number' && isFinite(object);
}
function isObject(object)
{
	return (object &&
		typeof object == 'object' &&
		object.constructor != String &&
		object.constructor != Number) || isFunction(object); /**/
}
function isString(object)
{
	return (typeof object == 'string') || (object != null && object.constructor == String);
}
function isUndefined(object)
{
	return typeof object == 'undefined';
}

/**
 * Dispatches custom events to event listeners.
 * All arguments to the constructor will be interpreted as event names
 * and these events will be created.
 * @constructor
 */
function Dispatcher()
{
	/**
	 * Object's defined event types
	 */
	this.events = new Array;
	/**
	 * Object listeners, by event type
	 */
	this.listeners = new Object;

	if (arguments.length != 0)
		for (var i = 0; i < arguments.length; i++)
			this.addEvent(arguments[i]);
}
/**
 * Adds an event type.
 * @param {String} eventType The type of the event.
 */
Dispatcher.prototype.addEvent = function (eventType)
{
	if (!this.hasEvent(eventType))
	{
		this.events.push(eventType);
		this.listeners[eventType] = new Array;
	}
}
/**
 * Returns true if the supplied eventType already exists.
 * @param {String} The type of the event.
 * @return {Boolean} True us the specified event type exists.
 */
Dispatcher.prototype.hasEvent = function (eventType)
{
	for (var i = 0; i < this.events.length; i++)
		if (this.events[i] == eventType)
			return true;

	return false;
}
/**
 * Adds an event listener for the specified event type.
 * The listener can be either an object whose method should be called
 * or just a function not related to an object. To use the second method,
 * pass a null value as the object argument.
 * @param {String} eventType The type of the event.
 * @param {String} object The listener object.
 * @param {String} method The listener method.
 */
Dispatcher.prototype.addListener = function (eventType, object, method)
{
	if (this.hasEvent(eventType) && !this.hasListener(eventType, object, method))
	{
		var listener = { object: object, method: method };
		this.listeners[eventType].push(listener);
	}
}
/**
 * Removes the specified event listener.
 * @param String eventType The event for which the listener should be removed.
 * @param Object The listener object
 * @param String The name of the object's method that handles the event.
 */
Dispatcher.prototype.removeListener = function (eventType, object, method)
{
	if (this.hasEvent(eventType))
	{
		for (var i = 0; i < this.listeners[eventType].length; i++)
		{
			if (method)
			{
				if (this.listeners[eventType][i].object == object && this.listeners[eventType][i].method == method)
				{
					this.listeners[eventType].splice(i, 1);
					return;
				}
			}
			else
			{
				if (this.listeners[eventType][i].object == object)
				{
					this.listeners[eventType].splice(i, 1);
					return;
				}
			}
		}
	}
}
/**
 * Returns true if the supplied eventType event listener exists.
 * @param {String} eventType The type of the event.
 * @param {String} object The listener object.
 * @param {String} method The listener method.
 * @return {Boolean} True us the specified listener exists.
 */
Dispatcher.prototype.hasListener = function (event, object, method)
{
	if (this.hasEvent(event))
	{
		for (var i = 0; i < this.listeners[event].length; i++)
		{
			if (method)
			{
				if (this.listeners[event][i].object == object && this.listeners[event][i].method == method)
					return true;
			}
			else
			{
				if (this.listeners[event][i].object == object)
					return true;
			}
		}
	}
	return false;
}
/**
 * Fires the event of the supplied event type.
 * @param {String} eventType The type of the event.
 */
Dispatcher.prototype.fireEvent = function (eventType, eventData)
{
	if (this.listeners[eventType] != null)
	{
		var eventObj = new Event(this, eventType, eventData);
		for (var i = 0; i < this.listeners[eventType].length; i++)
		{
			var listener = this.listeners[eventType][i];

			if (listener.object && listener.method)
				eventObj = listener.object[listener.method](eventObj);
			else if (listener.object)
				eventObj = listener.object(eventObj);
			else if (listener.method)
				eventObj = listener.method(eventObj);
		}
		return eventObj;
	}
	return null;
}

/**
 * Represents the event that was fired.
 * @param {Object} eventSource The element that is the source of the event.
 * @param {String} eventType The type/name of the event.
 * @param {Object} eventData Additional data to pass within the event's .data property.
 */
function Event(eventSource, eventType, eventData)
{
	this.element = eventSource;
	this.source = eventSource;
	this.type = eventType;
	this.name = eventType;
	this.data = new Object;
	for (var prop in eventData)
		this.data[prop] = eventData[prop];
}

function Query2Array(q){
	/* parse the query */
	/* semicolons are nonstandard but we accept them */
	var x = q.replace(/;/g, '&').split('&'), i, name, t;
	/* q changes from string version of query to object */
	for (q={}, i=0; i<x.length; i++){
		t = x[i].split('=', 2);
		name = unescape(t[0]);
		if (!q[name]){
			q[name] = '';
		}
		if (t.length > 1){
			q[name] = unescape(t[1]);
		}
		/* next two lines are nonstandard */
		else{
			q[name] = true;
		}
	}
	return q;
}
