"use strict";
module.exports = FilteredElementList;

var Node = require('./Node');

//
// This file defines node list implementation that lazily traverses
// the document tree (or a subtree rooted at any element) and includes
// only those elements for which a specified filter function returns true.
// It is used to implement the
// {Document,Element}.getElementsBy{TagName,ClassName}{,NS} methods.
//
// XXX this should inherit from NodeList

function FilteredElementList(root, filter) {
  this.root = root;
  this.filter = filter;
  this.lastModTime = root.lastModTime;
  this.done = false;
  this.cache = [];
  this.traverse();
}

FilteredElementList.prototype = Object.create(Object.prototype, {
  length: { get: function() {
    this.checkcache();
    if (!this.done) this.traverse();
    return this.cache.length;
  } },

  item: { value: function(n) {
    this.checkcache();
    if (!this.done && n >= this.cache.length) {
      // This can lead to O(N^2) behavior if we stop when we get to n
      // and the caller is iterating through the items in order; so
      // be sure to do the full traverse here.
      this.traverse(/*n*/);
    }
    return this.cache[n];
  } },

  checkcache: { value: function() {
    if (this.lastModTime !== this.root.lastModTime) {
      // subtree has changed, so invalidate cache
      for (var i = this.cache.length-1; i>=0; i--) {
        this[i] = undefined;
      }
      this.cache.length = 0;
      this.done = false;
      this.lastModTime = this.root.lastModTime;
    }
  } },

  // If n is specified, then traverse the tree until we've found the nth
  // item (or until we've found all items).  If n is not specified,
  // traverse until we've found all items.
  traverse: { value: function(n) {
    // increment n so we can compare to length, and so it is never falsy
    if (n !== undefined) n++;

    var elt;
    while ((elt = this.next()) !== null) {
      this[this.cache.length] = elt; //XXX Use proxy instead
      this.cache.push(elt);
      if (n && this.cache.length === n) return;
    }

    // no next element, so we've found everything
    this.done = true;
  } },

  // Return the next element under root that matches filter
  next: { value: function() {
    var start = (this.cache.length === 0) ? this.root // Start at the root or at
      : this.cache[this.cache.length-1]; // the last element we found

    var elt;
    if (start.nodeType === Node.DOCUMENT_NODE)
      elt = start.documentElement;
    else
      elt = start.nextElement(this.root);

    while(elt) {
      if (this.filter(elt)) {
        return elt;
      }

      elt = elt.nextElement(this.root);
    }
    return null;
  } },
});
