var Class = function(properties){
	var klass = function(){
		if (this.initialize && arguments[0] != 'noinit') return this.initialize.apply(this, arguments);
		else return this;
	};
	for (var property in this) klass[property] = this[property];
	klass.prototype = properties;
	return klass;
};

Class.empty = function(){};

Class.prototype = {
	extend: function(properties){
		var pr0t0typ3 = new this('noinit');

		var parentize = function(previous, current){
			if (!previous.apply || !current.apply) return false;
			return function(){
				this.parent = previous;
				return current.apply(this, arguments);
			};
		};

		for (var property in properties){
			var previous = pr0t0typ3[property];
			var current = properties[property];
			if (previous && previous != current) current = parentize(previous, current) || current;
			pr0t0typ3[property] = current;
		}
		return new Class(pr0t0typ3);
	},

	implement: function(properties){
		for (var property in properties) this.prototype[property] = properties[property];
	}
};

Object.extend = function(){
	var args = arguments;
	args = (args[1]) ? [args[0], args[1]] : [this, args[0]];
	for (var property in args[1]) args[0][property] = args[1][property];
	return args[0];
};

Object.Native = function(){
	for (var i = 0; i < arguments.length; i++) arguments[i].extend = Class.prototype.implement;
};

new Object.Native(Function, Array, String, Number, Class);

if (typeof HTMLElement == 'undefined'){
	var HTMLElement = Class.empty;
	HTMLElement.prototype = {};
}

function $type(obj){
	if (obj === null || obj === undefined) return false;
	var type = typeof obj;
	if (type == 'object'){
		if (obj instanceof HTMLElement) return 'element';
		if (obj instanceof Array) return 'array';
		if (obj.nodeName){
			switch (obj.nodeType){
				case 1: return 'element';
				case 3: return obj.nodeValue.test('\\S') ? 'textnode' : 'whitespace';
			}
		}
	}
	return type;
};

function $chk(obj){
	return !!(obj || obj === 0);
};

function $pick(obj, picked){
	return ($type(obj)) ? obj : picked;
};

function $random(min, max){
	return Math.floor(Math.random() * (max - min + 1) + min);
};

function $clear(timer){
	clearTimeout(timer);
	clearInterval(timer);
	return null;
};

if (window.ActiveXObject) window.ie = window[window.XMLHttpRequest ? 'ie7' : 'ie6'] = true;
else if (document.childNodes && !document.all && !navigator.taintEnabled) window.khtml = true;
else if (document.getBoxObjectFor != null) window.gecko = true;

Array.prototype.forEach = Array.prototype.forEach || function(fn, bind){
	for (var i = 0; i < this.length; i++) fn.call(bind, this[i], i, this);
};

Array.prototype.map = Array.prototype.map || function(fn, bind){
	var results = [];
	for (var i = 0; i < this.length; i++) results[i] = fn.call(bind, this[i], i, this);
	return results;
};

Array.prototype.every = Array.prototype.every || function(fn, bind){
	for (var i = 0; i < this.length; i++){
		if (!fn.call(bind, this[i], i, this)) return false;
	}
	return true;
};

Array.prototype.some = Array.prototype.some || function(fn, bind){
	for (var i = 0; i < this.length; i++){
		if (fn.call(bind, this[i], i, this)) return true;
	}
	return false;
};

Array.prototype.indexOf = Array.prototype.indexOf || function(item, from){
	from = from || 0;
	if (from < 0) from = Math.max(0, this.length + from);
	while (from < this.length){
		if(this[from] === item) return from;
		from++;
	}
	return -1;
};

Array.extend({
	each: Array.prototype.forEach,

	copy: function(){
		var newArray = [];
		for (var i = 0; i < this.length; i++) newArray[i] = this[i];
		return newArray;
	},

	remove: function(item){
		var i = 0;
		while (i < this.length){
			if (this[i] == item) this.splice(i, 1);
			else i++;
		}
		return this;
	},

	test: function(item, from){
		return this.indexOf(item, from) != -1;
	},

	extend: function(newArray){
		for (var i = 0; i < newArray.length; i++) this.push(newArray[i]);
		return this;
	},

	associate: function(keys){
		var obj = {}, length = Math.min(this.length, keys.length);
		for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
		return obj;
	}
});

