/*! Copyright © 2008 Brandon Aaron (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);