/*
     BEGIN FLOCK GPL

     Copyright Flock Inc. 2005-2009
     http://flock.com

     This file may be used under the terms of the
     GNU General Public License Version 2 or later (the "GPL"),
     http://www.gnu.org/licenses/gpl.html

     Software distributed under the License is distributed on an "AS IS" basis,
     WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
     for the specific language governing rights and limitations under the
     License.

     END FLOCK GPL
*/


/* in this world, a stream is a generator */

var PQ = function(compare) {
  this.compare = compare;
  this.data = new Array();
  this.size = 0;
}

PQ.prototype.down = function(parent) {
  var element = this.data[parent];
  var child = parent<<1;
  while (child <= this.size) { 
    
    if ((child < this.size) && 
        (this.compare(this.data[child+1],this.data[child]) > 0)) child++

    if (this.compare(element, this.data[child]) < 0) {
      this.data[parent] = this.data[child];
      parent = child;
      child = parent<<1;
    } else break;
      
  }
  this.data[parent] = element;
}
    
PQ.prototype.up = function(child) {
  var element = this.data[child];
  var parent = child>>1;
  while (parent >= 1) {
    if (this.compare(this.data[parent], element) < 0) {
      this.data[child] = this.data[parent]; 
      child = parent; 
      parent = child>>1;
    } else break;
  }
  this.data[child] = element;
}

PQ.prototype.push = function(val) {
  this.size++;
  this.data[this.size] = val;
  this.up(this.size)
}

PQ.prototype.pop = function() {
  var val = this.data[1];
  this.data[1] = this.data[this.size];
  this.size--;
  this.down(1)
  this.data.pop()
  return val;
}


function filterStream (stream, filter) {
  var next = null;
  function fillNext () {
    while (stream.hasMoreElements ()) {
      var item = stream.getNext ();
      if (!filter (item)) {
        continue;
      }
      next = item;
      return;
    }
    next = null;
  }
  return {
    hasMoreElements: function filterStream_hasMoreElements () {
      if (next) return true;
      fillNext ();
      return (next != null);
    },
    getNext: function filterStream_getNext () {
      if (!next) {
        fillNext ();
      }

      var r = next;
      next = null;
      return r;
    }
  }
}

function groupAggStreams( rdfStreamNodes, faves_coop ) {

  // this function creates two aggregations aggSeen, aggUnseen
  // filtering unseen and seen respectively from their output
  // then wraps the aggregators so all of aggUnseen is consumed
  // before aggSeen is consumed
  
  var streamsUnseen = []; // the enumerators that will be consumed by the
                          // aggregator that show only the unseen items
  var streamsSeen   = []; // enumerators used to show seen items

  for (var i in rdfStreamNodes) {
    streamsUnseen[i] = faves_coop.get(rdfStreamNodes[i]).children.enumerateBackwards();
    streamsSeen[i] = faves_coop.get(rdfStreamNodes[i]).children.enumerateBackwards();
  }

  var aggSeen = filterStream( aggregateStreams( streamsSeen ), function(item) {
      return !item.unseen;
    });

  var aggUnseen = filterStream( aggregateStreams( streamsUnseen ), function(item) {
      return item.unseen;
    });

  return {
    hasMoreElements: function groupStream_hasMoreElements () {
      return aggUnseen.hasMoreElements() || aggSeen.hasMoreElements();
    },
    getNext: function groupStream_getNext () {
      return aggUnseen.getNext() || aggSeen.getNext();
    }
  }
}

function fancyPantsEnumer( node, coop ) {
  const flockIStreamUtils = Components.interfaces.flockIStreamUtils;
  const SU = Components.classes['@flock.com/stream-utils;1']
                       .createInstance(flockIStreamUtils);

  var se = SU.enumerateStreams(coop.datasource, coop.get(node).resource(), true,
                               flockIStreamUtils.STREAMS_UNSEEN |
                               flockIStreamUtils.STREAMS_SEEN);

  var enumerator = {
    _coop: coop,
    _se: se,
    getNext: function() {
      var n = this._se.getNext()
                      .QueryInterface(Components.interfaces.nsIRDFResource);
      return this._coop.get_from_resource(n);
    },
    hasMoreElements: function() {
      return this._se.hasMoreElements();
    }
  };

  return enumerator;
}

function aggStreams( rdfStreamNodes, faves_coop ) {
    
  var streams = [];

  for (var i in rdfStreamNodes) {
    streams[i] = fancyPantsEnumer( rdfStreamNodes[i], faves_coop );
  }
  
  return aggregateStreams(streams);
}

function aggregateStreams (streams) {
  var myFunc = function (x,y) { return x.element.datevalue - y.element.datevalue; };
  return customAggregateStreams(myFunc, streams);
}

function aggregatePhotoStreams (streams) {
  // var myFunc = function (x,y) { return x.element.id - y.element.id; };
  // return customAggregateStreams(myFunc, streams);
  return aggregateStreams(streams);
}

function customAggregateStreams (myFunc, streams) {
  var pq = new PQ (myFunc);

  // initialize the priority queue
  for (var i in streams) {
    if (!streams[i].hasMoreElements ()) {
      continue;
    }
    pq.push ({element: streams[i].getNext(), stream: streams[i]});
  }

  // return an enumerator
  return {
    hasMoreElements: function aggregateStreams_hasMoreElements () {  
      return pq.size > 0;
    },
    getNext: function aggregateStreams_getNext () {
      // get the next item
      var item = pq.pop();
      if (!item) {
        // the queue is empty
        return null;
      }

      if (item.stream.hasMoreElements ()) {
        // add the next item from that stream to the queue
        pq.push ({element: item.stream.getNext(), stream: item.stream});
      }

      // return our element
      return item.element;
    }
  }
}


// streamWalker walks the RDF graph collecting a list of streams 
// it starts at a node, and recurses adding items with rdf type
// "livemarks"

function streamWalker(aDataSource, aNode) {
  const RDFS = Components.classes['@mozilla.org/rdf/rdf-service;1']
                         .getService(Components.interfaces.nsIRDFService);
  const RDFCU = Components.classes['@mozilla.org/rdf/container-utils;1']
                          .getService(Components.interfaces.nsIRDFContainerUtils);
  const RDF_TYPE = RDFS.GetResource('http://www.w3.org/1999/02/22-rdf-syntax-ns#type');
  const RDF_LIVEMARK = RDFS.GetResource('http://home.netscape.com/NC-rdf#Livemark');  // rdf type livemark == flock stream

  var streams = {};    // streams contained within aNode (using a hash to remove duplicates)
  var queue = [aNode];   // removing tail recursion using a queue

  while (queue.length > 0) {
    aNode = queue.pop();
    aNode.QueryInterface( Components.interfaces.nsIRDFResource );
    
    if (aDataSource.HasAssertion( aNode, RDF_TYPE, RDF_LIVEMARK, true )) {
      streams[ aNode.ValueUTF8 ] = 1; 
    } else {
      // sequences that aren't livemarks should be recursed into
      // (eg, streams inside folders/people/containers should be added)
      if (RDFCU.IsSeq (aDataSource, aNode)) {
        try {
          var children = RDFCU.MakeSeq( aDataSource, aNode ).GetElements();
          while (children && children.hasMoreElements()) {
            child = children.getNext();
            queue.push( child ); 
          }
        } catch (e) {}
      }
    }
  }
  
  return [i for (i in streams)]; // wonder if this javascript 1.7 feature works?
}

