Backbone.combine = function(Child, propName) {
	var prop = Child.prototype[propName],
		parentProto = Child.prototype.constructor.__super__
		parentProp = parentProto && parentProto[propName];
	
	if (prop && parentProp && prop != parentProp) {
		Child.prototype[propName] = _.extend({}, parentProp, prop);
	}
}

// checks if a property on a Class is unique and wasn't inherited
Backbone.unique = function(Child, propName) {
	return (Child.prototype[propName] !== Child.prototype.constructor.__super__[propName]);
}

Backbone.View = (function() {
	var newView = function(options) {
		options = options || {};
		this._configure(options);
		
		// if this View has no element in the template or options, create an element now
		if ((!this.templates || !this.templates.list.el) && !options.el) {
			this._ensureElement();
		}
		
		if (!this.templates || !this.templates.list.el) {
			this.delegateEvents();
		}
		
		this.initialize(options);
	}
	
	_.extend(newView.prototype, Backbone.View.prototype);
	newView.extend = Backbone.View.extend;
	
	var eventSplitter = /^(\w+)\s*(.*)$/;
	
	newView.prototype.delegateEvents = function(events) {
		if (!(events || (events = this.events))) {
			return;
		}
		
		if (this.eventsDelegated === true) {
			console.log('Unbound');
			$_(this.el).unbind();
		}
		this.eventsDelegated = true;
		
		for (var key in events) {
			var methodName = events[key],
				match = key.match(eventSplitter),
				eventName = match[1], selector = match[2],
				method = _.bind(this[methodName], this);
			
			if (selector === '') {
				$_(this.el).bind(eventName, method);
			} else {
				$_(this.el).delegate(selector, eventName, method);
			}
		}
	}
	
	return newView;
})();

_.each(['onDefine', 'onCreate', 'beforeInit'], function(method) {
	// create a store for the callbacks
	var typeMap = {
		model: [], collection: [],
		view: [], controller: []
	};
	
	var setter = Backbone[method] = function(types, callback) {
		if (typeof types == 'string') {
			types = [types];
		}
		
		for (var i = 0, type; (type = types[i]); i++) {
			type = type.toLowerCase();
			typeMap[type].push(callback);
		}
	}
	
	setter.invoke = function(type, thisObj, args) {
		// if a single argument was passed, place it in an array for when .apply is called
		if (!_.isArguments(args) && !_.isArray(args)) {
			args = [args];
		}
		
		for (var i = 0, callback; (callback = typeMap[type][i]); i++) {
			callback.apply(thisObj, args);
		}
	}
});

// change the original extend to support running functions after an extend occurs
_.each(['Model', 'View', 'Collection', 'Controller'], function(name) {
	var Class = Backbone[name], newClass,
		constructor = Class.prototype.constructor,
		extend = Class.extend,
		lower = name.toLowerCase();
	
	newClass = function(options) {
		// when the real constructor is run the initialize function gets called
		// replace it with a noop until the beforeInit functions have run
		var init = this.initialize;
		this.initialize = function() {};
		
		// call the real constructor
		constructor.apply(this, arguments);
		
		// execute all the onCreate functions
		Backbone.onCreate.invoke(lower, this, arguments);
		// execute all the beforeInit functions
		Backbone.beforeInit.invoke(lower, this, arguments);
		
		// now run the initialize function
		this.initialize = init;
		init.apply(this, arguments);
	}
	
	newClass.prototype = Class.prototype;
	
	newClass.extend = function( protoProps, classProps ) {
		var Child = extend.call(this, protoProps, classProps);
		Child.extend = newClass.extend;
		
		Backbone.onDefine.invoke(lower, Child, Child);
		
		return Child;
	}
	
	Backbone[name] = newClass;
});


/* -- All -- */

Backbone.Trait = {};

Backbone.onDefine(['Model', 'View'], function() {
	var mixin = this.prototype.mixin,
		__super__ = this.prototype.constructor.__super__,
		parentMixin = __super__.mixin;
	
	this.prototype.$super = __super__;
	
	if (mixin && mixin != parentMixin) {
		var newMixin = {};
		for (var i = 0, mixinName; (mixinName = this.prototype.mixin[i]); i++) {
			var trait = Backbone.Trait[mixinName];
			
			if (!trait) {
				throw 'Mixin ' + mixinName + ' was not found';
			}
			
			// make the mixins initialize function accessible through this.mixin.{Mixin Name}()
			newMixin[mixinName] = trait.initialize || function() {};
			
			for (var propName in trait) {
				if (propName != 'initialize') {
					if (!this.prototype[propName]) {
						this.prototype[propName] = trait[propName];
					}
					
					newMixin[mixinName][propName] = trait[propName];
				}
			}
			
			/*for (var propName in mixin) {
				// check for the mixins required functions on the Class
				if (propName == 'requires') {
					var requires = (typeof mixin.requires == 'string') ? [mixin.requires] : mixin.requires;
					for (var j = 0, require; (require = requires[i]); i++) {
						if (!this.prototype[mixinName] || !this.prototype[mixinName][require]) {
							throw "The trait " + mixinName + " requires the property/method " + mixinName + "." + require;
						}
					}
				}
				else if (!this.prototype[propName]) {
					this.prototype[propName] = mixin[propName];
				}
			}*/
		}
		
		this.prototype.mixin = _.extend(newMixin, parentMixin);
	}
	
	Backbone.combine(this, 'updates');
});