function $A(array){
	return Array.prototype.copy.call(array);
};

function $each(iterable, fn, bind){
	return Array.prototype.forEach.call(iterable, fn, bind);
};

String.extend({
	test: function(regex, params){
		return new RegExp(regex, params).test(this);
	},

	toInt: function(){
		return parseInt(this);
	},

	toFloat: function(){
		return parseFloat(this);
	},

	camelCase: function(){
		return this.replace(/-\D/g, function(match){
			return match.charAt(1).toUpperCase();
		});
	},

	hyphenate: function(){
		return this.replace(/\w[A-Z]/g, function(match){
			return (match.charAt(0)+'-'+match.charAt(1).toLowerCase());
		});
	},

	capitalize: function(){
		return this.toLowerCase().replace(/\b[a-z]/g, function(match){
			return match.toUpperCase();
		});
	},

	trim: function(){
		return this.replace(/^\s+|\s+$/g, '');
	},

	clean: function(){
		return this.replace(/\s{2,}/g, ' ').trim();
	},

	rgbToHex: function(array){
		var rgb = this.match(/\d{1,3}/g);
		if (!rgb || rgb.length < 3) return false;
		if (rgb[3] && rgb[3] == 0) return 'transparent';
		var hex = [];
		for (var i = 0; i < 3; i++){
			var bit = (rgb[i]-0).toString(16);
			hex.push(bit.length == 1 ? '0'+bit : bit);
		}
		return array ? hex : '#'+hex.join('');
	},

	hexToRgb: function(array){
		var hex = this.match('^#?(\\w{1,2})(\\w{1,2})(\\w{1,2})$');
		if (!hex) return false;
		var rgb = [];
		for (var i = 1; i < 4; i++){
			if (hex[i].length == 1) hex[i] += hex[i];
			rgb.push(parseInt(hex[i], 16));
		}
		return array ? rgb : 'rgb('+rgb.join(',')+')';
	}
});

Number.extend({
	toInt: function(){
		return parseInt(this);
	},

	toFloat: function(){
		return parseFloat(this);
	}
});

Function.extend({
	create: function(options){
		var fn = this;
		options = Object.extend({
			'bind': fn,
			'event': false,
			'arguments': null,
			'delay': false,
			'periodical': false,
			'attempt': false
		}, options || {});
		if (options.arguments != null && typeof options.arguments != 'undefined' && !(options.arguments instanceof Array))
			options.arguments = [options.arguments];
		return function(event){
			var args = options.arguments || arguments;
			if (options.event){
				event = (options.event === true) ? event || window.event : new options.event(event);
				args = [event].concat(args);
			}
			var returns = function(){
				return fn.apply(options.bind, args);
			};
			if (options.delay) return setTimeout(returns, options.delay);
			if (options.periodical) return setInterval(returns, options.periodical);
			if (options.attempt){
				try {
					var result = returns();
				} catch(err){
					result = err;
				} finally {
					return result;
				}
			} else return returns();
		};
	},

	pass: function(args, bind){
		return this.create({'arguments': args, 'bind': bind});
	},

	attempt: function(args, bind){
		return this.create({'arguments': args, 'bind': bind, 'attempt': true})();
	},

	bind: function(bind, args){
		return this.create({'bind': bind, 'arguments': args});
	},

	bindAsEventListener: function(bind, args){
		return this.create({'bind': bind, 'event': true, 'arguments': args});
	},

	delay: function(ms, bind, args){
		return this.create({'delay': ms, 'bind': bind, 'arguments': args})();
	},

	periodical: function(ms, bind, args){
		return this.create({'periodical': ms, 'bind': bind, 'arguments': args})();
	}
});

var Element = new Class({
	initialize: function(el){
		if ($type(el) == 'string') el = document.createElement(el);
		return $(el);
	}
});

