/**
 * jQuery.Listen
 * Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date: 2/22/2008
 *
 * @projectDescription Light and fast event handling, using event delegation.
 * http://flesler.blogspot.com/2007/10/jquerylisten.html
 * Requires jQuery 1.2.3 or higher. Tested on FF 2|IE 6|Safari 3|Opera 9, Windows XP.
 *
 * @author Ariel Flesler
 * @version 1.0.2
 *
 * @id jQuery.listen
 * @param {String} name Name of the event to listen (f.e: click, mouseover, etc).
 * @param {DOM Element} listener optional: The DOM element to listen from, the document element by default.
 * @param {String|Boolean} selector A simple selector in one of this formats: "#id", "tagname", ".class", or "tagname.class".
 * @param {Function} handler The event handler to register.
 *
 * Notes:
 *	-The selectors support is low in order to mantain scalability.
 *	  I consider these 4 options, the most useful and I believe they are enough for many cases.
 *	-This plugin can't handle non-bubbling events. It handles focus & blur thanks to the focusin/focusout approach.
 */
;(function( $ ){
	
	$.fn.indexer = function( name ){//allow public access to the indexers
		return this[0] && indexer( this[0], name ) || null;
	};
	$.indexer = function( name ){
		return indexer( document, name );	
	};

	var $event = $.event,
		$special = $event.special,
		$listen = $.listen = function( name, listener, selector, handler ){
			if( typeof listener != 'object' ){ //document is the default listener
				handler = selector;
				selector = listener;
				listener = document;
			}
			each( name.split(/\s+/), function( ev ){
				ev = $listen.fixes[ev] || ev;//try to use a fixed event.
				var idxer = indexer( listener, ev ) || indexer( listener, ev, new Indexer(ev,listener) );
	
				idxer.append( selector, handler );// register the handler.
				idxer.start();
			});
		},
		indexer = function( elem, name, val ){
			return $.data( elem, name+'.indexer', val );	
		};

	$listen.regex = /^((?:\w*?|\*))(?:([#.])([\w-]+))?$/; //matches "#id", "tag", ".class" or "tag.class", also "tag#id" but the tag is ignored.
	$listen.fixes = {
		focus:'focusin',
		blur:'focusout'	
	};	

	//taken and adapted from http://dev.jquery.com/browser/trunk/plugins/validate/lib/jquery.delegate.js?rev=4374	
	$.each( $listen.fixes, function( original, fix ){
		$special[fix] = {
			setup:function() { 
                if ( $.browser.msie ) return false; 
                this.addEventListener( original, $special[fix].handler, true );
            }, 
            teardown:function() { 
                if ( $.browser.msie ) return false; 
                this.removeEventListener( original, $special[fix].handler, true ); 
            }, 
            handler: function(e) { 
                arguments[0] = e = $event.fix(e);
                e.type = fix;
                return $event.handle.apply(this, arguments); 
            }
		};
	});

	$.fn.listen = function( name, selector, handler ){//listen using the prototype
		return this.each(function(){
			$listen( name, this, selector, handler );						  
		});
	};	

	function Indexer( name, listener ){
		$.extend( this, {
			ids: {},
			tags: {},
			listener: listener,
			event: name
		});
		Indexer.instances.push( this );//for cleaning up later
	};
	Indexer.instances = [ ];

	Indexer.prototype = {
		constructor: Indexer,
		handle:function( e ){
			var sp = e.stopPropagation;//intercept any call to stopPropagation
			e.stopPropagation = function(){
				e.stopped = true;
				sp.apply(this,arguments);	
			};
			indexer(this,e.type).parse( e );			
		},
		on:false,
		bubbles:false,
		start:function(){//start listening (bind)
			if( !this.on ){ //avoid duplicates
				$event.add( this.listener, this.event, this.handle );
				this.on = true;
			}
		},
		stop:function(){//stop listening (unbind)
			if( this.on ){
				$event.remove( this.listener, this.event, this.handle );
				this.on = false;
			}
		},
		parse:function( e ){
			var node = e.data || e.target,
				handlers = [ ],
				args = arguments;

			if( node.id && this.ids[node.id] )//if this node has an id and there are handlers registered to it..
				push( handlers, this.ids[node.id] );

			each([ node.nodeName, '*' ], function( tag ){//look for handlers registered by name.class.
				var klasses = this.tags[tag];
				if( klasses )
					each( (node.className + ' *').split(' '), function( klass ){
						if( klass && klasses[klass] )
							push( handlers, klasses[klass] );//append the handlers to the list.
					});
			}, this);
			
			if( handlers[0] )
				each( handlers, function( handler ){
					if( handler.apply(node, args) === false ){
						e.preventDefault();
						e.stopPropagation();	
					}
				});

			if( !e.stopped && (node = node.parentNode) && (node.nodeName == 'A' || this.bubbles && node != this.listener) ){//go up ?
				e.data = node;//I rather not alter e.target, it might be used.
				this.parse( e );
			}			
		},
		append:function( selector, handler ){
			var match = $listen.regex.exec( selector );
			if( !match )
				throw '$.listen > "' + selector + '" is not a supported selector.';
			var 
				id = match[2] == '#' && match[3],
				tag = match[1].toUpperCase() || '*',
				klass =	match[3] || '*';
			if( id )//we have an id, register the handler to it.
				(this.ids[id] || (this.ids[id] = [ ])).push( handler );
			else if( tag ){//we have an name and/or class
				tag = this.tags[tag] = this.tags[tag] || { };
				(tag[klass] || (tag[klass] = [ ])).push( handler );
			}
		}
	};

	function each( arr, fn, scope ){
		for(var i=0, l=arr.length; i < l; i++ )
			fn.call( scope, arr[i], i );
	};
	function push( arr, elems ){
		arr.push.apply( arr, elems );
		return arr;
	};

	$(window).unload(function(){// cleanup
		if( typeof Indexer == 'function' ) 
			each( Indexer.instances, function(idxer){
				idxer.stop();
				$.removeData( idxer.listener, idxer.event + '.indexer' );
				idxer.names = idxer.listener = null;
			});
	});

})( jQuery );