// Copyright 2016 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <algorithm>

#include "src/base/iterator.h"
#include "src/common/globals.h"
#include "src/utils/memcopy.h"
#include "src/zone/zone.h"

#ifndef V8_ZONE_ZONE_CHUNK_LIST_H_
#define V8_ZONE_ZONE_CHUNK_LIST_H_

namespace v8 {
namespace internal {

template <typename T, bool backwards, bool modifiable>
class ZoneChunkListIterator;

// A zone-backed hybrid of a vector and a linked list. Use it if you need a
// collection that
// * needs to grow indefinitely,
// * will mostly grow at the back, but may sometimes grow in front as well
// (preferably in batches),
// * needs to have very low overhead,
// * offers forward- and backwards-iteration,
// * offers relatively fast seeking,
// * offers bidirectional iterators,
// * can be rewound without freeing the backing store,
// * can be split and joined again efficiently.
// This list will maintain a doubly-linked list of chunks. When a chunk is
// filled up, a new one gets appended. New chunks appended at the end will
// grow in size up to a certain limit to avoid over-allocation and to keep
// the zone clean. Chunks may be partially filled. In particular, chunks may
// be empty after rewinding, such that they can be reused when inserting
// again at a later point in time.
template <typename T>
class ZoneChunkList : public ZoneObject {
 public:
  using iterator = ZoneChunkListIterator<T, false, true>;
  using const_iterator = ZoneChunkListIterator<T, false, false>;
  using reverse_iterator = ZoneChunkListIterator<T, true, true>;
  using const_reverse_iterator = ZoneChunkListIterator<T, true, false>;

  static constexpr uint32_t kInitialChunkCapacity = 8;
  static constexpr uint32_t kMaxChunkCapacity = 256;

  explicit ZoneChunkList(Zone* zone) : zone_(zone) {}

  MOVE_ONLY_NO_DEFAULT_CONSTRUCTOR(ZoneChunkList);

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

  T& front();
  const T& front() const;
  T& back();
  const T& back() const;

  void push_back(const T& item);

  // If the first chunk has space, inserts into it at the front. Otherwise
  // allocate a new chunk with the same growth strategy as `push_back`.
  // This limits the amount of copying to O(`kMaxChunkCapacity`).
  void push_front(const T& item);

  // Cuts the last list elements so at most 'limit' many remain. Does not
  // free the actual memory, since it is zone allocated.
  void Rewind(const size_t limit = 0);

  // Quickly scans the list to retrieve the element at the given index. Will
  // *not* check bounds.
  iterator Find(const size_t index);
  const_iterator Find(const size_t index) const;
  // TODO(heimbuef): Add 'rFind', seeking from the end and returning a
  // reverse iterator.

  // Splits off a new list that contains the elements from `split_begin` to
  // `end()`. The current list is truncated to end just before `split_begin`.
  // This naturally invalidates all iterators, including `split_begin`.
  ZoneChunkList<T> SplitAt(iterator split_begin);
  void Append(ZoneChunkList<T>& other);

  void CopyTo(T* ptr);

  iterator begin() { return iterator::Begin(this); }
  iterator end() { return iterator::End(this); }
  reverse_iterator rbegin() { return reverse_iterator::Begin(this); }
  reverse_iterator rend() { return reverse_iterator::End(this); }
  const_iterator begin() const { return const_iterator::Begin(this); }
  const_iterator end() const { return const_iterator::End(this); }
  const_reverse_iterator rbegin() const {
    return const_reverse_iterator::Begin(this);
  }
  const_reverse_iterator rend() const {
    return const_reverse_iterator::End(this);
  }

  void swap(ZoneChunkList<T>& other) {
    DCHECK_EQ(zone_, other.zone_);
    std::swap(size_, other.size_);
    std::swap(front_, other.front_);
    std::swap(last_nonempty_, other.last_nonempty_);
  }

 private:
  template <typename S, bool backwards, bool modifiable>
  friend class ZoneChunkListIterator;

  struct Chunk {
    uint32_t capacity_ = 0;
    uint32_t position_ = 0;
    Chunk* next_ = nullptr;
    Chunk* previous_ = nullptr;
    T* items() { return reinterpret_cast<T*>(this + 1); }
    const T* items() const { return reinterpret_cast<const T*>(this + 1); }
    uint32_t size() const {
      DCHECK_LE(position_, capacity_);
      return position_;
    }
    bool empty() const { return size() == 0; }
    bool full() const { return size() == capacity_; }
  };