function $(el){
	if (!el) return false;
	if (el._element_extended_ || [window, document].test(el)) return el;
	if ($type(el) == 'string') el = document.getElementById(el);
	if ($type(el) != 'element') return false;
	if (!['object', 'embed'].test(el.tagName.toLowerCase()) && !el.extend){
		el._element_extended_ = true;
		Garbage.collect(el);
		el.extend = Object.extend;
		if (!(el instanceof HTMLElement)) el.extend(Element.prototype);
	}
	return el;
};

var Elements = new Class({});

new Object.Native(Elements);

document.getElementsBySelector = document.getElementsByTagName;

function $$(){
	if (!arguments) return false;
	if (arguments.length == 1){
		if (!arguments[0]) return false;
		if (arguments[0]._elements_extended_) return arguments[0];
	}
	var elements = [];
	$each(arguments, function(selector){
		switch ($type(selector)){
			case 'element': elements.push($(selector)); break;
			case 'string': selector = document.getElementsBySelector(selector);
			default:
			if (selector.length){
				$each(selector, function(el){
					if ($(el)) elements.push(el);
				});
			}
		}
	});
	elements._elements_extended_ = true;
	return Object.extend(elements, new Elements);
};

Elements.Multi = function(property){
	return function(){
		var args = arguments;
		var items = [];
		var elements = true;
		$each(this, function(el){
			var returns = el[property].apply(el, args);
			if ($type(returns) != 'element') elements = false;
			items.push(returns);
		});
		if (elements) items = $$(items);
		return items;
	};
};

Element.extend = function(properties){
	for (var property in properties){
		HTMLElement.prototype[property] = properties[property];
		Element.prototype[property] = properties[property];
		Elements.prototype[property] = Elements.Multi(property);
	}
};

