/*! Copyright (c) 2008 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.0.3
 * Requires jQuery 1.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 || document;
		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();
	});


	// Save a reference to the original init method
	var init = $.prototype.init;

	// Create a new init method that exposes two new properties: selector and context
	$.prototype.init = function(a,c) {
		// Call the original init and save the result
		var r = init.apply(this, arguments);

		// Copy over properties if they exist already
		if (a && a.selector)
			r.context = a.context, r.selector = a.selector;

		// Set properties
		if ( typeof a == 'string' )
			r.context = c || document, r.selector = a;

		// Return the result
		return r;
	};

	// Give the init function the jQuery prototype for later instantiation (needed after Rev 4091)
	$.prototype.init.prototype = $.prototype;

})(jQuery);