  Chunk* NewChunk(const uint32_t capacity) {
    void* memory = zone_->Allocate<Chunk>(sizeof(Chunk) + capacity * sizeof(T));
    Chunk* chunk = new (memory) Chunk();
    chunk->capacity_ = capacity;
    return chunk;
  }

  static uint32_t NextChunkCapacity(uint32_t previous_capacity) {
    return std::min(previous_capacity * 2, kMaxChunkCapacity);
  }

  struct SeekResult {
    Chunk* chunk_;
    uint32_t chunk_index_;
  };

  // Returns the chunk and relative index of the element at the given global
  // index. Will skip entire chunks and is therefore faster than iterating.
  SeekResult SeekIndex(size_t index) const;

#ifdef DEBUG
  // Check the invariants.
  void Verify() const {
    if (front_ == nullptr) {
      // Initial empty state.
      DCHECK_NULL(last_nonempty_);
      DCHECK_EQ(0, size());
    } else if (empty()) {
      // Special case: Fully rewound list, with only empty chunks.
      DCHECK_EQ(front_, last_nonempty_);
      DCHECK_EQ(0, size());
      for (Chunk* chunk = front_; chunk != nullptr; chunk = chunk->next_) {
        DCHECK(chunk->empty());
      }
    } else {
      // Normal state: Somewhat filled and (partially) rewound.
      DCHECK_NOT_NULL(last_nonempty_);

      size_t size_check = 0;
      bool in_empty_tail = false;
      for (Chunk* chunk = front_; chunk != nullptr; chunk = chunk->next_) {
        // Chunks from `front_` to `last_nonempty_` (inclusive) are non-empty.
        DCHECK_EQ(in_empty_tail, chunk->empty());
        size_check += chunk->size();

        if (chunk == last_nonempty_) {
          in_empty_tail = true;
        }
      }
      DCHECK_EQ(size_check, size());
    }
  }
#endif

  Zone* zone_;

