//===- llvm/ADT/AllocatorList.h - Custom allocator list ---------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef LLVM_ADT_ALLOCATORLIST_H
#define LLVM_ADT_ALLOCATORLIST_H

#include "llvm/ADT/ilist_node.h"
#include "llvm/ADT/iterator.h"
#include "llvm/ADT/simple_ilist.h"
#include "llvm/Support/Allocator.h"
#include <cassert>
#include <cstddef>
#include <iterator>
#include <type_traits>
#include <utility>

namespace llvm {

/// A linked-list with a custom, local allocator.
///
/// Expose a std::list-like interface that owns and uses a custom LLVM-style
/// allocator (e.g., BumpPtrAllocator), leveraging \a simple_ilist for the
/// implementation details.
///
/// Because this list owns the allocator, calling \a splice() with a different
/// list isn't generally safe.  As such, \a splice has been left out of the
/// interface entirely.
template <class T, class AllocatorT> class AllocatorList : AllocatorT {
  struct Node : ilist_node<Node> {
    Node(Node &&) = delete;
    Node(const Node &) = delete;
    Node &operator=(Node &&) = delete;
    Node &operator=(const Node &) = delete;

    Node(T &&V) : V(std::move(V)) {}
    Node(const T &V) : V(V) {}
    template <class... Ts> Node(Ts &&... Vs) : V(std::forward<Ts>(Vs)...) {}
    T V;
  };

  using list_type = simple_ilist<Node>;

  list_type List;

  AllocatorT &getAlloc() { return *this; }
  const AllocatorT &getAlloc() const { return *this; }

  template <class... ArgTs> Node *create(ArgTs &&... Args) {
    return new (getAlloc()) Node(std::forward<ArgTs>(Args)...);
  }

  struct Cloner {
    AllocatorList &AL;

    Cloner(AllocatorList &AL) : AL(AL) {}

    Node *operator()(const Node &N) const { return AL.create(N.V); }
  };

  struct Disposer {
    AllocatorList &AL;

    Disposer(AllocatorList &AL) : AL(AL) {}

    void operator()(Node *N) const {
      N->~Node();
      AL.getAlloc().Deallocate(N);
    }
  };

public:
  using value_type = T;
  using pointer = T *;
  using reference = T &;
  using const_pointer = const T *;
  using const_reference = const T &;
  using size_type = typename list_type::size_type;
  using difference_type = typename list_type::difference_type;

private:
  template <class ValueT, class IteratorBase>
  class IteratorImpl
      : public iterator_adaptor_base<IteratorImpl<ValueT, IteratorBase>,
                                     IteratorBase,
                                     std::bidirectional_iterator_tag, ValueT> {
    template <class OtherValueT, class OtherIteratorBase>
    friend class IteratorImpl;
    friend AllocatorList;

    using base_type =
        iterator_adaptor_base<IteratorImpl<ValueT, IteratorBase>, IteratorBase,
                              std::bidirectional_iterator_tag, ValueT>;

  public:
    using value_type = ValueT;
    using pointer = ValueT *;
    using reference = ValueT &;

    IteratorImpl() = default;
    IteratorImpl(const IteratorImpl &) = default;
    IteratorImpl &operator=(const IteratorImpl &) = default;

    explicit IteratorImpl(const IteratorBase &I) : base_type(I) {}

    template <class OtherValueT, class OtherIteratorBase>
    IteratorImpl(const IteratorImpl<OtherValueT, OtherIteratorBase> &X,
                 std::enable_if_t<std::is_convertible<
                     OtherIteratorBase, IteratorBase>::value> * = nullptr)
        : base_type(X.wrapped()) {}

    ~IteratorImpl() = default;

    reference operator*() const { return base_type::wrapped()->V; }
    pointer operator->() const { return &operator*(); }
  };

public:
  using iterator = IteratorImpl<T, typename list_type::iterator>;
  using reverse_iterator =
      IteratorImpl<T, typename list_type::reverse_iterator>;
  using const_iterator =
      IteratorImpl<const T, typename list_type::const_iterator>;
  using const_reverse_iterator =
      IteratorImpl<const T, typename list_type::const_reverse_iterator>;

  AllocatorList() = default;
  AllocatorList(AllocatorList &&X)
      : AllocatorT(std::move(X.getAlloc())), List(std::move(X.List)) {}

  AllocatorList(const AllocatorList &X) {
    List.cloneFrom(X.List, Cloner(*this), Disposer(*this));
  }

  AllocatorList &operator=(AllocatorList &&X) {
    clear(); // Dispose of current nodes explicitly.
    List = std::move(X.List);
    getAlloc() = std::move(X.getAlloc());
    return *this;
  }

  AllocatorList &operator=(const AllocatorList &X) {
    List.cloneFrom(X.List, Cloner(*this), Disposer(*this));
    return *this;
  }

  ~AllocatorList() { clear(); }

  void swap(AllocatorList &RHS) {
    List.swap(RHS.List);
    std::swap(getAlloc(), RHS.getAlloc());
  }

  [[nodiscard]] bool empty() const { return List.empty(); }
  [[nodiscard]] size_t size() const { return List.size(); }

  iterator begin() { return iterator(List.begin()); }
  iterator end() { return iterator(List.end()); }
  const_iterator begin() const { return const_iterator(List.begin()); }
  const_iterator end() const { return const_iterator(List.end()); }
  reverse_iterator rbegin() { return reverse_iterator(List.rbegin()); }
  reverse_iterator rend() { return reverse_iterator(List.rend()); }
  const_reverse_iterator rbegin() const {
    return const_reverse_iterator(List.rbegin());
  }
  const_reverse_iterator rend() const {
    return const_reverse_iterator(List.rend());
  }

  T &back() { return List.back().V; }
  T &front() { return List.front().V; }
  const T &back() const { return List.back().V; }
  const T &front() const { return List.front().V; }

  template <class... Ts> iterator emplace(iterator I, Ts &&... Vs) {
    return iterator(List.insert(I.wrapped(), *create(std::forward<Ts>(Vs)...)));
  }

  iterator insert(iterator I, T &&V) {
    return iterator(List.insert(I.wrapped(), *create(std::move(V))));
  }
  iterator insert(iterator I, const T &V) {
    return iterator(List.insert(I.wrapped(), *create(V)));
  }

  template <class Iterator>
  void insert(iterator I, Iterator First, Iterator Last) {
    for (; First != Last; ++First)
      List.insert(I.wrapped(), *create(*First));
  }

  iterator erase(iterator I) {
    return iterator(List.eraseAndDispose(I.wrapped(), Disposer(*this)));
  }

  iterator erase(iterator First, iterator Last) {
    return iterator(
        List.eraseAndDispose(First.wrapped(), Last.wrapped(), Disposer(*this)));
  }

  void clear() { List.clearAndDispose(Disposer(*this)); }
  void pop_back() { List.eraseAndDispose(--List.end(), Disposer(*this)); }
  void pop_front() { List.eraseAndDispose(List.begin(), Disposer(*this)); }
  void push_back(T &&V) { insert(end(), std::move(V)); }
  void push_front(T &&V) { insert(begin(), std::move(V)); }
  void push_back(const T &V) { insert(end(), V); }
  void push_front(const T &V) { insert(begin(), V); }
  template <class... Ts> void emplace_back(Ts &&... Vs) {
    emplace(end(), std::forward<Ts>(Vs)...);
  }
  template <class... Ts> void emplace_front(Ts &&... Vs) {
    emplace(begin(), std::forward<Ts>(Vs)...);
  }

  /// Reset the underlying allocator.
  ///
  /// \pre \c empty()
  void resetAlloc() {
    assert(empty() && "Cannot reset allocator if not empty");
    getAlloc().Reset();
  }
};

template <class T> using BumpPtrList = AllocatorList<T, BumpPtrAllocator>;

} // end namespace llvm

#endif // LLVM_ADT_ALLOCATORLIST_H
