function $returnFalse() { return false; }

Function.implement({
	callOnce:function () {
		if (!this.$alreadyCalled) this();
		this.$alreadyCalled=true;
	},
	resetCall:function () {
		this.$alreadyCalled=null;
	}
});

Array.implement({
	removeDuplicates:function () {
		// TODO to implement
		return this;
	},
	pluck:function (property) {
		var a=[];
		this.each(function (o) { a.push(o?o[property]:null); });
		return a;
	},
	invoke:function (method,args) {
		var a=[];
		this.each(function (o) { a.push(o[method].apply(o,args || [])); });
		return a;
	}
});

//Element.prototype.$oldEmpty=Element.prototype.empty;

Window.implement({
	$E: function(selector){
		return this.document.getElement(selector);
	}
});
Element.implement({
	/*
	empty:function () {
		var tag=this.get("tag");
		if (["table","tbody","thead","tfoot","tr"].contains(tag)) return this.removeChildren();
		else return Element.prototype.$oldEmpty.call(this);
	},
	*/
	setHTML:function() {
		var html=Array.join(arguments,"");

		var tag=this.get("tag");
		var tableIdx=-1;
		switch (tag) {
			case "table":
				html="<table>"+html+"</table>";
				tableIdx=1;
				break;
			case "tbody": case "thead": case "tfoot":
				html="<table><"+tag+">"+html+"</"+tag+"></table>";
				tableIdx=2;
				break;
			case "tr":
				html="<table><tbody><tr>"+html+"</tr></tbody></table>";
				tableIdx=3;
				break;
		}
		
		html=html.trim().stripScripts(true);

		if (tableIdx>-1) {
			var div=new Element("div").set("html",html);
			var child=div;
			for (var i=0;i<=tableIdx;i++) child=child.firstChild;
			this.removeChildren().grab(child);
		}
		else this.innerHTML=html;
		return this;
	},
	show:function () {
		return this.set("display",true);
	},
	hide:function () {
		return this.set("display",false);
	},
	setVisibility:function (visible) {
		//console.log("setVisibility",this);
		return this.set("visibility",visible);
	},
	visible:function () {
		//console.log("visible",this);
		return this.get("visibility");
	},

	toggle:function () {
		this.set("display","toggle");
	},

	hover:function (over,out) {
		return this.addEvent("mouseenter",over).addEvent("mouseleave",out);
	},
	setHoverClass:function (className) {
		if (!className) className="over";
		// only on ie6 since :hover is not working
		return Browser.Engine.trident4 ?
			this.hover(
				function () { this.addClass(className); },
				function () { this.removeClass(className); }
			) : this;
	},

	setSelection:function (b) {
		if (window.ie || window.opera) this[(b?"remove":"add")+"Event"]("selectstart",$returnFalse);
		if (window.gecko) this.style.MozUserSelect=b?"":"none";
		if (window.webkit) this.style.KhtmlUserSelect=b?"":"none";
		return this;
	},
	
	removeTextNodes:function () {
		if (this._removedTextNodes) return;
		$A(this.childNodes).each(function (o) {
			if (o.nodeType===3) {
				if (/^\s+$/.test(o.data)) o.parentNode.removeChild(o);
			}
			else $(o).removeTextNodes();
		});
		this._removedTextNodes=true;
		return this;
	},

	removeChildren:function () {
		while (this.hasChildNodes()) this.removeChild(this.lastChild);
		return this;
	},

	findParent:function (predicate) {
		var el=this;
		while (!predicate($(el)) && el && el!=document.documentElement) el=el.parentNode;
		return el===document.documentElement ? null : el;
	},

	replaceClass:function (oldClass,newClass) {
		this.removeClass(oldClass).addClass(newClass);
	},

	retrieveOrStore:function (key,getValueFunction) {
		if (!this.retrieve(key)) this.store(key,getValueFunction.call(this));
		return this.retrieve(key);
	},

	addReplacingEvent:function (type,fn) {
		return this.addEvent(type,function (e) {
			e.stop();
			fn.call(this,e);
		});
	},
	effects:function(options) {
		return new Fx.Morph(this,options);
	},
	effect:function(property,options) {
		options=$extend({property:property},options);
		return new Fx.Tween(this,options);
	}
});

