/*
 * Copyright 2017 WebAssembly Community Group participants
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef WABT_INTRUSIVE_LIST_H_
#define WABT_INTRUSIVE_LIST_H_

#include <cassert>
#include <iterator>
#include <memory>

// This uses a similar interface as std::list, but is missing the following
// features:
//
// * Add "extract_" functions that remove an element from the list and return
//   it.
// * Only supports move-only operations
// * No allocator support
// * No initializer lists
// * Asserts instead of exceptions
// * Some functions are not implemented (merge, remove, remove_if, reverse,
//   unique, sort, non-member comparison operators)

namespace wabt {

template <typename T>
class intrusive_list;

template <typename T>
class intrusive_list_base {
 private:
  friend class intrusive_list<T>;

  mutable T* next_ = nullptr;
  mutable T* prev_ = nullptr;
};

template <typename T>
class intrusive_list {
 public:
  // types:
  using value_type = T;
  using reference = value_type&;
  using const_reference = const value_type&;
  class iterator;
  class const_iterator;
  using size_type = std::size_t;
  using difference_type = std::ptrdiff_t;
  using reverse_iterator = std::reverse_iterator<iterator>;
  using const_reverse_iterator = std::reverse_iterator<const_iterator>;

  // construct/copy/destroy:
  intrusive_list();
  explicit intrusive_list(std::unique_ptr<T> node);
  explicit intrusive_list(T&& node);
  intrusive_list(const intrusive_list&) = delete;
  intrusive_list(intrusive_list&&);
  ~intrusive_list();
  intrusive_list& operator=(const intrusive_list& other) = delete;
  intrusive_list& operator=(intrusive_list&& other);

  // iterators:
  iterator begin() noexcept;
  const_iterator begin() const noexcept;
  iterator end() noexcept;
  const_iterator end() const noexcept;

  reverse_iterator rbegin() noexcept;
  const_reverse_iterator rbegin() const noexcept;
  reverse_iterator rend() noexcept;
  const_reverse_iterator rend() const noexcept;

  const_iterator cbegin() const noexcept;
  const_iterator cend() const noexcept;
  const_reverse_iterator crbegin() const noexcept;
  const_reverse_iterator crend() const noexcept;

  // capacity:
  size_type size() const noexcept;
  bool empty() const noexcept;

  // element access:
  reference front();
  const_reference front() const;
  reference back();
  const_reference back() const;

  // modifiers:
  template <class... Args>
  void emplace_front(Args&&... args);
  template <class... Args>
  void emplace_back(Args&&... args);
  void push_front(std::unique_ptr<T> node);
  void push_front(T&& node);
  void push_back(std::unique_ptr<T> node);
  void push_back(T&& node);
  void pop_front();
  void pop_back();
  std::unique_ptr<T> extract_front();
  std::unique_ptr<T> extract_back();

  template <class... Args>
  iterator emplace(iterator pos, Args&&... args);
  iterator insert(iterator pos, std::unique_ptr<T> node);
  iterator insert(iterator pos, T&& node);
  std::unique_ptr<T> extract(iterator it);

  iterator erase(iterator pos);
  iterator erase(iterator first, iterator last);
  void swap(intrusive_list&);
  void clear() noexcept;

  void splice(iterator pos, intrusive_list& node);
  void splice(iterator pos, intrusive_list&& node);
  void splice(iterator pos, intrusive_list& node, iterator it);
  void splice(iterator pos,
              intrusive_list& node,
              iterator first,
              iterator last);

 private:
  T* first_ = nullptr;
  T* last_ = nullptr;
  size_t size_ = 0;
};

/// iterator
template <typename T>
class intrusive_list<T>::iterator {
 public:
  using difference_type = std::ptrdiff_t;
  using iterator_category = std::bidirectional_iterator_tag;
  using value_type = T;
  using pointer = T*;
  using reference = T&;

  iterator(const intrusive_list<T>& list, T* node)
      : list_(&list), node_(node) {}

  reference operator*() const {
    assert(node_);
    return *node_;
  }

  pointer operator->() const {
    assert(node_);
    return node_;
  }

  iterator& operator++() {
    assert(node_);
    node_ = node_->next_;
    return *this;
  }

  iterator operator++(int) {
    iterator tmp = *this;
    operator++();
    return tmp;
  }

  iterator& operator--() {
    node_ = node_ ? node_->prev_ : list_->last_;
    return *this;
  }

  iterator operator--(int) {
    iterator tmp = *this;
    operator--();
    return tmp;
  }

  bool operator==(iterator rhs) const {
    assert(list_ == rhs.list_);
    return node_ == rhs.node_;
  }

  bool operator!=(iterator rhs) const {
    assert(list_ == rhs.list_);
    return node_ != rhs.node_;
  }

 private:
  friend class const_iterator;

  const intrusive_list<T>* list_;
  T* node_;
};

/// const_iterator
template <typename T>
class intrusive_list<T>::const_iterator {
 public:
  using difference_type = std::ptrdiff_t;
  using iterator_category = std::bidirectional_iterator_tag;
  using value_type = T;
  using pointer = const T*;
  using reference = const T&;

  const_iterator(const intrusive_list<T>& list, T* node)
      : list_(&list), node_(node) {}

  const_iterator(const iterator& other)
      : list_(other.list_), node_(other.node_) {}

  reference operator*() const {
    assert(node_);
    return *node_;
  }

  pointer operator->() const {
    assert(node_);
    return node_;
  }

  const_iterator& operator++() {
    assert(node_);
    node_ = node_->next_;
    return *this;
  }

  const_iterator operator++(int) {
    const_iterator tmp = *this;
    operator++();
    return tmp;
  }

  const_iterator& operator--() {
    node_ = node_ ? node_->prev_ : list_->last_;
    return *this;
  }

  const_iterator operator--(int) {
    const_iterator tmp = *this;
    operator--();
    return tmp;
  }

  bool operator==(const_iterator rhs) const {
    assert(list_ == rhs.list_);
    return node_ == rhs.node_;
  }

  bool operator!=(const_iterator rhs) const {
    assert(list_ == rhs.list_);
    return node_ != rhs.node_;
  }

 private:
  const intrusive_list<T>* list_;
  T* node_;
};

template <typename T>
inline intrusive_list<T>::intrusive_list() {}

template <typename T>
inline intrusive_list<T>::intrusive_list(std::unique_ptr<T> node) {
  push_back(std::move(node));
}

template <typename T>
inline intrusive_list<T>::intrusive_list(T&& node) {
  push_back(std::move(node));
}

template <typename T>
inline intrusive_list<T>::intrusive_list(intrusive_list&& other)
    : first_(other.first_), last_(other.last_), size_(other.size_) {
  other.first_ = other.last_ = nullptr;
  other.size_ = 0;
}

template <typename T>
inline intrusive_list<T>::~intrusive_list() {
  clear();
}

template <typename T>
inline intrusive_list<T>& intrusive_list<T>::operator=(
    intrusive_list<T>&& other) {
  clear();
  first_ = other.first_;
  last_ = other.last_;
  size_ = other.size_;
  other.first_ = other.last_ = nullptr;
  other.size_ = 0;
  return *this;
}

template <typename T>
inline typename intrusive_list<T>::iterator
intrusive_list<T>::begin() noexcept {
  return iterator(*this, first_);
}

template <typename T>
inline typename intrusive_list<T>::const_iterator intrusive_list<T>::begin()
    const noexcept {
  return const_iterator(*this, first_);
}

template <typename T>
inline typename intrusive_list<T>::iterator intrusive_list<T>::end() noexcept {
  return iterator(*this, nullptr);
}

template <typename T>
inline typename intrusive_list<T>::const_iterator intrusive_list<T>::end()
    const noexcept {
  return const_iterator(*this, nullptr);
}

template <typename T>
inline typename intrusive_list<T>::reverse_iterator
intrusive_list<T>::rbegin() noexcept {
  return reverse_iterator(iterator(*this, nullptr));
}

template <typename T>
inline typename intrusive_list<T>::const_reverse_iterator
intrusive_list<T>::rbegin() const noexcept {
  return const_reverse_iterator(const_iterator(*this, nullptr));
}

template <typename T>
inline typename intrusive_list<T>::reverse_iterator
intrusive_list<T>::rend() noexcept {
  return reverse_iterator(iterator(*this, first_));
}

template <typename T>
inline typename intrusive_list<T>::const_reverse_iterator
intrusive_list<T>::rend() const noexcept {
  return const_reverse_iterator(const_iterator(*this, first_));
}

template <typename T>
inline typename intrusive_list<T>::const_iterator intrusive_list<T>::cbegin()
    const noexcept {
  return const_iterator(*this, first_);
}

template <typename T>
inline typename intrusive_list<T>::const_iterator intrusive_list<T>::cend()
    const noexcept {
  return const_iterator(*this, nullptr);
}

template <typename T>
inline typename intrusive_list<T>::const_reverse_iterator
intrusive_list<T>::crbegin() const noexcept {
  return const_reverse_iterator(const_iterator(*this, nullptr));
}

template <typename T>
inline typename intrusive_list<T>::const_reverse_iterator
intrusive_list<T>::crend() const noexcept {
  return const_reverse_iterator(const_iterator(*this, first_));
}

template <typename T>
inline typename intrusive_list<T>::size_type intrusive_list<T>::size()
    const noexcept {
  return size_;
}

template <typename T>
inline bool intrusive_list<T>::empty() const noexcept {
  return size_ == 0;
}

template <typename T>
inline typename intrusive_list<T>::reference intrusive_list<T>::front() {
  assert(!empty());
  return *first_;
}

template <typename T>
inline typename intrusive_list<T>::const_reference intrusive_list<T>::front()
    const {
  assert(!empty());
  return *first_;
}

template <typename T>
inline typename intrusive_list<T>::reference intrusive_list<T>::back() {
  assert(!empty());
  return *last_;
}

template <typename T>
inline typename intrusive_list<T>::const_reference intrusive_list<T>::back()
    const {
  assert(!empty());
  return *last_;
}

template <typename T>
template <class... Args>
inline void intrusive_list<T>::emplace_front(Args&&... args) {
  push_front(std::make_unique<T>(std::forward<Args>(args)...));
}

template <typename T>
template <class... Args>
inline void intrusive_list<T>::emplace_back(Args&&... args) {
  push_back(std::make_unique<T>(std::forward<Args>(args)...));
}

template <typename T>
inline void intrusive_list<T>::push_front(std::unique_ptr<T> node) {
  assert(node->prev_ == nullptr && node->next_ == nullptr);

  T* node_p = node.release();
  if (first_) {
    node_p->next_ = first_;
    first_->prev_ = node_p;
  } else {
    last_ = node_p;
  }
  first_ = node_p;
  size_++;
}

template <typename T>
inline void intrusive_list<T>::push_front(T&& node) {
  push_front(std::make_unique<T>(std::move(node)));
}

template <typename T>
inline void intrusive_list<T>::push_back(std::unique_ptr<T> node) {
  assert(node->prev_ == nullptr && node->next_ == nullptr);

  T* node_p = node.release();
  if (last_) {
    node_p->prev_ = last_;
    last_->next_ = node_p;
  } else {
    first_ = node_p;
  }
  last_ = node_p;
  size_++;
}

template <typename T>
inline void intrusive_list<T>::push_back(T&& node) {
  push_back(std::make_unique<T>(std::move(node)));
}

template <typename T>
inline void intrusive_list<T>::pop_front() {
  extract_front();
}

template <typename T>
inline void intrusive_list<T>::pop_back() {
  extract_back();
}

template <typename T>
inline std::unique_ptr<T> intrusive_list<T>::extract_front() {
  assert(!empty());
  T* node = first_;
  if (first_ == last_) {
    first_ = last_ = nullptr;
  } else {
    first_ = first_->next_;
    first_->prev_ = nullptr;
  }
  node->next_ = node->prev_ = nullptr;
  size_--;
  return std::unique_ptr<T>(node);
}

template <typename T>
inline std::unique_ptr<T> intrusive_list<T>::extract_back() {
  assert(!empty());
  T* node = last_;
  if (first_ == last_) {
    first_ = last_ = nullptr;
  } else {
    last_ = last_->prev_;
    last_->next_ = nullptr;
  }
  node->next_ = node->prev_ = nullptr;
  size_--;
  return std::unique_ptr<T>(node);
}

template <typename T>
template <class... Args>
inline typename intrusive_list<T>::iterator intrusive_list<T>::emplace(
    iterator pos,
    Args&&... args) {
  return insert(pos, std::make_unique<T>(std::forward<Args>(args)...));
}

template <typename T>
inline typename intrusive_list<T>::iterator intrusive_list<T>::insert(
    iterator pos,
    std::unique_ptr<T> node) {
  assert(node->prev_ == nullptr && node->next_ == nullptr);

  T* node_p;
  if (pos == end()) {
    push_back(std::move(node));
    node_p = &back();
  } else {
    node_p = node.release();
    node_p->prev_ = pos->prev_;
    node_p->next_ = &*pos;
    if (pos->prev_) {
      pos->prev_->next_ = node_p;
    } else {
      first_ = node_p;
    }
    pos->prev_ = node_p;
    size_++;
  }
  return iterator(*this, node_p);
}

template <typename T>
inline typename intrusive_list<T>::iterator intrusive_list<T>::insert(
    iterator pos,
    T&& node) {
  return insert(pos, std::make_unique<T>(std::move(node)));
}

template <typename T>
inline std::unique_ptr<T> intrusive_list<T>::extract(iterator pos) {
  assert(!empty());
  assert(pos != end());
  T* node = &*pos;
  if (first_ == last_) {
    first_ = last_ = nullptr;
  } else {
    if (node->prev_) {
      node->prev_->next_ = node->next_;
    } else {
      first_ = node->next_;
    }

    if (node->next_) {
      node->next_->prev_ = node->prev_;
    } else {
      last_ = node->prev_;
    }
  }
  node->next_ = node->prev_ = nullptr;
  size_--;
  return std::unique_ptr<T>(node);
}

template <typename T>
inline typename intrusive_list<T>::iterator intrusive_list<T>::erase(
    iterator pos) {
  iterator next = std::next(pos);
  extract(pos);
  return next;
}

template <typename T>
inline typename intrusive_list<T>::iterator intrusive_list<T>::erase(
    iterator first,
    iterator last) {
  while (first != last)
    first = erase(first);
  return first;
}

template <typename T>
inline void intrusive_list<T>::swap(intrusive_list& other) {
  std::swap(first_, other.first_);
  std::swap(last_, other.last_);
  std::swap(size_, other.size_);
}

template <typename T>
inline void intrusive_list<T>::clear() noexcept {
  for (T* iter = first_; iter;) {
    T* next = iter->next_;
    delete iter;
    iter = next;
  }
  first_ = last_ = nullptr;
  size_ = 0;
}

template <typename T>
inline void intrusive_list<T>::splice(iterator pos, intrusive_list& other) {
  splice(pos, other, other.begin(), other.end());
}

template <typename T>
inline void intrusive_list<T>::splice(iterator pos, intrusive_list&& other) {
  splice(pos, other, other.begin(), other.end());
}

template <typename T>
inline void intrusive_list<T>::splice(iterator pos,
                                      intrusive_list& other,
                                      iterator it) {
  insert(pos, other.extract(it));
}

template <typename T>
inline void intrusive_list<T>::splice(iterator pos,
                                      intrusive_list& other,
                                      iterator first,
                                      iterator last) {
  while (first != last)
    insert(pos, other.extract(first++));
}

}  // namespace wabt

#endif  // WABT_INTRUSIVE_LIST_H_
