// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef BASE_TASK_SEQUENCE_MANAGER_INTRUSIVE_HEAP_H_
#define BASE_TASK_SEQUENCE_MANAGER_INTRUSIVE_HEAP_H_

#include <algorithm>
#include <vector>

#include "base/logging.h"

namespace base {
namespace sequence_manager {
namespace internal {

template <typename T>
class IntrusiveHeap;

// Intended as an opaque wrapper around |index_|.
class HeapHandle {
 public:
  HeapHandle() : index_(0u) {}

  bool IsValid() const { return index_ != 0u; }

 private:
  template <typename T>
  friend class IntrusiveHeap;

  HeapHandle(size_t index) : index_(index) {}

  size_t index_;
};

// A standard min-heap with the following assumptions:
// 1. T has operator <=
// 2. T has method void SetHeapHandle(HeapHandle handle)
// 3. T has method void ClearHeapHandle()
// 4. T is moveable
// 5. T is default constructible
// 6. The heap size never gets terribly big so reclaiming memory on pop/erase
// isn't a priority.
//
// The reason IntrusiveHeap exists is to provide similar performance to
// std::priority_queue while allowing removal of arbitrary elements.
template <typename T>
class IntrusiveHeap {
 public:
  IntrusiveHeap() : nodes_(kMinimumHeapSize), size_(0) {}

  ~IntrusiveHeap() {
    for (size_t i = 1; i <= size_; i++) {
      MakeHole(i);
    }
  }

  bool empty() const { return size_ == 0; }

  size_t size() const { return size_; }

  void Clear() {
    for (size_t i = 1; i <= size_; i++) {
      MakeHole(i);
    }
    nodes_.resize(kMinimumHeapSize);
    size_ = 0;
  }

  const T& Min() const {
    DCHECK_GE(size_, 1u);
    return nodes_[1];
  }

  void Pop() {
    DCHECK_GE(size_, 1u);
    MakeHole(1u);
    size_t top_index = size_--;
    if (!empty())
      MoveHoleDownAndFillWithLeafElement(1u, std::move(nodes_[top_index]));
  }

  void insert(T&& element) {
    size_++;
    if (size_ >= nodes_.size())
      nodes_.resize(nodes_.size() * 2);
    // Notionally we have a hole in the tree at index |size_|, move this up
    // to find the right insertion point.
    MoveHoleUpAndFillWithElement(size_, std::move(element));
  }

  void erase(HeapHandle handle) {
    DCHECK_GT(handle.index_, 0u);
    DCHECK_LE(handle.index_, size_);
    MakeHole(handle.index_);
    size_t top_index = size_--;
    if (empty() || top_index == handle.index_)
      return;
    if (nodes_[handle.index_] <= nodes_[top_index]) {
      MoveHoleDownAndFillWithLeafElement(handle.index_,
                                         std::move(nodes_[top_index]));
    } else {
      MoveHoleUpAndFillWithElement(handle.index_, std::move(nodes_[top_index]));
    }
  }

  void ReplaceMin(T&& element) {
    // Note |element| might not be a leaf node so we can't use
    // MoveHoleDownAndFillWithLeafElement.
    MoveHoleDownAndFillWithElement(1u, std::move(element));
  }

  void ChangeKey(HeapHandle handle, T&& element) {
    if (nodes_[handle.index_] <= element) {
      MoveHoleDownAndFillWithLeafElement(handle.index_, std::move(element));
    } else {
      MoveHoleUpAndFillWithElement(handle.index_, std::move(element));
    }
  }

  // Caution mutating the heap invalidates the iterators.
  const T* begin() const { return &nodes_[1u]; }
  const T* end() const { return begin() + size_; }

 private:
  enum {
    // The majority of sets in the scheduler have 0-3 items in them (a few will
    // have perhaps up to 100), so this means we usually only have to allocate
    // memory once.
    kMinimumHeapSize = 4u
  };

  friend class IntrusiveHeapTest;

  size_t MoveHole(size_t new_hole_pos, size_t old_hole_pos) {
    DCHECK_GT(new_hole_pos, 0u);
    DCHECK_LE(new_hole_pos, size_);
    DCHECK_GT(new_hole_pos, 0u);
    DCHECK_LE(new_hole_pos, size_);
    DCHECK_NE(old_hole_pos, new_hole_pos);
    nodes_[old_hole_pos] = std::move(nodes_[new_hole_pos]);
    nodes_[old_hole_pos].SetHeapHandle(HeapHandle(old_hole_pos));
    return new_hole_pos;
  }

  // Notionally creates a hole in the tree at |index|.
  void MakeHole(size_t index) {
    DCHECK_GT(index, 0u);
    DCHECK_LE(index, size_);
    nodes_[index].ClearHeapHandle();
  }

  void FillHole(size_t hole, T&& element) {
    DCHECK_GT(hole, 0u);
    DCHECK_LE(hole, size_);
    nodes_[hole] = std::move(element);
    nodes_[hole].SetHeapHandle(HeapHandle(hole));
    DCHECK(std::is_heap(begin(), end(), CompareNodes));
  }

  // is_heap requires a strict comparator.
  static bool CompareNodes(const T& a, const T& b) { return !(a <= b); }

  // Moves the |hole| up the tree and when the right position has been found
  // |element| is moved in.
  void MoveHoleUpAndFillWithElement(size_t hole, T&& element) {
    DCHECK_GT(hole, 0u);
    DCHECK_LE(hole, size_);
    while (hole >= 2u) {
      size_t parent_pos = hole / 2;
      if (nodes_[parent_pos] <= element)
        break;

      hole = MoveHole(parent_pos, hole);
    }
    FillHole(hole, std::move(element));
  }

  // Moves the |hole| down the tree and when the right position has been found
  // |element| is moved in.
  void MoveHoleDownAndFillWithElement(size_t hole, T&& element) {
    DCHECK_GT(hole, 0u);
    DCHECK_LE(hole, size_);
    size_t child_pos = hole * 2;
    while (child_pos < size_) {
      if (nodes_[child_pos + 1] <= nodes_[child_pos])
        child_pos++;

      if (element <= nodes_[child_pos])
        break;

      hole = MoveHole(child_pos, hole);
      child_pos *= 2;
    }
    if (child_pos == size_ && !(element <= nodes_[child_pos]))
      hole = MoveHole(child_pos, hole);
    FillHole(hole, std::move(element));
  }

  // Moves the |hole| down the tree and when the right position has been found
  // |leaf_element| is moved in.  Faster than MoveHoleDownAndFillWithElement
  // (it does one key comparison per level instead of two) but only valid for
  // leaf elements (i.e. one of the max values).
  void MoveHoleDownAndFillWithLeafElement(size_t hole, T&& leaf_element) {
    DCHECK_GT(hole, 0u);
    DCHECK_LE(hole, size_);
    size_t child_pos = hole * 2;
    while (child_pos < size_) {
      size_t second_child = child_pos + 1;
      if (nodes_[second_child] <= nodes_[child_pos])
        child_pos = second_child;

      hole = MoveHole(child_pos, hole);
      child_pos *= 2;
    }
    if (child_pos == size_)
      hole = MoveHole(child_pos, hole);
    MoveHoleUpAndFillWithElement(hole, std::move(leaf_element));
  }

  std::vector<T> nodes_;  // NOTE we use 1-based indexing
  size_t size_;
};

}  // namespace internal
}  // namespace sequence_manager
}  // namespace base

#endif  // BASE_TASK_SEQUENCE_MANAGER_INTRUSIVE_HEAP_H_