Element.Properties.extend({
	visibility:{
		set:function (visible) {
			if (visible==="toggle") {
				this.set("visibility",!this.get("visibility"));
				return;
			}
			if (visible) this.removeClass("visibility-hidden").setStyle("visibility","");
			else this.addClass("visibility-hidden").setStyle("visibility","hidden");
		},
		get:function () {
			// check all parents for one that's not visible
			var p=this;
			while (p && p.get("tag")!="body" && p.getStyle("display")!="none" && p.getStyle("visibility")!="hidden") {
				p=p.getParent();
			}
			return p && p.get("tag")=="body";
		}
	},
	display:{
		set:function (visible) {
			if (visible==="toggle") {
				this.set("display",!this.get("display"));
				return;
			}
			if (visible) this.removeClass("display-none").setStyle("display","");
			else this.addClass("display-none").setStyle("display","none");
		},
		get:function () {
			return this.get("visibility");
		}
	}
});

Element.fromMarkup=function (markup,multipleElements) {
	var div=new Element("span").setHTML(markup.trim());
	return multipleElements ? div.getChildren() : div.getFirst();
};

String.implement({
	escapeHtml:function (isHtml) { 
		var str=this;
		if (!isHtml) str=str.replace(/>/g,"&gt;").replace(/</g,"&lt;");
		else str=str.replace(/\r?\n/g,"<br/>");
		str=str.replace(/"/g,"&quot;").replace(/'/g,"&#39;");
		return str;
	}
});

Events.makeObjectEventable=function (obj) {
	$extend(obj,Events.prototype);
};
Options.makeObjectOptionable=function (obj) {
	$extend(obj,Options.prototype);
};
Options.makeClassOptionable=function (cls) {
	cls.setOptions=function () {
		Options.prototype.setOptions.apply(cls.prototype,arguments);
	};
};

/* ooooooooooooooooooooo MORE ooooooooooooooooooooo */

//MooTools More, <http://mootools.net/more>. Copyright (c) 2006-2008 Valerio Proietti, <http://mad4milk.net>, MIT Style License.

/*
Script: Scroller.js
	Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.

License:
	MIT-style license.
*/

var Scroller = new Class({

	Implements: [Events, Options],

	options: {
		area: 50,
		velocity: 1,
		onChange: function(x, y){
			this.element.scrollTo(x, y);
		}
	},

	initialize: function(element, options){
		this.setOptions(options);
		this.element = $(element);
		this.listener = ($type(this.element) != 'element') ? $(this.element.getDocument().body) : this.element;
		this.timer = null;
		this.coord = this.getCoords.bind(this);
	},

	start: function(){
		this.listener.addEvent('mousemove', this.coord);
	},

	stop: function(){
		this.listener.removeEvent('mousemove', this.coord);
		this.timer = $clear(this.timer);
	},

	getCoords: function(event){
		this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
		if (!this.timer) this.timer = this.scroll.periodical(50, this);
	},

	scroll: function(){
		var size = this.element.getSize(), scroll = this.element.getScroll(), pos = this.element.getPosition(), change = {'x': 0, 'y': 0};
		for (var z in this.page){
			if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0)
				change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
			else if (this.page[z] + this.options.area > (size[z] + pos[z]) && size[z] + size[z] != scroll[z])
				change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
		}
		if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
	}

});

//MooTools More, <http://mootools.net/more>. Copyright (c) 2006-2008 Valerio Proietti, <http://mad4milk.net>, MIT Style License.

/*
Script: Tips.js
	Class for creating nice tips that follow the mouse cursor when hovering an element.

License:
	MIT-style license.
*/

