Backbone.Template = {
	postfix: '-template',
	
	engines: {
		underscore: {
			compile: function(content) {
				return _.template(content);
			}
		},
		handlebars: {
			compile: function(content) {
				return Handlebars.compile(content);
			}
		}
	},
	
	engine: 'underscore',
	
	create: function(template, data) {
		return template(data);
	},
	
	// automatically makes a widget from any element matching the selector
	autos: [],
	auto: function(selector, View) {
		this.autos.push([selector, View ]);
	},
	
	find: function(parent, selector) {
		return (parent.querySelectorAll) ?
			parent.querySelectorAll(selector) :
			$(selector, parent).get();
	},
	
	//findAttr: function(parent, attr, callback, thisObj) {
	//	var selector = '*[' + attr + ']';
	//	
	//	this.find(parent, selector, function(el) {
	//		var value = el.getAttribute(attr);
	//		callback.call(thisObj, el, value);
	//	});
	//},
	
	getEl: function(obj, name) {
		var camel = $.camelCase(name),
			el = obj[camel];
		
		if (!el) {
			el = (document.querySelector) ?
				document.querySelector(name) :
				$(selector, parent)[0];
		}
		
		return el;
	},
	
	tags: {
		ref: function(el, ref, options) {
			var id = options && options.refId;
			this.addReference(el, ref, id);
		},
		appendTo: function(el, name) {
			var targetEl = Backbone.Template.getEl(this, name);
			targetEl && targetEl.appendChild(el);
		},
		prependTo: function() {
			
		},
		
		before: function( el, name ) {
			var targetEl = Backbone.Template.getEl(this, name);
			targetEl.parentNode.insertBefore( el, targetEl );
		}
	},
};

// after a view is defined: compile all its templates
Backbone.onDefine('view', function() {
	var proto = this.prototype, templates = proto.templates;
	
	if (templates && Backbone.unique(this, 'templates')) {
		// handle templates without options
		if (!templates.list || typeof templates.list == 'string') {
			templates = proto.templates = {
				list: templates,
				groups: [], $: []
			};
		}
		
		for (var name in templates.list) {
			var template = templates.list[name],
				Tmpl = Backbone.Template,
				el = document.getElementById(template + Tmpl.postfix),
				content = (el) ? el.innerHTML : template;
			
			templates.list[name] = Tmpl.engines[Tmpl.engine].compile(content);
		}
		
		var parent = proto.constructor.__super__.templates;
		if (parent) {
			templates.groups = parent.groups.concat(templates.groups);
			templates.$ = parent.$.concat(templates.$);
		}
		
		proto._templateList = templates.list;
		Backbone.combine(this, '_templateList');
		templates.list = proto._templateList;
	}
	return;
	if ( this.prototype.templates && this.prototype.templates != this.prototype.constructor.__super__.templates ) {
		var newGroups = [];
		// compile all the new templates
		_.each(this.prototype.templates, function(template, name) {
			var el = document.getElementById( template + '-template' ),
				content = (el) ? el.innerHTML : template,
				Tmpl = Backbone.Template;
			
			if ( el.hasAttribute('groups') ) {
				var groups = el.getAttribute('groups').split(',');
				for ( var i = 0, group; (group = groups[i]); i++ ) {
					groups[i] = $.trim(group);
				}
				
				newGroups = newGroups.concat(groups);
			}
			
			this.prototype.templates[name] = Tmpl.engines[Tmpl.engine].compile(content);
		}, this);
		
		var oldGroups = this.prototype.constructor.__super__.template_groups;
		
		if ( oldGroups ) {
			newGroups = oldGroups.concat( newGroups );
		}
		
		this.prototype.template_groups = newGroups;
		
		//Backbone.combine(this, 'templates');
		this.prototype.templates = _.extend( this.prototype.templates, this.prototype.constructor.__super__.templates );
	}
});

Backbone.beforeInit('view', function() {
	if (this.templates) {
		for (var i = 0, group; (group = this.templates.groups[i]); i++) {
			group = $.camelCase(group);
			this[group] = [];
			
			if (_.indexOf(this.templates.$, group) != -1) {
				this['$' + group] = $();
			}
		}
	}
});

Backbone.View.prototype.template = function(name, data, options) {
	if (!this.templates.list[name]) {
		throw name + ' template is not defined on this view';
	}
	
	// if there is no passed data use the models data or use the view itself
	data = data || (this.model && this.model.toJSON()) || this;
	
	var template = this.templates.list[name],
		container = document.createElement('div'), templateEl,
		// run the engine's create function or use the default one
		Tmpl = Backbone.Template,
		html = (Tmpl.engines[Tmpl.engine].create || Tmpl.create)(template, data);
	
	container.innerHTML = html;
	templateEl = (container.children) ? container.children[0] : $_(container).children().get();
	
	for (var i = 0, auto; (auto = Tmpl.autos[i]); i++) {
		var selector = auto[0], View = auto[1],
			autoEls = (container.querySelectorAll) ? container.querySelectorAll(selector) : $(selector, container).get();
		
		for (var j = 0, autoEl; (autoEl = autoEls[j]); j++) {
			new View({ el: autoEl, parent: this });
		}
	}
	
	for (var tag in Tmpl.tags) {
		if (html.indexOf(tag) == -1) {
			continue;
		}
		var selector = '*[' + tag + ']',
			attrEls = (container.querySelectorAll) ? container.querySelectorAll(selector) : $(selector, container).get();
		
		for (var i = 0, attrEl; (attrEl = attrEls[i]); i++) {
			var value = attrEl.getAttribute(tag);
			Tmpl.tags[tag].call(this, attrEl, value, options);
			attrEl.removeAttribute(tag);
		}
	}
	
	if (name == 'el') {
		if (this.el && this.el.parentNode) {
			this.el.parentNode.replaceChild(templateEl, this.el);
		}
		
		this.el = templateEl;
		if (_.indexOf(this.templates.$, 'el') != -1) {
			this.$el = $(this.el);
		}
		
		this.eventsDelegated = false;
		this.delegateEvents();
	}
	
	return templateEl;
}

Backbone.View.prototype.addReference = function(el, ref, id) {
	var group = _.indexOf(this.templates.groups, ref) != -1;
		use$ = _.indexOf(this.templates.$, ref) != -1;
	
	ref = $.camelCase(ref);
			
	if (!group) {
		this[ref] = el;
		
		if (use$) {
			this['$' + ref] = $(el);
		}
	} else {
		var els = (!use$) ? this[ref] : this['$' + ref],
			i = id || els.length++;
		
		els[i] = el;
	}
}