  size_t size_ = 0;
  Chunk* front_ = nullptr;
  Chunk* last_nonempty_ = nullptr;
};

template <typename T, bool backwards, bool modifiable>
class ZoneChunkListIterator
    : public base::iterator<std::bidirectional_iterator_tag, T> {
 private:
  template <typename S>
  using maybe_const =
      typename std::conditional<modifiable, S,
                                typename std::add_const<S>::type>::type;
  using Chunk = maybe_const<typename ZoneChunkList<T>::Chunk>;
  using ChunkList = maybe_const<ZoneChunkList<T>>;

 public:
  maybe_const<T>& operator*() const { return current_->items()[position_]; }
  maybe_const<T>* operator->() const { return &current_->items()[position_]; }
  bool operator==(const ZoneChunkListIterator& other) const {
    return other.current_ == current_ && other.position_ == position_;
  }
  bool operator!=(const ZoneChunkListIterator& other) const {
    return !operator==(other);
  }

  ZoneChunkListIterator& operator++() {
    Move<backwards>();
    return *this;
  }

  ZoneChunkListIterator operator++(int) {
    ZoneChunkListIterator clone(*this);
    Move<backwards>();
    return clone;
  }

  ZoneChunkListIterator& operator--() {
    Move<!backwards>();
    return *this;
  }

  ZoneChunkListIterator operator--(int) {
    ZoneChunkListIterator clone(*this);
    Move<!backwards>();
    return clone;
  }

  void Advance(uint32_t amount) {
    static_assert(!backwards, "Advance only works on forward iterators");

#ifdef DEBUG
    ZoneChunkListIterator clone(*this);
    for (uint32_t i = 0; i < amount; ++i) {
      ++clone;
    }
#endif

    CHECK(!base::bits::UnsignedAddOverflow32(position_, amount, &position_));
    while (position_ > 0 && position_ >= current_->position_) {
      auto overshoot = position_ - current_->position_;
      current_ = current_->next_;
      position_ = overshoot;

      DCHECK(position_ == 0 || current_);
    }

#ifdef DEBUG
    DCHECK_EQ(clone, *this);
#endif
  }

 private:
  friend class ZoneChunkList<T>;

  static ZoneChunkListIterator Begin(ChunkList* list) {
    // Forward iterator:
    if (!backwards) return ZoneChunkListIterator(list->front_, 0);

    // Backward iterator:
    if (list->empty()) return End(list);

    DCHECK(!list->last_nonempty_->empty());
    return ZoneChunkListIterator(list->last_nonempty_,
                                 list->last_nonempty_->position_ - 1);
  }

  static ZoneChunkListIterator End(ChunkList* list) {
    // Backward iterator:
    if (backwards) return ZoneChunkListIterator(nullptr, 0);

    // Forward iterator:
    if (list->empty()) return Begin(list);

    // NOTE: Decrementing `end()` is not supported if `last_nonempty_->next_`
    // is nullptr (in that case `Move` will crash on dereference).
    return ZoneChunkListIterator(list->last_nonempty_->next_, 0);
  }

  ZoneChunkListIterator(Chunk* current, uint32_t position)
      : current_(current), position_(position) {
    DCHECK(current == nullptr || position < current->capacity_);
  }

  template <bool move_backward>
  void Move() {
    if (move_backward) {
      // Move backwards.
      if (position_ == 0) {
        current_ = current_->previous_;
        position_ = current_ ? current_->position_ - 1 : 0;
      } else {
        --position_;
      }
    } else {
      // Move forwards.
      ++position_;
      if (position_ >= current_->position_) {
        current_ = current_->next_;
        position_ = 0;
      }
    }
  }

  Chunk* current_;
  uint32_t position_;
};

template <typename T>
T& ZoneChunkList<T>::front() {
  DCHECK(!empty());
  return *begin();
}

template <typename T>
const T& ZoneChunkList<T>::front() const {
  DCHECK(!empty());
  return *begin();
}

template <typename T>
T& ZoneChunkList<T>::back() {
  DCHECK(!empty());
  // Avoid the branch in `ZoneChunkListIterator::Begin()`.
  V8_ASSUME(size_ != 0);
  return *rbegin();
}

template <typename T>
const T& ZoneChunkList<T>::back() const {
  DCHECK(!empty());
  // Avoid the branch in `ZoneChunkListIterator::Begin()`.
  V8_ASSUME(size_ != 0);
  return *rbegin();
}

template <typename T>
void ZoneChunkList<T>::push_back(const T& item) {
  if (last_nonempty_ == nullptr) {
    // Initially empty chunk list.
    front_ = NewChunk(kInitialChunkCapacity);
    last_nonempty_ = front_;
  } else if (last_nonempty_->full()) {
    // If there is an empty chunk following, reuse that, otherwise allocate.
    if (last_nonempty_->next_ == nullptr) {
      Chunk* chunk = NewChunk(NextChunkCapacity(last_nonempty_->capacity_));
      last_nonempty_->next_ = chunk;
      chunk->previous_ = last_nonempty_;
    }
    last_nonempty_ = last_nonempty_->next_;
    DCHECK(!last_nonempty_->full());
  }

  last_nonempty_->items()[last_nonempty_->position_] = item;
  ++last_nonempty_->position_;
  ++size_;
  DCHECK_LE(last_nonempty_->position_, last_nonempty_->capacity_);
}

template <typename T>
void ZoneChunkList<T>::push_front(const T& item) {
  if (front_ == nullptr) {
    // Initially empty chunk list.
    front_ = NewChunk(kInitialChunkCapacity);
    last_nonempty_ = front_;
  } else if (front_->full()) {
    // First chunk at capacity, so prepend a new chunk.
    DCHECK_NULL(front_->previous_);
    Chunk* chunk = NewChunk(NextChunkCapacity(front_->capacity_));
    front_->previous_ = chunk;
    chunk->next_ = front_;
    front_ = chunk;
  }
  DCHECK(!front_->full());

  T* end = front_->items() + front_->position_;
  std::move_backward(front_->items(), end, end + 1);
  front_->items()[0] = item;
  ++front_->position_;
  ++size_;
  DCHECK_LE(front_->position_, front_->capacity_);
}

template <typename T>
typename ZoneChunkList<T>::SeekResult ZoneChunkList<T>::SeekIndex(
    size_t index) const {
  DCHECK_LT(index, size());
  Chunk* current = front_;
  while (index >= current->capacity_) {
    index -= current->capacity_;
    current = current->next_;
  }
  DCHECK_LT(index, current->capacity_);
  return {current, static_cast<uint32_t>(index)};
}

template <typename T>
void ZoneChunkList<T>::Rewind(const size_t limit) {
  if (limit >= size()) return;

  SeekResult seek_result = SeekIndex(limit);
  DCHECK_NOT_NULL(seek_result.chunk_);

  // Do a partial rewind of the chunk containing the index.
  seek_result.chunk_->position_ = seek_result.chunk_index_;

  // Set last_nonempty_ so iterators will work correctly.
  last_nonempty_ = seek_result.chunk_;

  // Do full rewind of all subsequent chunks.
  for (Chunk* current = seek_result.chunk_->next_; current != nullptr;
       current = current->next_) {
    current->position_ = 0;
  }

  size_ = limit;

#ifdef DEBUG
  Verify();
#endif
}

template <typename T>
typename ZoneChunkList<T>::iterator ZoneChunkList<T>::Find(const size_t index) {
  SeekResult seek_result = SeekIndex(index);
  return typename ZoneChunkList<T>::iterator(seek_result.chunk_,
                                             seek_result.chunk_index_);
}

template <typename T>
typename ZoneChunkList<T>::const_iterator ZoneChunkList<T>::Find(
    const size_t index) const {
  SeekResult seek_result = SeekIndex(index);
  return typename ZoneChunkList<T>::const_iterator(seek_result.chunk_,
                                                   seek_result.chunk_index_);
}

template <typename T>
ZoneChunkList<T> ZoneChunkList<T>::SplitAt(iterator split_begin) {
  ZoneChunkList<T> result(zone_);

  // `result` is an empty freshly-constructed list.
  if (split_begin == end()) return result;

  // `this` is empty after the split and `result` contains everything.
  if (split_begin == begin()) {
    this->swap(result);
    return result;
  }

  // There is at least one element in both `this` and `result`.

  // Split the chunk.
  Chunk* split_chunk = split_begin.current_;
  DCHECK_LE(split_begin.position_, split_chunk->position_);
  T* chunk_split_begin = split_chunk->items() + split_begin.position_;
  T* chunk_split_end = split_chunk->items() + split_chunk->position_;
  uint32_t new_chunk_size =
      static_cast<uint32_t>(chunk_split_end - chunk_split_begin);
  uint32_t new_chunk_capacity = std::max(
      kInitialChunkCapacity, base::bits::RoundUpToPowerOfTwo32(new_chunk_size));
  CHECK_LE(new_chunk_size, new_chunk_capacity);
  Chunk* new_chunk = NewChunk(new_chunk_capacity);
  std::copy(chunk_split_begin, chunk_split_end, new_chunk->items());
  new_chunk->position_ = new_chunk_size;
  split_chunk->position_ = split_begin.position_;

  // Split the linked list.
  result.front_ = new_chunk;
  result.last_nonempty_ =
      (last_nonempty_ == split_chunk) ? new_chunk : last_nonempty_;
  new_chunk->next_ = split_chunk->next_;
  if (new_chunk->next_) {
    new_chunk->next_->previous_ = new_chunk;
  }

  last_nonempty_ = split_chunk;
  split_chunk->next_ = nullptr;

  // Compute the new size.
  size_t new_size = 0;
  for (Chunk* chunk = front_; chunk != split_chunk; chunk = chunk->next_) {
    DCHECK(!chunk->empty());
    new_size += chunk->size();
  }
  new_size += split_chunk->size();
  DCHECK_LT(new_size, size());
  result.size_ = size() - new_size;
  size_ = new_size;

#ifdef DEBUG
  Verify();
  result.Verify();
#endif

  return result;
}

template <typename T>
void ZoneChunkList<T>::Append(ZoneChunkList<T>& other) {
  DCHECK_EQ(zone_, other.zone_);

  if (other.front_ == nullptr) return;

  last_nonempty_->next_ = other.front_;
  other.front_->previous_ = last_nonempty_;

  last_nonempty_ = other.last_nonempty_;

  size_ += other.size_;
#ifdef DEBUG
  Verify();
#endif

  // Leave `other` in empty, but valid state.
  other.front_ = nullptr;
  other.last_nonempty_ = nullptr;
  other.size_ = 0;
}

template <typename T>
void ZoneChunkList<T>::CopyTo(T* ptr) {
  for (Chunk* current = front_; current != nullptr; current = current->next_) {
    void* start = current->items();
    void* end = current->items() + current->position_;
    size_t bytes = static_cast<size_t>(reinterpret_cast<uintptr_t>(end) -
                                       reinterpret_cast<uintptr_t>(start));

    MemCopy(ptr, current->items(), bytes);
    ptr += current->position_;
  }
}

}  // namespace internal
}  // namespace v8

#endif  // V8_ZONE_ZONE_CHUNK_LIST_H_
