// Based on http://en.wikipedia.org/wiki/Binary_Heap
// as well as http://eloquentjavascript.net/appendix2.html
module.exports = BinaryHeap;
function BinaryHeap(options) {
  options = options || {};

  this._elements = options.elements || [];
  this._score    = options.score || this._score;
}

BinaryHeap.prototype.add = function(/* elements */) {
  for (var i = 0; i < arguments.length; i++) {
    var element = arguments[i];

    this._elements.push(element);
    this._bubble(this._elements.length - 1);
  }
};

BinaryHeap.prototype.first = function() {
  return this._elements[0];
};

BinaryHeap.prototype.removeFirst = function() {
  var root = this._elements[0];
  var last = this._elements.pop();

  if (this._elements.length > 0) {
    this._elements[0] = last;
    this._sink(0);
  }

  return root;
};

BinaryHeap.prototype.clone = function() {
  return new BinaryHeap({
    elements: this.toArray(),
    score: this._score,
  });
};

BinaryHeap.prototype.toSortedArray = function() {
  var array = [];
  var clone = this.clone();

  while (true) {
    var element = clone.removeFirst();
    if (element === undefined) break;

    array.push(element);
  }

  return array;
};

BinaryHeap.prototype.toArray = function() {
  return [].concat(this._elements);
};

BinaryHeap.prototype.size = function() {
  return this._elements.length;
};

BinaryHeap.prototype._bubble = function(bubbleIndex) {
  var bubbleElement = this._elements[bubbleIndex];
  var bubbleScore   = this._score(bubbleElement);

  while (bubbleIndex > 0) {
    var parentIndex   = this._parentIndex(bubbleIndex);
    var parentElement = this._elements[parentIndex];
    var parentScore   = this._score(parentElement);

    if (bubbleScore <= parentScore) break;

    this._elements[parentIndex] = bubbleElement;
    this._elements[bubbleIndex]  = parentElement;
    bubbleIndex                  = parentIndex;
  }
};

BinaryHeap.prototype._sink = function(sinkIndex) {
  var sinkElement = this._elements[sinkIndex];
  var sinkScore   = this._score(sinkElement);
  var length      = this._elements.length;

  while (true) {
    var swapIndex    = null;
    var swapScore    = null;
    var swapElement  = null;
    var childIndexes = this._childIndexes(sinkIndex);

    for (var i = 0; i < childIndexes.length; i++) {
      var childIndex   = childIndexes[i];

      if (childIndex >= length) break;

      var childElement = this._elements[childIndex];
      var childScore   = this._score(childElement);

      if (childScore > sinkScore) {
        if (swapScore === null || swapScore < childScore) {
          swapIndex   = childIndex;
          swapScore   = childScore;
          swapElement = childElement;
        }
      }
    }

    if (swapIndex === null) break;

    this._elements[swapIndex] = sinkElement;
    this._elements[sinkIndex] = swapElement;
    sinkIndex = swapIndex;
  }
};

BinaryHeap.prototype._parentIndex = function(index) {
  return Math.floor((index - 1) / 2);
};

BinaryHeap.prototype._childIndexes = function(index) {
  return [
    2 * index + 1,
    2 * index + 2,
  ];
  return ;
};

BinaryHeap.prototype._score = function(element) {
  return element.valueOf();
};