Backbone.onCreate(['Model', 'View'], function() {
	var newSup = _.bind(function() {
		return this.__super('initialize', arguments);
	}, this);
	
	for (var propName in this.$super) {
		newSup[propName] = (function(self, propName) {
			return function() {
				return self.__super(propName, arguments);
			}
		})(this, propName);
	}
	
	this.$super = newSup;
	
	// bind all the trait init function to this instance
	if (this.mixin) {
		var newMixin = {};
		for (var traitName in this.mixin) {
			var trait = Backbone.Trait[traitName];
			
			newMixin[traitName] = _.bind(this.mixin[traitName], this);
			
			for (var propName in trait) {
				if (_.isFunction(trait[propName])) {
					newMixin[traitName][propName] = _.bind(trait[propName], this);
				}
			}
		}
		
		this.mixin = newMixin;
	}
});

Backbone.beforeInit(['Model', 'View'], function() {
	this.setUpdates();
});

// execute a function of the parent but in the context of the child
Backbone.Model.prototype.__super =
Backbone.View.prototype.__super = function(funcName, arguments) {
	var contextName = '_context' + funcName,
		oldContext = this[contextName],
		context = (oldContext || this).constructor.__super__, parent;
	
	while(context) {
		parent = context.constructor.__super__;
		
		if (context[funcName] != parent[funcName]) {
			this[contextName] = context;
			break;
		}
		
		context = parent;
	}
	
    // execute the function from the parent
	var retValue = context[funcName].apply(this, arguments);
    
	this[contextName] = oldContext;
	
	return retValue;
}

Backbone.Model.prototype.setUpdates =
Backbone.View.prototype.setUpdates = function() {
	var	self = this;
		model = ( this.toJSON ) ? this : this.model;
	
	if (!model) {
		return;
	}
	
	_.each(this.updates, function(callback, name) {
		model.bind('change:' + name, function( model, value ) {
			callback.call( self, value );
		});
	});
}

/* -- View -- */

// a counter to keep the names assigned to anonymous events unique
Backbone.eventId = 0;

Backbone.onDefine('View', function() {
	var proto = this.prototype;
	
	// map $el to el if el does not exist
	//if (proto.$el && !proto.el) {
	//	proto.el = proto.$el[0];
	//}
	// create a jQuery/Zepto wrapped el
	//if (proto.el && !proto.$el) {
	//	proto.$el = $(proto.el);
	//}
	
	Backbone.combine(this, 'events');
	
	_.each(proto.events, function(callback, name) {
		// if the callback is a function and not a string:
		// create a named function on the class and set the callback to that name
		if (_.isFunction(callback)) {
			var funcName = 'eventFunc' + Backbone.eventId++;
			
			this[funcName] = callback;
			this.events[name] = funcName;
		}
		
		if ( callback === null ) {
			delete proto.events[ name ];
		}
	}, proto);
});

Backbone.beforeInit('view', function(options) {
	if (this.widget) {
		if (this.el) {
			// pull out any properties from the existing widget element
			if (this.properties) {
				for (var i = 0, property; (property = this.properties[i]); i++) {
					this.options[property] = this.el.getAttribute(property);
				}
			}
			
			if (this.el.hasAttribute('ref') && options.parent) {
				options.parent.addReference(this, this.el.getAttribute('ref'));
			}
		}
	}
});

Backbone.Model.prototype.refresh =
Backbone.View.prototype.refresh = function() {
	var model = (this.toJSON) ? this : this.model,
		data = model.attributes;
	
	for (var name in data) {
		var update = this.updates[name];
		update && update.call(this, data[name]);
	}
	
	// refresh the view aswell
	if (this.toJSON) {
		this.view.refresh();
	}
}

/*Backbone.onCreate('View', function() {
		/* Is this needed?
		if (_.isString(callback) && callback.indexOf('.') != -1) {
			var path = callback.split('.');
			callback = function(e) {
				var name, method = this, thisObj = this, i = 0;
				if (this[path[0]] && this[path[0]].relegate) {
					thisObj = this[path[0]];
				}
				
				while (name = path[i++]) {
					method = method[name];
				}
				
				method.call(thisObj, e);
			}
		}
		
		if (_.isFunction(callback)) {
			var funcName = 'eventFunc' + Backbone.eventId++;
			this.prototype[funcName] = callback;
			this.prototype.events[name] = funcName;
		}
	}, this);
});*/

var $_ = jQuery.single = (function() {
    var collection = jQuery([1]);
 
    return function(element) {
        // Give collection the element:
        collection[0] = element;
 
        // Return the collection:
        return collection;
    };
}());