Element.extend({
	inject: function(el, where){
		el = $(el) || new Element(el);
		switch (where){
			case "before": $(el.parentNode).insertBefore(this, el); break;
			case "after":
				if (!el.getNext()) $(el.parentNode).appendChild(this);
				else $(el.parentNode).insertBefore(this, el.getNext());
				break;
			case "inside": el.appendChild(this);
		}
		return this;
	},

	injectBefore: function(el){
		return this.inject(el, 'before');
	},

	injectAfter: function(el){
		return this.inject(el, 'after');
	},

	injectInside: function(el){
		return this.inject(el, 'inside');
	},

	adopt: function(el){
		this.appendChild($(el) || new Element(el));
		return this;
	},

	remove: function(){
		this.parentNode.removeChild(this);
		return this;
	},

	clone: function(contents){
		var el = this.cloneNode(contents !== false);
		return $(el);
	},

	replaceWith: function(el){
		el = $(el) || new Element(el);
		this.parentNode.replaceChild(el, this);
		return el;
	},

	appendText: function(text){
		if (window.ie){
			switch(this.getTag()){
				case 'style': this.styleSheet.cssText = text; return this;
				case 'script': this.setProperty('text', text); return this;
			}
		}
		this.appendChild(document.createTextNode(text));
		return this;
	},

	hasClass: function(className){
		return this.className.test('(?:^|\\s+)' + className + '(?:\\s+|$)');
	},

	addClass: function(className){
		if (!this.hasClass(className)) this.className = (this.className+' '+className).clean();
		return this;
	},

	removeClass: function(className){
		if (this.hasClass(className)) this.className = this.className.replace(className, '').clean();
		return this;
	},

	toggleClass: function(className){
		return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
	},

	setStyle: function(property, value){
		if (property == 'opacity') this.setOpacity(parseFloat(value));
		else this.style[property.camelCase()] = value;
		return this;
	},

	setStyles: function(source){
		switch ($type(source)){
			case 'object':
				for (var property in source) this.setStyle(property, source[property]);
				break;
			case 'string':
				if (window.ie) this.cssText = source;
				else this.setAttribute('style', source);
		}
		return this;
	},

	setOpacity: function(opacity){
		if (opacity == 0){
			if(this.style.visibility != "hidden") this.style.visibility = "hidden";
		} else {
			if(this.style.visibility != "visible") this.style.visibility = "visible";
		}
		if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
		if (window.ie) this.style.filter = "alpha(opacity=" + opacity*100 + ")";
		this.style.opacity = this.opacity = opacity;
		return this;
	},

	getStyle: function(property){
		property = property.camelCase();
		var style = this.style[property] || false;
		if (!$chk(style)){
			if (property == 'opacity') return (this.opacity != undefined) ? this.opacity : 1;
			if (['margin', 'padding'].test(property)){
				return [this.getStyle(property+'-top') || 0,
						this.getStyle(property+'-right') || 0,
						this.getStyle(property+'-bottom') || 0,
						this.getStyle(property+'-left') || 0].join(' ');
			}
			if (document.defaultView) style = document.defaultView.getComputedStyle(this, null).getPropertyValue(property.hyphenate());
			else if (this.currentStyle) style = this.currentStyle[property];
		}
		return (style && property.test('color', 'i') && style.test('rgb')) ? style.rgbToHex() : style;
	},

	addEvent: function(type, fn){
		this.events = this.events || {};
		this.events[type] = this.events[type] || {};
		if (!this.events[type][fn]){
			if (this.addEventListener){
				this.events[type][fn] = fn;
				this.addEventListener((type == 'mousewheel' && !window.khtml) ? 'DOMMouseScroll' : type, this.events[type][fn], false);
			} else {
				this.events[type][fn] = fn.bind(this);
				this.attachEvent('on'+type, this.events[type][fn]);
			}
		}
		return this;
	},

	addEvents: function(source){
		if (source){
			for (var type in source) this.addEvent(type, source[type]);
		}
		return this;
	},

	removeEvent: function(type, fn){
		if (this.events && this.events[type] && this.events[type][fn]){
			if (this.removeEventListener){
				this.removeEventListener((type == 'mousewheel' && !window.khtml) ? 'DOMMouseScroll' : type, this.events[type][fn], false);
			} else {
				this.detachEvent('on'+type, this.events[type][fn]);
			}
			this.events[type][fn] = null;
		}
		return this;
	},

	removeEvents: function(type){
		if (this.events){
			if (type){
				if (this.events[type]){
					for (var fn in this.events[type]) this.removeEvent(type, fn);
					this.events[type] = null;
				}
			} else {
				for (var evType in this.events) this.removeEvents(evType);
				this.events = null;
			}
		}
		return this;
	},

	fireEvent: function(type, args){
		if (this.events && this.events[type]){
			for (var fn in this.events[type]){
				if (this.events[type][fn]) this.events[type][fn].apply(this, args || []);
			}
		}
	},

	getBrother: function(what){
		var el = this[what+'Sibling'];
		while ($type(el) == 'whitespace') el = el[what+'Sibling'];
		return $(el);
	},

	getPrevious: function(){
		return this.getBrother('previous');
	},

	getNext: function(){
		return this.getBrother('next');
	},

	getFirst: function(){
		var el = this.firstChild;
		while ($type(el) == 'whitespace') el = el.nextSibling;
		return $(el);
	},

	getLast: function(){
		var el = this.lastChild;
		while ($type(el) == 'whitespace') el = el.previousSibling;
		return $(el);
	},

	getParent: function(){
		return $(this.parentNode);
	},

	getChildren: function(){
		return $$(this.childNodes);
	},

	setProperty: function(property, value){
		switch (property){
			case 'class': this.className = value; break;
			case 'style': this.setStyles(value); break;
			case 'name': if (window.ie6){
				var el = $(document.createElement('<'+this.getTag()+' name="'+value+'" />'));
				$each(this.attributes, function(attribute){
					if (attribute.name != 'name') el.setProperty(attribute.name, attribute.value);
				});
				if (this.parentNode) this.replaceWith(el);
				return el;
			}
			default: this.setAttribute(property, value);
		}
		return this;
	},

	setProperties: function(source){
		for (var property in source) this.setProperty(property, source[property]);
		return this;
	},

	setHTML: function(html){
		this.innerHTML = html;
		return this;
	},

	getProperty: function(property){
		return (property == 'class') ? this.className : this.getAttribute(property);
	},

	getTag: function(){
		return this.tagName.toLowerCase();
	},

	getOffsets: function(){
		var el = this, offsetLeft = 0, offsetTop = 0;
		do {
			offsetLeft += el.offsetLeft || 0;
			offsetTop += el.offsetTop || 0;
			el = el.offsetParent;
		} while (el);
		return {'x': offsetLeft, 'y': offsetTop};
	},

	scrollTo: function(x, y){
		this.scrollLeft = x;
		this.scrollTop = y;
	},

	getSize: function(){
		return {
			'scroll': {'x': this.scrollLeft, 'y': this.scrollTop},
			'size': {'x': this.offsetWidth, 'y': this.offsetHeight},
			'scrollSize': {'x': this.scrollWidth, 'y': this.scrollHeight}
		};
	},

	getTop: function(){
		return this.getOffsets().y;
	},

	getLeft: function(){
		return this.getOffsets().x;
	},

	getPosition: function(){
		var offs = this.getOffsets();
		var obj = {
			'width': this.offsetWidth,
			'height': this.offsetHeight,
			'left': offs.x,
			'top': offs.y
		};
		obj.right = obj.left + obj.width;
		obj.bottom = obj.top + obj.height;
		return obj;
	},

	getValue: function(){
		switch (this.getTag()){
			case 'select': if (this.selectedIndex != -1) return this.options[this.selectedIndex].value; break;
			case 'input': if (!(this.checked && ['checkbox', 'radio'].test(this.type)) && !['hidden', 'text', 'password'].test(this.type)) break;
			case 'textarea': return this.value;
		}
		return false;
	}
});

