/** 
 * 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);
;

