/*! Copyright (c) 2010 Brandon Aaron (http://brandonaaron.net)
* Dual licensed under the MIT (MIT_LICENSE.txt)
* and GPL Version 2 (GPL_LICENSE.txt) licenses.
*
* Version: 1.1.1
* 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', 'html');

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

})(jQuery);