/**
 *	LB Utils for jQuery
 *	---------------------------------------------
 *	(c) 2008 Lost Boys - http://www.lostboys.nl
 */

var Class = {
	extend:function(Base, constructor, prototype) {
		var Extended = function() {
			Base.apply(this, arguments);
			if(constructor) constructor.apply(this, arguments);
		}

		this.implement(Extended, Base.prototype);
		if(prototype) this.implement(Extended, prototype);
		Extended.prototype.constructor = Extended;
		return Extended;
	},

	implement:function(Class, protoface) {
		for(var i in protoface) {
			Class.prototype[i] = protoface[i];
		}
	}
}

Function.prototype.bind = function(scope) {
	var method = this;
	return function() {
		return method.apply(scope, arguments);
	}
}

/**
 *	LinkRelations for jQuery
 *	--------------------------
 */

function LinkListener(node, settings) {
	this.relations = [];
	this.regNode = settings.node || /^a$/i;
	this.attribute = settings.attribute || 'rel';
	var self = this;
	$(node).click(function(e) {
		self.handleClick(e);
	})
}

LinkListener.prototype = {
	register:function(expression, handler, scope) {
		this.relations.push({
			expression:expression, handler:handler, scope:(scope || window)
		});
	},

	getHandlers:function(attribute) {
		var handlers = [];
		for (var rel,i=0; i<this.relations.length; i++) {
			rel = this.relations[i];
			if(rel.expression.test(attribute)) { handlers.push(rel); }
		}
		return handlers;
	},

	handleClick:function(e) {
		var target = e.target;
		while(target && !this.regNode.test(target.nodeName)) {
			target = target.parentNode;
		}
		var attribute = target? (target[this.attribute] || target.getAttribute(this.attribute)) : null;
		var relations = target? this.getHandlers(attribute) : [];
		
		var cancel = false;
		for(var relation,i=0; i<relations.length; i++) {
			relation = relations[i];
			if(relation && relation.handler.apply(relation.scope, [target, attribute])) {
				cancel = true;
			}
		}

		if(cancel) e.preventDefault();
	}
}

/**
 *	Extend jQuery
 *	--------------------------
 */

jQuery.extend(jQuery, {
	registerPlugin:function(name, Constructor) {
		var plugin = {};
		plugin[name] = function(settings) {
			for(var i=0; i<this.length; i++) {
				new Constructor(this[i], settings);
			}
			return this;
		}
		
		jQuery.extend(jQuery.fn, plugin);
	},

	// http://www.danwebb.net/2008/2/8/event-delegation-made-easy-in-jquery
	delegateEvent:function(rules) {
	   return function(e) {
		  var target = $(e.target);
		  for(var selector in rules) {
			 if(target.is(selector))
				return rules[selector].apply(this, $.makeArray(arguments));
		  }
	   }
	}
});

/**
 *	Extend jQuery functions
 *	--------------------------
 */

jQuery.extend(jQuery.fn, {
	// links LinkListener to jQuery
	addRelation:function(expression, handler, scope) {
		this.each(function(){
			if(!this.$linkListener) {
				this.$linkListener = new LinkListener(this, {});
			}	this.$linkListener.register(expression, handler, scope);
		});
		return this;
	},

	// links InputListener to jQuery
	addInputRelation:function(expression, handler, scope) {
		this.each(function(){
			if(!this.$inputListener) {
				this.$inputListener = new LinkListener(this, {node:/^(input|select)$/i, attribute:'className'});
				this.$inputListener.handleClick = function(e) {
					var input = e.target;
					if(/(select|option)/i.test(input.nodeName)) {
						var select = $(input).bubbleTo('select')[0];
						if(!select.$dynamicOnchange) {
							$(select).bindScoped('change', LinkListener.prototype.handleClick, this);
							select.$dynamicOnchange = true;
						}
					} else {
						LinkListener.prototype.handleClick.call(this, e);
					}
				}
			}	this.$inputListener.register(expression, handler, scope);
		});
		return this;
	},

	// bubbles from given element to the one of given type (including self)
	bubbleTo:function(type) {
		var node = this[0], regType = new RegExp('^'+type+'$', 'i');
		while(node) {
			if(regType.test(node.nodeName)) return $(node);
			node = node.parentNode;
		}	return null;
	},

	// jQuery already has add, remove and toggle
	replaceClass:function(oldClass, newClass) {
		if(this.hasClass(oldClass)) {
			this.removeClass(oldClass);
			this.addClass(newClass);
		}	return this;
	},

	// jQuery doesn't do offset from a specified parent
	calculateLeft:function(parent) {
		var left = 0, node = this[0];
		while(node && (!parent || node != parent)) {
			left += node.offsetLeft;
			node = node.offsetParent;
		}	return left;
	},

	// jQuery doesn't do offset from a specified parent
	calculateTop:function(parent) {
		var top = 0, node = this[0];
		while(node && (!parent || node != parent)) {
			top += node.offsetTop;
			node = node.offsetParent;
		}	return top;
	},

	// allows a scoped handler to be bound to an event
	bindScoped:function(type, handler, scope) {
		var scoped = function (e) { handler.call(scope || this, e); };
		this.bind(type, scoped);
		handler.guid = scoped.guid;
		return this;
	}
});

/**
 *	Function extentions
 *	--------------------------
 */

Function.prototype.bind = function(scope) {
	var method = this;
	return function() {
		return method.apply(scope, arguments);
	}
}

Function.prototype.partial = function() {
	var defaults = arguments, method = this;
	return function() {
		var passed = [], amount = Math.max(defaults.length, arguments.length);
		for(var i=0; i<amount; i++) {
			passed[i] = arguments[i] || defaults[i];
		}	
		return method.apply(this, passed);
	}
}

/**
 *	Array extentions
 *	--------------------------
 */

if(!Array.prototype.forEach) {
	Array.prototype.forEach = function(handler, scope) {
		var l = this.length, s = scope || window;
		for(var i=0; i<l; i++) {
			handler.call(s, this[i], i, this);
		}
	}
}

if(!Array.prototype.filter) {
	Array.prototype.filter = function(handler, scope) {
		var value, result = [], l = this.length, s = scope || window;
		for(var i=0; i<l; i++) {
			value = this[i];
			if(handler.call(s, value, i, this)) {
				result.push(value);
			}
		}
		return result;
	}
}

if(!Array.prototype.some) {
	Array.prototype.some = function(handler, scope) {
		var value, l = this.length, s = scope || window;
		for(var i=0; i<l; i++) {
			value = this[i];
			if(value != null && handler.call(s, value, i, this)) {
				return true;
			}
		}
		return false;
	}
}

if(!Array.prototype.every) {
	Array.prototype.every = function(handler, scope) {
		var value, l = this.length, s = scope || window;
		for(var i=0; i<l; i++) {
			value = this[i];
			if(value != null && !handler.call(s, value, i, this)) {
				return false;
			}
		}
		return true;
	}
}

if(!Array.prototype.map) {
	Array.prototype.map = function(handler, scope) {
		var result = [], l = this.length, s = scope || window;
		for(var i=0; i<l; i++) {
			result[i] = handler.call(s, this[i]);
		}	return result;
	}
}
