/** var $$ = pulp.nodeList.getInstance; */
pulp.Modules.nodeList = '$$';

(function() {

  var $p = pulp.base,
    slice = Array.prototype.slice;

  /**
   * @class  Return a collection of pulp.node objects to manipulate a set of DOM Elements
   * @name pulp.nodeList
   * @requires pulp.array
   * @requires pulp.node
   * @requires pulp.cssQuery
   * @extends pulp.node
   */
  var self = pulp.nodeList = pulp.array.createSubclass(/** @lends pulp.nodeList# */{
    /**
     * Define the selector and parent element of the nodeList
     * @param {String|Array|pulp.array} [selector]  A CSS string, Array or pulp.array object
     * @param {HTMLElement} [from=document]  The parent element of the node list
     */
    initialize: function(selector, from) {
      if (selector instanceof pulp.array) {
        this.setSesults(selector.raw);
      } 
      else if ($p.isArray(selector)) {
        this.setResults(selector);
      }
      else {
        this.selector = selector;
        this.context = from || this.context;
      }
    },
    /**
     * Load or reload the results of the selector into this object
     * @return {this}
     * @chainable
     */
    load: function() {
      if (this.selector) {
        this.setResults(pulp.cssQuery(this.selector, this.context));
      }
      return this;
    },
    /**
     * Check if a given element matches the internal selector
     * @param {HTMLElement} element
     * @return {Boolean}
     */
    include: function(element) {
      // TODO: verify that this approach is faster than pulp.array#include
      return pulp.cssQuery.match(element, this.selector);
    },
    setResults: function(iterable) {
      var i, len;
      i = len = iterable.length;
      this.raw = Array(i);
      while (i--) {
        this[i] = this.raw[i] = pulp.node.getInstance(iterable[i]);
      }
      this.length = len;
      // clear old properties if new result set is shorter
      i = len;
      while (this[i]) {
        delete this[i++];
      }
    }
  });
  
  self.aliasMethods(/** @lends pulp.nodeList# */{
    /**
     * @function  see {@link pulp.nodeList#initialize}
     */
    setQuery: 'initialize'
  });
  
  if ('event' in pulp) {
    self.extendPrototype(/** @lends pulp.nodeList# */{
      /**
       * Observe the parent and fire a handler if a child element matches the selector
       * @param {String} type  The event name
       * @param {Function} handler  The handler to call
       * @return {this}
       * @chainable
       */
      delegate: function(type, handler) {
        pulp.event.delegate(this.selector, type, handler, this.context);
        return this;
      },
      /**
       * Stop firing the given delegated event
       * @param {String} type  The event name
       * @param {Function} handler  The handler that was attached
       * @return {this}
       * @chainable
       */
      stopDelegating: function(type, handler) {
        pulp.event.stopDelegating(this.selector, type, handler, this.context);
        return this;
      }      
    });
  }

  /** @function pulp.nodeList#setStyle
   * Applies {@link pulp.node#setStyle} to every element in the result set
   */
  /** @function pulp.nodeList#setOpacity
   * Applies {@link pulp.node#setOpacity} to every element in the result set
   */
  /** @function pulp.nodeList#set
   * Applies {@link pulp.node#set} to every element in the result set
   */
  /** @function pulp.nodeList#addClass
   * Applies {@link pulp.node#addClass} to every element in the result set
   */
  /** @function pulp.nodeList#removeClass
   * Applies {@link pulp.node#removeClass} to every element in the result set
   */
  /** @function pulp.nodeList#toggleClass
   * Applies {@link pulp.node#toggleClass} to every element in the result set
   */
  /** @function pulp.nodeList#hide
   * Applies {@link pulp.node#hide} to every element in the result set
   */
  /** @function pulp.nodeList#show
   * Applies {@link pulp.node#show} to every element in the result set
   */
  /** @function pulp.nodeList#remove
   * Applies {@link pulp.node#remove} to every element in the result set
   */
  /** @function pulp.nodeList#replaceChild
   * Applies {@link pulp.node#replaceChild} to every element in the result set
   */
  /** @function pulp.nodeList#update
   * Applies {@link pulp.node#update} to every element in the result set
   */
  /** @function pulp.nodeList#removeAttribute
   * Applies {@link pulp.node#removeAttribute} to every element in the result set
   */
  /** @function pulp.nodeList#setAttribute
   * Applies {@link pulp.node#setAttribute} to every element in the result set
   */
  /** @function pulp.nodeList#prependChild
   * Applies {@link pulp.node#prependChild} to every element in the result set
   */
  /** @function pulp.nodeList#appendChild
   * Applies {@link pulp.node#appendChild} to every element in the result set
   */
  /** @function pulp.nodeList#insertBefore
   * Applies {@link pulp.node#insertBefore} to every element in the result set
   */
  /** @function pulp.nodeList#insertAfter
   * Applies {@link pulp.node#insertAfter} to every element in the result set
   */
  /** @function pulp.nodeList#wrap
   * Applies {@link pulp.node#wrap} to every element in the result set
   */
  /** @function pulp.nodeList#wrapInner
   * Applies {@link pulp.node#wrapInner} to every element in the result set
   */
  /** @function pulp.nodeList#fire
   * Applies {@link pulp.node#fire} to every element in the result set (available only when pulp.event is loaded)
   */
  /** @function pulp.nodeList#observe
   * Applies {@link pulp.node#observe} to every element in the result set (available only when pulp.event is loaded)
   */
  /** @function pulp.nodeList#stopObserving
   * Applies {@link pulp.node#stopObserving} to every element in the result set (available only when pulp.event is loaded)
   */
  // pulp.node methods that can be called for every element in the node list
  var applyAll = 'setStyle setOpacity set addClass removeClass toggleClass hide show remove replaceChild update ' +
    'removeAttribute setAttribute prependChild appendChild insertBefore insertAfter wrap wrapInner';
  if ('event' in pulp) {
    applyAll += ' fire observe stopObserving';
  }
  
  $p.each(applyAll, function(method) {    
    self.prototype[method] = /** @ignore */ function() {
      for (var i = 0, len = this.raw.length; i < len; i++) {
        pulp.node.prototype[method].apply(this.raw[i], slice.call(arguments));
      }
      return this;
    };
  });
  
  /** @function pulp.nodeList#getNextSibling
   * Returns an array with the results of {@link pulp.node#getNextSibling} called on every element
   */
  /** @function pulp.nodeList#getNextSiblings
   * Returns an array with the concatenation of results of {@link pulp.node#getNextSiblings} called on every element
   */
  /** @function pulp.nodeList#getPreviousSibling
   * Returns an array with the results of {@link pulp.node#getPreviousSibling} called on every element
   */
  /** @function pulp.nodeList#getPreviousSiblings
   * Returns an array with the concatenation of results of {@link pulp.node#getPreviousSiblings} called on every element
   */
  /** @function pulp.nodeList#getSiblings
   * Returns an array with the concatenation of results of {@link pulp.node#getSiblings} called on every element
   */
  /** @function pulp.nodeList#getChildren
   * Returns an array with the concatenation of results of {@link pulp.node#getChildren} called on every element
   */
  /** @function pulp.nodeList#getElementsByTagName
   * Returns an array with the concatenation of results of {@link pulp.node#getElementsByTagName} called on every element
   */
  /** @function pulp.nodeList#getElementsByClassName
   * Returns an array with the concatenation of results of {@link pulp.node#getElementsByClassName} called on every element
   */
  /** @function pulp.nodeList#up
   * Returns an array with the results of {@link pulp.node#up} called on every element
   */
  /** @function pulp.nodeList#down
   * Returns an array with the results of {@link pulp.node#down} called on every element
   */
  /** @function pulp.nodeList#select
   * Returns an array with the concatenation of results of {@link pulp.node#select} called on every element
   */
  /** @function pulp.nodeList#cloneNode
   * Returns an array with the results of {@link pulp.node#cloneNode} called on every element
   */
  // pulp.node methods that return an array should return a concatenation of nodeList objects
  var nodeListReturners = 'getNextSibling getNextSiblings getPreviousSibling getPreviousSiblings getSiblings ' +
    'getChildren getElementsByTagName getElementsByClassName getAncestors up down select cloneNode';
  $p.each(nodeListReturners, function(method) {
    self.prototype[method] = (function(fnName) {
      return function() {
        var results = [], ret;
        for (var i = 0, len = this.raw.length; i < len; i++) {
          ret = pulp.node.prototype[fnName].apply(this.raw[i], slice.call(arguments));
          if ($p.isArray(ret)) {
            results.concat(ret);
          } else if (ret) {
            results.push(ret);
          }
        }
        new self(arrayUnique(results));
      };
    })(method);
  });    
  
  // pulp.node methods that make sense to be applied only to the first member of the collection
  var applyFirst = 'isDescendentOf identify getDimensions getHeight getWidth ' +
    'getAttribute hasAttribute hasClassName match offsetParent';
  if ('event' in pulp) {
    applyFirst += ' listHandlers';
  }
  $p.each(applyFirst, function(method) {
    self.prototype[method] = (function(fnName) {
      return function() {
        if (this.raw[0]) {
          return pulp.node.prototype[fnName].apply(this.raw[0], slice.call(arguments));
        }
        return undefined;
      };
    })(method);
  });  
    
  /**
   * Create a pulp.nodeList object, set it's properties, load results, and return the object
   * @name pulp.nodeList.getInstance
   */
  self.getInstance = function(selector, from) {
    return new self(selector, from).load();
  };
  
  function arrayUnique(a) {
    var results = [], r = 0;
    for (var i = 0, len = a.length; i < len; i++) {
      if (!$p.inArray(results, a[i])) {
        results[r++] = a[i];
      }
    }
    return results;
  }	
    
})();
