function log() {
	if (window.console)
		window.console.log.apply(window.console, arguments);
	else {
		log.output.push(array(arguments).join(', '));
		clearTimeout(log.time);
		log.time = setTimeout(function(){
			var t = log.output.join('\n');
			log.output = [];
			alert(t);
		}, 100);
	}
	return arguments[0];
}
log.output = [];

function extend(target) {
	for (var a = arguments, i, k = 0; ++k < a.length;)
		for (i in a[k]) target[i] = a[k][i];
	return target;
}

// Object to Array
function array(a) {
	var r = [], i = 0, l = a.length;
	try {
		r = array.a.slice.call(a, 0);
		if (r[0] == undefined) throw "";
	} catch (e) {
		for (; i < l; i++) r[i] = a[i];
	}
	return r;
}
array.a = [];

function copy(object) {
	var fn = new Function;
	fn.prototype = object;
	return new fn;
};

function assignID(object) {
	// assign a unique id
	if (!object.__guid) object.__guid = assignID.$id++;
	return object.__guid;
}
assignID.$id = 1;

// bind object to method
function bind(method, context) {
	var bound = function() {
		return method.apply(context, arguments);
	};
	bound.cloneID = assignID(method);
	return bound;
}

function dom(elem, name) {
	return !elem ? document : name
		? array(dom(elem).getElementsByTagName(name))
		: elem.constructor == String
			? dom().getElementById(elem)
			: elem;
}

extend(dom, {

	// get last of nodes with name
	last: function(elem, name) {
		var elems = this(name ? elem : null).getElementsByTagName(name || elem);
		return elems[elems.length - 1];
	},

	getByClass:
	(dom().getElementsByClassName // FF 3, ... ?
	? function(klass, parent, tag) {
		var res = this(parent).getElementsByClassName(klass);
		return tag && (tag = tag.toUpperCase())
			? Array.filter(res, function(elem) { return elem.nodeName == tag })
			: array(res);
	}
	: dom().evaluate // FF <= 2, Opera, Safari
	? function(klass, parent, tag) {
		var xpath = this().evaluate("descendant::"+ (tag || '*') +"[contains(concat(' ',@class,' '),'"+ klass +"')]", this(parent), null, 7, null);
		for (var i = 0, res = []; (e = xpath.snapshotItem(i)); i++) res[res.length] = e;
		return res;
	}
	: function(klass, parent, tag) { // IE
		var regxp = new RegExp('(^|\\s)' + klass + '(\\s|$)'),
			elems = this(this(parent), tag || '*');
		for (var i = 0, res = [], e; (e = elems[i]); i++)
			if (regxp.test(e.className)) res[res.length] = e;
		e = elems = null;
		return res;
	}),

	hasClass: function(elem, klass) {
		var r = new RegExp("(^|\\s)" + klass + "(\\s|$)");
		return r.test(elem.className);
	},

	addClass: function(elem, klass) {
		if (!this.hasClass(elem, klass))
			elem.className += (elem.className ? " " : "") + klass;
		return elem;
	},

	removeClass: function(elem, klass) {
		var r = new RegExp("(^|\\s)" + klass + "(\\s|$)");
		elem.className = elem.className.replace(r, "$2");
		return elem;
	}
});


var events = {
	add: function(elem, type, handl) {
		if (!elem.events) elem.events = {};

		var handlers = elem.events[type];

		if (!handlers) {
			handlers = elem.events[type] = {};
			if (elem["on"+ type]) {
				var on = elem["on"+ type];
				elem["on"+ type] = null;
				this.add(elem, type, on);
			}
		}

		handlers[assignID(handl)] = handl;
		elem["on"+ type] = this.handle;
	},

	remove: function(elem, type, handl) {
		if (elem.events && elem.events[type])
			delete elem.events[type][handl.__guid];
	},

	handle: function(e) {
		e = e || window.event;

		if (!e.target && e.srcElement)
			e.target = e.srcElement;

		if (!e.relatedTarget && e.toElement)
			e.relatedTarget = e.toElement == e.target
				? e.fromElement : e.toElement;

		if (!e.preventDefault)
			e.preventDefault = function() {
				this.returnValue = false;
			};

		if (!e.stopPropagation)
			e.stopPropagation = function() {
				this.cancelBubble = true;
			};

		if (e.pageX == null && e.clientX != null) {
			var d = document.documentElement, b = document.body;
			e.pageX = e.clientX + (d && d.scrollLeft || b.scrollLeft || 0);
			e.pageY = e.clientY + (d && d.scrollTop || b.scrollTop || 0);
		}

		var handlers = this.events[e.type];
		var result = true;
		for (var i in handlers) {
			if (bind(handlers[i], this)(e) === false) {
				e.preventDefault();
				e.stopPropagation();
				result = false;
			}
		}

		if (e.srcElement)
			e = e.target = e.relatedTarget = e.preventDefault = e.stopPropagation = null;
		return result;
	},

	dispatch: function(e) {
		var elem = e.target, type = e.type;
		while (elem) {
			if (elem['on'+ type])
				elem['on'+ type](e);
			elem = !e.cancelBubble && dom.parent(elem);
		}
		return !(e.returnValue === false);
	}

};

new function() {
	var re = /([\/()[\]{}|*+-.,^$?\\])/g;
	var FORMAT = /%([1-9])/g;
	var dateFormat = {
		dmy: /(\d{1,2}) (\d{1,2}) (\d{4})|(\d{2})(\d{2})(\d{4})/,
		ymd: /(\d{4}) (\d{1,2}) (\d{1,2})|(\d{4})(\d{2})(\d{2})/
	}
	dateFormat.mdy = dateFormat.dmy;

	// Additional string methods
	extend(String.prototype, {

		toDate: function(format) {
			format = format || 'ymd';
			var s = this.replace(/\D+/g, ' '),
				a = format.split(''),
				m = s.match(dateFormat[format]),
				d = {}, i = 0;

			for (; m && s && i < a.length; i++)
				d[a[i]] = m[i + 1] || m[i + 4];

			d = Date.parse(d.y +'/'+ d.m +'/'+ d.d);

			return isNaN(d) ? null : new Date(d);
		}
	});

	extend(Number.prototype, {

		pad: function() {
			return (this <= 9) ? '0' + this : this;
		}

	});

};