/*! Copyright (c) 2009 Brandon Aaron (http://brandonaaron.net)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Version: 1.1.0-pre
 * Requires jQuery 1.3+
 * Docs: http://docs.jquery.com/Plugins/livequery
 */

(function($) {

	$.extend($.fn, {
		livequery: function(type, fn, fn2) {
		var self = this, q;

//		Handle different call patterns
		if ($.isFunction(type))
			fn2 = fn, fn = type, type = undefined;

//		See if Live Query already exists
		$.each( $.livequery.queries, function(i, query) {
			if ( self.selector == query.selector && self.context == query.context &&
					type == query.type && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) )
//				Found the query, exit the each loop
				return (q = query) && false;
		});

//		Create new Live Query if it wasn't found
		q = q || new $.livequery(this.selector, this.context, type, fn, fn2);

//		Make sure it is running
		q.stopped = false;

//		Run it immediately for the first time
		q.run();

//		Contnue the chain
		return this;
	},

	expire: function(type, fn, fn2) {
		var self = this;

//		Handle different call patterns
		if ($.isFunction(type))
			fn2 = fn, fn = type, type = undefined;

//		Find the Live Query based on arguments and stop it
		$.each( $.livequery.queries, function(i, query) {
			if ( self.selector == query.selector && self.context == query.context &&
					(!type || type == query.type) && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) && !this.stopped )
				$.livequery.stop(query.id);
		});

//		Continue the chain
		return this;
	}
	});

	$.livequery = function(selector, context, type, fn, fn2) {
		this.selector = selector;
		this.context = context;
		this.type = type;
		this.fn = fn;
		this.fn2 = fn2;
		this.elements = [];
		this.stopped = false;

//		The id is the index of the Live Query in $.livequery.queries
		this.id = $.livequery.queries.push(this)-1;

//		Mark the functions for matching later on
		fn.$lqguid = fn.$lqguid || $.livequery.guid++;
		if (fn2) fn2.$lqguid = fn2.$lqguid || $.livequery.guid++;

//		Return the Live Query
		return this;
	};

	$.livequery.prototype = {
			stop: function() {
		var query = this;

		if ( this.type )
//			Unbind all bound events
			this.elements.unbind(this.type, this.fn);
		else if (this.fn2)
//			Call the second function for all matched elements
			this.elements.each(function(i, el) {
				query.fn2.apply(el);
			});

//		Clear out matched elements
		this.elements = [];

//		Stop the Live Query from running until restarted
		this.stopped = true;
	},

	run: function() {
		// Short-circuit if stopped
		if ( this.stopped ) return;
		var query = this;

		var oEls = this.elements,
		els = $(this.selector, this.context),
		nEls = els.not(oEls);

//		Set elements to the latest set of matched elements
		this.elements = els;

		if (this.type) {
			// Bind events to newly matched elements
			nEls.bind(this.type, this.fn);

//			Unbind events to elements no longer matched
			if (oEls.length > 0)
				$.each(oEls, function(i, el) {
					if ( $.inArray(el, els) < 0 )
						$.event.remove(el, query.type, query.fn);
				});
		}
		else {
			// Call the first function for newly matched elements
			nEls.each(function() {
				query.fn.apply(this);
			});

//			Call the second function for elements no longer matched
			if ( this.fn2 && oEls.length > 0 )
				$.each(oEls, function(i, el) {
					if ( $.inArray(el, els) < 0 )
						query.fn2.apply(el);
				});
		}
	}
	};

	$.extend($.livequery, {
		guid: 0,
		queries: [],
		queue: [],
		running: false,
		timeout: null,

		checkQueue: function() {
		if ( $.livequery.running && $.livequery.queue.length ) {
			var length = $.livequery.queue.length;
//			Run each Live Query currently in the queue
			while ( length-- )
				$.livequery.queries[ $.livequery.queue.shift() ].run();
		}
	},

	pause: function() {
		// Don't run anymore Live Queries until restarted
		$.livequery.running = false;
	},

	play: function() {
		// Restart Live Queries
		$.livequery.running = true;
//		Request a run of the Live Queries
		$.livequery.run();
	},

	registerPlugin: function() {
		$.each( arguments, function(i,n) {
			// Short-circuit if the method doesn't exist
			if (!$.fn[n]) return;

//			Save a reference to the original method
			var old = $.fn[n];

//			Create a new method
			$.fn[n] = function() {
				// Call the original method
				var r = old.apply(this, arguments);

//				Request a run of the Live Queries
				$.livequery.run();

//				Return the original methods result
				return r;
			}
		});
	},

	run: function(id) {
		if (id != undefined) {
			// Put the particular Live Query in the queue if it doesn't already exist
			if ( $.inArray(id, $.livequery.queue) < 0 )
				$.livequery.queue.push( id );
		}
		else
//			Put each Live Query in the queue if it doesn't already exist
			$.each( $.livequery.queries, function(id) {
				if ( $.inArray(id, $.livequery.queue) < 0 )
					$.livequery.queue.push( id );
			});

//		Clear timeout if it already exists
		if ($.livequery.timeout) clearTimeout($.livequery.timeout);
//		Create a timeout to check the queue and actually run the Live Queries
		$.livequery.timeout = setTimeout($.livequery.checkQueue, 20);
	},

	stop: function(id) {
		if (id != undefined)
//			Stop are particular Live Query
			$.livequery.queries[ id ].stop();
		else
//			Stop all Live Queries
			$.each( $.livequery.queries, function(id) {
				$.livequery.queries[ id ].stop();
			});
	}
	});

//	Register core DOM manipulation methods
	$.livequery.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove');

//	Run Live Queries when the Document is ready
	$(function() { $.livequery.play(); });

})(jQuery);