function $Element(el, method, args){
	if ($type(el) == 'string') el = document.getElementById(el);
	if ($type(el) != 'element') return false;
	if (!args) args = [];
	else if ($type(args) != 'array') args = [args];
	return Element.prototype[method].apply(el, args);
};

var Window = window;

window.addEvent = document.addEvent = Element.prototype.addEvent;
window.removeEvent = document.removeEvent = Element.prototype.removeEvent;

var Garbage = {

	elements: [],

	collect: function(element){
		Garbage.elements.push(element);
	},

	trash: function(){
		window.removeEvent('unload', Garbage.trash);
		Garbage.elements.each(function(el){
			el.removeEvents();
			for (var p in Element.prototype) HTMLElement[p] = window[p] = document[p] = el[p] = null;
			el.extend = null;
		});
	}

};

window.addEvent('unload', Garbage.trash);

var Event = new Class({
	initialize: function(event){
		this.event = event || window.event;
		this.type = this.event.type;
		this.target = this.event.target || this.event.srcElement;
		if (this.target.nodeType == 3) this.target = this.target.parentNode; // Safari
		this.shift = this.event.shiftKey;
		this.control = this.event.ctrlKey;
		this.alt = this.event.altKey;
		this.meta = this.event.metaKey;
		if (['DOMMouseScroll', 'mousewheel'].test(this.type)){
			this.wheel = this.event.wheelDelta ? (this.event.wheelDelta / (window.opera ? -120 : 120)) : -(this.event.detail || 0) / 3;
		} else if (this.type.test('key')){
			this.code = this.event.which || this.event.keyCode;
			var specials = {
				'enter': 13,
				'up': 38,
				'down': 40,
				'left': 37,
				'right': 39,
				'esc': 27,
				'space': 32,
				'backspace': 8,
				'delete': 46
			};
			for (var name in specials){
				if (specials[name] == this.code) var special = name;
			}
			this.key = special || String.fromCharCode(this.code).toLowerCase();

		} else if (this.type.test('mouse') || this.type == 'click'){
			this.page = {
				'x': this.event.pageX || this.event.clientX + document.documentElement.scrollLeft,
				'y': this.event.pageY || this.event.clientY + document.documentElement.scrollTop
			};
			this.client = {
				'x': this.event.pageX ? this.event.pageX - window.pageXOffset : this.event.clientX,
				'y': this.event.pageY ? this.event.pageY - window.pageYOffset : this.event.clientY
			};
			this.rightClick = (this.event.which == 3) || (this.event.button == 2);
			switch (this.type){
				case 'mouseover': this.relatedTarget = this.event.relatedTarget || this.event.fromElement; break;
				case 'mouseout': this.relatedTarget = this.event.relatedTarget || this.event.toElement;
			}
		}
	},

	stop: function() {
		this.stopPropagation();
		this.preventDefault();
		return this;
	},

	stopPropagation: function(){
		if (this.event.stopPropagation) this.event.stopPropagation();
		else this.event.cancelBubble = true;
		return this;
    },

	preventDefault: function(){
		if (this.event.preventDefault) this.event.preventDefault();
		else this.event.returnValue = false;
		return this;
	}
});

