/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef ds_PriorityQueue_h
#define ds_PriorityQueue_h

#include "js/Vector.h"

namespace js {

/*
 * Class which represents a heap based priority queue using a vector.
 * Inserting elements and removing the highest priority one are both O(log n).
 *
 * Template parameters are the same as for Vector, with the addition that P
 * must have a static higherPriority(const T& a, const T& b) method which
 * returns true if |a| has a higher priority than |b|.
 */
template <class T, class P, size_t MinInlineCapacity = 0,
          class AllocPolicy = TempAllocPolicy>
class PriorityQueue {
  Vector<T, MinInlineCapacity, AllocPolicy> heap;

  PriorityQueue(const PriorityQueue&) = delete;
  PriorityQueue& operator=(const PriorityQueue&) = delete;

 public:
  explicit PriorityQueue(AllocPolicy ap = AllocPolicy())
      : heap(std::move(ap)) {}

  [[nodiscard]] bool reserve(size_t capacity) { return heap.reserve(capacity); }

  size_t length() const { return heap.length(); }

  bool empty() const { return heap.empty(); }

  // highest and popHighest are used to enforce necessary move semantics for
  // working with UniquePtrs in a queue, and should be used together Example:
  //   UniquePtr<...> x = std::move(queue.highest());
  //   queue.popHighest();
  T& highest() {
    MOZ_ASSERT(!empty());
    return heap[0];
  }

  void popHighest() {
    if (heap.length() == 1) {
      heap.popBack();
      return;
    }
    std::swap(heap[0], heap.back());
    heap.popBack();
    siftDown(0);
  }
  [[nodiscard]] bool insert(T&& v) {
    if (!heap.append(std::move(v))) {
      return false;
    }
    siftUp(heap.length() - 1);
    return true;
  }

  // The combination of reserveOne+infallibleInsert can be used for the case
  // where the failure case needs to take the ownership of the value.
  [[nodiscard]] bool reserveOne() { return heap.reserve(heap.length() + 1); }

  void infallibleInsert(T&& v) {
    heap.infallibleAppend(std::move(v));
    siftUp(heap.length() - 1);
  }

 private:
  /*
   * Elements of the vector encode a binary tree:
   *
   *      0
   *    1   2
   *   3 4 5 6
   *
   * The children of element N are (2N + 1) and (2N + 2).
   * The parent of element N is (N - 1) / 2.
   *
   * Each element has higher priority than its children.
   */

  void siftDown(size_t n) {
    while (true) {
      size_t left = n * 2 + 1;
      size_t right = n * 2 + 2;

      if (left < heap.length()) {
        if (right < heap.length()) {
          if (P::higherPriority(heap[right], heap[n]) &&
              P::higherPriority(heap[right], heap[left])) {
            swap(n, right);
            n = right;
            continue;
          }
        }

        if (P::higherPriority(heap[left], heap[n])) {
          swap(n, left);
          n = left;
          continue;
        }
      }

      break;
    }
  }

  void siftUp(size_t n) {
    while (n > 0) {
      size_t parent = (n - 1) / 2;

      if (P::higherPriority(heap[parent], heap[n])) {
        break;
      }

      swap(n, parent);
      n = parent;
    }
  }

  void swap(size_t a, size_t b) { std::swap(heap[a], heap[b]); }
};

} /* namespace js */

#endif /* ds_PriorityQueue_h */
