(function($) {	

	/** Taken from jQuery 1.5.2 for backwards compatibility */
	if ($.support.changeBubbles == undefined) {
		$.support.changeBubbles = true;

		var el = document.createElement("div");
		eventName = "onchange";

		if (el.attachEvent) {
			var isSupported = (eventName in el);
			if (!isSupported) {
				el.setAttribute(eventName, "return;");
				isSupported = typeof el[eventName] === "function";
			}

			$.support.changeBubbles = isSupported;
		}
	}

	/* Return true if node b is the same as, or is a descendant of, node a */
	if (document.compareDocumentPosition) {
		var is_or_contains = function(a, b) {
			return a && b && (a == b || !!(a.compareDocumentPosition(b) & 16));
		};
	}
	else {
		var is_or_contains = function(a, b) {
			return a && b && (a == b || (a.contains ? a.contains(b) : true));
		};
	}

	/* Add the methods to handle event binding to the Namespace class */
	$.entwine.Namespace.addMethods({
		build_event_proxy: function(name) {
			var one = this.one(name, 'func');
			
			var prxy = function(e, data) {
				// For events that do not bubble we manually trigger delegation (see delegate_submit below) 
				// If this event is a manual trigger, the event we actually want to bubble is attached as a property of the passed event
				e = e.delegatedEvent || e;
				
				var el = e.target;
				while (el && el.nodeType == 1 && !e.isPropagationStopped()) {
					var ret = one(el, arguments);
					if (ret !== undefined) e.result = ret;
					if (ret === false) { e.preventDefault(); e.stopPropagation(); }
					
					el = el.parentNode;
				}
			};
			
			return prxy;
		},
		
		build_mouseenterleave_proxy: function(name) {
			var one = this.one(name, 'func');
			
			var prxy = function(e) {
				var el = e.target;
				var rel = e.relatedTarget;
				
				while (el && el.nodeType == 1 && !e.isPropagationStopped()) {
					/* We know el contained target. If it also contains relatedTarget then we didn't mouseenter / leave. What's more, every ancestor will also
					contan el and rel, and so we can just stop bubbling */
					if (is_or_contains(el, rel)) break;
					
					var ret = one(el, arguments);
					if (ret !== undefined) e.result = ret;
					if (ret === false) { e.preventDefault(); e.stopPropagation(); }
					
					el = el.parentNode;
				}
			};
			
			return prxy;
		},
		
		build_change_proxy: function(name) {
			var one = this.one(name, 'func');

			/*
			This change bubble emulation code is taken mostly from jQuery 1.6 - unfortunately we can't easily reuse any of
			it without duplication, so we'll have to re-migrate any bugfixes
			*/

			// Get the value of an item. Isn't supposed to be interpretable, just stable for some value, and different
			// once the value changes
			var getVal = function( elem ) {
				var type = elem.type, val = elem.value;

				if (type === "radio" || type === "checkbox") {
					val = elem.checked;
				}
				else if (type === "select-multiple") {
					val = "";
					if (elem.selectedIndex > -1) {
						val = jQuery.map(elem.options, function(elem){ return elem.selected; }).join("-");
					}
				}
				else if (jQuery.nodeName(elem, "select")) {
					val = elem.selectedIndex;
				}

				return val;
			};

			// Test if a node name is a form input
			var rformElems = /^(?:textarea|input|select)$/i;

			// Check if this event is a change, and bubble the change event if it is
			var testChange = function(e) {
				var elem = e.target, data, val;

				if (!rformElems.test(elem.nodeName) || elem.readOnly) return;

				data = jQuery.data(elem, "_entwine_change_data");
				val = getVal(elem);

				// the current data will be also retrieved by beforeactivate
				if (e.type !== "focusout" || elem.type !== "radio") {
					jQuery.data(elem, "_entwine_change_data", val);
				}

				if (data === undefined || val === data) return;

				if (data != null || val) {
					e.type = "change";

					while (elem && elem.nodeType == 1 && !e.isPropagationStopped()) {
						var ret = one(elem, arguments);
						if (ret !== undefined) e.result = ret;
						if (ret === false) { e.preventDefault(); e.stopPropagation(); }

						elem = elem.parentNode;
					}
				}
			};

			// The actual proxy - responds to several events, some of which triger a change check, some
			// of which just store the value for future change checks
			var prxy = function(e) {
				var event = e.type, elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : "";

				switch (event) {
					case 'focusout':
					case 'beforedeactivate':
						testChange.apply(this, arguments);
						break;

					case 'click':
						if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) {
							testChange.apply(this, arguments);
						}
						break;

					// Change has to be called before submit
					// Keydown will be called before keypress, which is used in submit-event delegation
					case 'keydown':
						if (
							(e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) ||
							(e.keyCode === 32 && (type === "checkbox" || type === "radio")) ||
							type === "select-multiple"
						) {
							testChange.apply(this, arguments);
						}
						break;

					// Beforeactivate happens also before the previous element is blurred
					// with this event you can't trigger a change event, but you can store
					// information
					case 'focusin':
					case 'beforeactivate':
						jQuery.data( elem, "_entwine_change_data", getVal(elem) );
						break;
				}
			}

			return prxy;
		},
		
		bind_event: function(selector, name, func, event) {
			var funcs = this.store[name] || (this.store[name] = $.entwine.RuleList()) ;
			var proxies = funcs.proxies || (funcs.proxies = {});
			
			var rule = funcs.addRule(selector, name); rule.func = func;
			
			if (!proxies[name]) {
				switch (name) {
					case 'onmouseenter':
						proxies[name] = this.build_mouseenterleave_proxy(name);
						event = 'mouseover';
						break;
					case 'onmouseleave':
						proxies[name] = this.build_mouseenterleave_proxy(name);
						event = 'mouseout';
						break;
					case 'onchange':
						if (!$.support.changeBubbles) {
							proxies[name] = this.build_change_proxy(name);
							event = 'click keydown focusin focusout beforeactivate beforedeactivate';
						}
						break;
					case 'onsubmit':
						event = 'delegatedSubmit';
						break;
					case 'onfocus':
					case 'onblur':
						$.entwine.warn('Event '+event+' not supported - using focusin / focusout instead', $.entwine.WARN_LEVEL_IMPORTANT);
				}
				
				// If none of the special handlers created a proxy, use the generic proxy
				if (!proxies[name]) proxies[name] = this.build_event_proxy(name);

				$(document).bind(event.replace(/(\s+|$)/g, '.entwine$1'), proxies[name]);
			}
		}
	});
	
	$.entwine.Namespace.addHandler({
		order: 40,
		
		bind: function(selector, k, v){
			var match, event;
			if ($.isFunction(v) && (match = k.match(/^on(.*)/))) {
				event = match[1];
				this.bind_event(selector, k, v, event);
				return true;
			}
		}
	});
	
	// Find all forms and bind onsubmit to trigger on the document too. 
	// This is the only event that can't be grabbed via delegation
	
	var delegate_submit = function(e, data){
		var delegationEvent = $.Event('delegatedSubmit'); delegationEvent.delegatedEvent = e;
		return $(document).trigger(delegationEvent, data); 
	};

	$(document).bind('EntwineElementsAdded', function(e){
		var forms = $(e.targets).filter('form');
		if (!forms.length) return;

		forms.bind('submit.entwine_delegate_submit', delegate_submit);
	});

})(jQuery);