/*
   Behaviour v2
   
   My stuff is BSD Licensed. Selector class is MIT Licensed.
*/

// Selects all elements matching expression (a CSS Selector). Uses the 
// excellent Selector class written for the prototype library by Sam 
// Stephenson. 
//
// scope argument is optional.
//
// Usage:
//	alert($('li', newDiv).length + ' list items added');
//	...
//	$('#some-object-id')[0].innerHTML += 'blah';
//	...
//	with($('.tabbedClass li ')){
//		removeChild(firstChild);
//	}
function $(expression, scope){
	if (typeof expression != 'string'){
		return expression;
	}
	
	var results = Selector.Find(expression, scope);
	return expression.match(/#\S+$/)?results[0]:results;
}

// Create a class (with an optional superclass)
//
// Usage:
//	MyClass = Class.create(BaseClass);
//	MyClass.prototype = {
//		initialize : function(){ this.someVar = 0; }
//	}
var Class = {
	create: function(superClass){
		return function() {
			if (superClass){
				for (property in superClass.prototype){
					this[property] = superClass.prototype[property];
				}
			}
			
			if (this.initialize){
				this.initialize.apply(this, arguments);
			}
		}
	}
}

Object.extend = function(destination, source) {
	for (property in source) destination[property] = source[property];
	return destination;
}

// Bind functions (from prototype) to an object.
// Usage:
//	this.someFunction.bind(this);
Function.prototype.bind = function(object) {
  var __method = this;
  return function() {
    return __method.apply(object, arguments);
  }
}

Function.prototype.bindAsEventListener = function(object) {
var __method = this;
	return function(event) {
		__method.call(object, event || window.event);
	}
}



// Class to find elements by using a CSS Selector. Copied from Sam Stephensons 
// Selector class from the Prototype Library.
var Selector = Class.create();
// Class method to evaluate a complete expression
Selector.Find = function(expression, scope){
	if (!expression.split){
		return null;
	}
	
	var expr, expressions = expression.split(/\s+/), scopes=[scope || document];
	
	for (var i=0;expr=expressions[i];i++){
		var selector = new Selector(expr), results = [];
		
		for (var j=0;scope=scopes[j];j++){
			selector.findElements(scope || document, results);
		}
		
		scopes = results;
	}
	
	return scopes;
};
Selector.prototype = {
	initialize: function(expression){
		this.params ={classNames: []};
		this.expression = expression;
		this.parseExpression();
		this.compileMatcher();
	},
	parseExpression: function(){
		function abort(message){ throw 'Parse error in selector: ' + message; }
		
		if (this.expression == '') abort('empty expression');
		if (this.expression == '*') return this.params.wildcard = true;
			
		var params = this.params, expr = this.expression, match;
		while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)){
			var modifier = match[1], clause = match[2], rest = match[3];
			switch (modifier){
				case '#':
					params.id = clause; 
					break;
				case '.':
					params.classNames.push(clause); 
					break;
				default:
					params.tagName = clause.toUpperCase(); 
					break;
			}
			expr = rest;
		}
		
		if (expr.length > 0) abort(expr);
	},
	buildMatchExpression: function(){
		var params = this.params, conditions = [], clause;
		
		if (params.wildcard)
			return 'true';
	 
		if (clause = params.id)
			conditions.push('element.id == "' + clause + '"');
		if (clause = params.tagName)
			conditions.push('element.tagName.toUpperCase() == "' + clause + '"');
		if ((clause = params.classNames).length > 0)
			for (var i = 0; i < clause.length; i++)
				conditions.push('element.className.match(/\\b' + clause[i] + '\\b/)');
		
		return conditions.join(' && ');
	},
	compileMatcher: function(){
		eval('this.match = function(element){ if (!element.tagName) return false; return ' + this.buildMatchExpression() + ' }');
	},
	findElements: function(scope, results){
		var element;

		if (element = document.getElementById(this.params.id))
			if (this.match(element))
				return results.push(element);
		
		scope = scope.getElementsByTagName(this.params.tagName || '*');
		
		for (var i = 0; i < scope.length; i++)
			if (this.match(element = scope[i])){
				results.push(element);
			}
	
		return results;
	}
}

// Behaviour converted to prototype-y classes
$Behaviour = Class.create();
$Behaviour.prototype = {
	initialize : function(){
		this.sheets = new Array;
		
		// TODO: Add some magic here to apply automagically on DOMLoaded...
	},
	// Adds a hash of selectors to behaviour
	//
	// Usage:
	//	Behaviour.register({'body':function(e){e.style.cssText="background: red"}});
	register : function(sheet){
		this.sheets.push(sheet);
	},
	// Applies all selectors from all sheets (scope is optional)
	//
	// Usage:
	//	Behaviour.apply();
	apply : function(scope){
		var sheet, selector;
		
		for (var i=0;sheet=this.sheets[i];i++){
			for (selector in sheet){
				this.applyFunctionToSelector(selector, scope, sheet[selector])
			}
		}
	},
	// Apply a named selector (scope is optional)
	//
	// Usage:
	//	Behaviour.applySelector('//li');
	applySelector : function(selector, scope){
		var sheet;
		
		for (var i=0;sheet=this.sheets[i];i++){
			if (sheet[selector]){
				this.applyFunctionToSelector(selector, scope, sheet[selector])
			}
		}
	},
	// Internal method - actually applies the user function
	//
	// Applies a function once per selector per element.
	applyFunctionToSelector : function(selector, scope, func){
		var element, list;
		
		try{
			list = Selector.Find(selector, scope);
		}catch(e){
			throw 'Error applying selector \'' + selector +'\'';
		};
		
		for (var i=0;element=list[i];i++){
			// Bit ugly....
			if (element.behaving_for){
				if (element.behaving_for[selector]){
					continue;
				}
			}else{
				element.behaving_for={};
			}
			element.behaving_for[selector] = true;
			
			//try{
				func(element);
			//}catch(e){
			//	throw 'Error applying function for selector \'' + selector +'\'';
			//}
		}
	}	
}

// Create legacy-compatible Behaviour object
Behaviour = new $Behaviour;