Function.extend({
	bindWithEvent: function(bind, args){
		return this.create({'bind': bind, 'arguments': args, 'event': Event});
	}
});

var Chain = new Class({
	chain: function(fn){
		this.chains = this.chains || [];
		this.chains.push(fn);
		return this;
	},

	callChain: function(){
		if (this.chains && this.chains.length) this.chains.splice(0, 1)[0].delay(10, this);
	},

	clearChain: function(){
		this.chains = [];
	}
});

var Events = new Class({
	addEvent: function(type, fn){
		if (fn != Class.empty){
			this.events = this.events || {};
			this.events[type] = this.events[type] || [];
			if (!this.events[type].test(fn)) this.events[type].push(fn);
		}
		return this;
	},

	fireEvent: function(type, args, delay){
		if (this.events && this.events[type]){
			this.events[type].each(function(fn){
				fn.create({'bind': this, 'delay': delay, 'arguments': args})();
			}, this);
		}
		return this;
	},

	removeEvent: function(type, fn){
		if (this.events && this.events[type]) this.events[type].remove(fn);
		return this;
	}
});

var Hash = new Class({
	length: 0,

	initialize: function(obj) {
		this.obj = {};
		for (var property in obj) {
			this.obj[property] = obj[property];
			this.length++;
		}
	},

	get: function(key) {
		return this.obj[key];
	},

	set: function(key, value) {
		if (value == null) throw 'Cannot put null values in the map';
		if (this.obj[key] == undefined) this.length++;
		this.obj[key] = value;
		return this;
	},

	remove: function(key) {
		if (this.obj[key] == undefined) return;
		var obj = {};
		this.length--;
		for (var property in this.obj){
			if (property != key) obj[property] = this.obj[property];
		}
		this.obj = obj;
		return this;
	},

	each: function(fn, bind) {
		for (var property in this.obj) fn.call(bind || this, property, this.obj[property]);
	},
	
	extend: function(obj){
		this.initialize(Object.extend(this.obj, obj));
		return this;
	},

	empty: function() {
		return (this.length == 0);
	},

	keys: function() {
		var keys = [];
		for (var property in this.obj) keys.push(property);
		return keys;
	},

	values: function() {
		var values = [];
		for (var property in this.obj) values.push(this.obj[property]);
		return values;
	}
});

function $H(obj) {
	return new Hash(obj);
};

window.extend = Object.extend;

window.extend({
	addEvent: function(type, fn){
		if (type == 'domready'){
			if (this.loaded) fn();
			else if (!this.events || !this.events.domready){
				var domReady = function(){
					if (this.loaded) return;
					this.loaded = true;
					if (this.timer) this.timer = $clear(this.timer);
					Element.prototype.fireEvent.call(this, 'domready');
					this.events.domready = null;
				}.bind(this);
				if (document.readyState && this.khtml){ //safari and konqueror
					this.timer = function(){
						if (['loaded','complete'].test(document.readyState)) domReady();
					}.periodical(50);
				}
				else if (document.readyState && this.ie){ //ie
					document.write("<script id=ie_ready defer src=javascript:void(0)><\/script>");
					$('ie_ready').onreadystatechange = function(){
						if (this.readyState == 'complete') domReady();
					};
				} else { //others
					this.addEvent("load", domReady);
					document.addEvent("DOMContentLoaded", domReady);
				}
			}
		}
		Element.prototype.addEvent.call(this, type, fn);
		return this;
	},

	onDomReady: function(init){
		return this.addEvent('domready', init);
	}
});