/* Copyright (c) 2007 Brandon Aaron (brandon.aaron@gmail.com || 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.2
 * 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
                $.livequery.run( q.id );
                
                // 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);