var Tips = new Class({
	
	Implements: [Events, Options],

	options: {
		onShow: function(tip){
			tip.setStyle('visibility', 'visible');
		},
		onHide: function(tip){
			tip.setStyle('visibility', 'hidden');
		},
		showDelay: 100,
		hideDelay: 100,
		className: 'tool',
		offsets: {x: -354, y: 0},
		fixed: false
	},

	initialize: function(){
		var params = Array.link(arguments, {options: Object.type, elements: $defined});
		this.setOptions(params.options || null);
		
		this.tip = new Element('div').inject(document.body);
		
		if (this.options.className) this.tip.addClass(this.options.className);
		
		var top = new Element('div', {'class': 'tip-top'}).inject(this.tip);
		this.container = new Element('div', {'class': 'tip'}).inject(this.tip);
		var bottom = new Element('div', {'class': 'tip-bottom'}).inject(this.tip);

		this.tip.setStyles({position: 'absolute', top: 0, left: 0, visibility: 'hidden'});
		
		if (params.elements) this.attach(params.elements);
	},
	
	attach: function(elements){
		$$(elements).each(function(element){
			var title = element.retrieve('tip:title', element.get('title'));
			var text = element.retrieve('tip:text', element.get('rel') || element.get('href'));
			var enter = element.retrieve('tip:enter', this.elementEnter.bindWithEvent(this, element));
			var leave = element.retrieve('tip:leave', this.elementLeave.bindWithEvent(this, element));
			element.addEvents({mouseenter: enter, mouseleave: leave});
			if (!this.options.fixed){
				var move = element.retrieve('tip:move', this.elementMove.bindWithEvent(this, element));
				element.addEvent('mousemove', move);
			}
			element.store('tip:native', element.get('title'));
			element.erase('title');
		}, this);
		return this;
	},
	
	detach: function(elements){
		$$(elements).each(function(element){
			element.removeEvent('mouseenter', element.retrieve('tip:enter') || $empty);
			element.removeEvent('mouseleave', element.retrieve('tip:leave') || $empty);
			element.removeEvent('mousemove', element.retrieve('tip:move') || $empty);
			element.eliminate('tip:enter').eliminate('tip:leave').eliminate('tip:move');
			var original = element.retrieve('tip:native');
			if (original) element.set('title', original);
		});
		return this;
	},
	
	elementEnter: function(event, element){
		
		$A(this.container.childNodes).each(Element.dispose);
		
		var title = element.retrieve('tip:title');
		
		if (title){
			this.titleElement = new Element('div', {'class': 'tip-title'}).inject(this.container);
			this.fill(this.titleElement, title);
		}
		
		var text = element.retrieve('tip:text');
		if (text){
			this.textElement = new Element('div', {'class': 'tip-text'}).inject(this.container);
			this.fill(this.textElement, text);
		}
		
		this.timer = $clear(this.timer);
		this.timer = this.show.delay(this.options.showDelay, this);

		this.position((!this.options.fixed) ? event : {page: element.getPosition()});
	},
	
	elementLeave: function(event){
		$clear(this.timer);
		this.timer = this.hide.delay(this.options.hideDelay, this);
	},
	
	elementMove: function(event){
		this.position(event);
	},
	
	position: function(event){
		var size = window.getSize(), scroll = window.getScroll();
		var tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight};
		var props = {x: 'left', y: 'top'};
		for (var z in props){
			var pos = event.page[z] + this.options.offsets[z];
			if ((pos + tip[z] - scroll[z]) > size[z]) pos = event.page[z] - this.options.offsets[z] - tip[z];
			this.tip.setStyle(props[z], pos);
		}
	},
	
	fill: function(element, contents){
		(typeof contents == 'string') ? element.set('html', contents) : element.adopt(contents);
	},

	show: function(){
		this.fireEvent('show', this.tip);
	},

	hide: function(){
		this.fireEvent('hide', this.tip);
	}

});