/*!
 * Copyright 2018 by Contributors
 *
 * \file arena.h
 * \brief Arena allocator that allocates
 *  memory chunks and frees them all during destruction time.
 */
#ifndef TVM_COMMON_ARENA_H_
#define TVM_COMMON_ARENA_H_

#include <type_traits>

namespace tvm {
namespace common {

const constexpr int kArenaPageSize = 16 << 10;

/*!
 * \brief Arena allocator that allocates memory from continuous
 *  chunk and frees them all only during destruction.
 */
class Arena {
 public:
  Arena() {
    // eagerly allocate the first page.
    head_ = reinterpret_cast<PageHeader*>(new Page());
    head_->next = nullptr;
    head_->ptr = sizeof(PageHeader);
  }
  ~Arena() {
    // delete all the allocated pages.
    while (head_ != nullptr) {
      Page* page = reinterpret_cast<Page*>(head_);
      head_ = head_->next;
      delete page;
    }
  }
  /*!
   * \brief Allocate a space from Arena for type T
   * \param T the data type to be allocated
   * \note The space of T is not initialized.
   */
  template<typename T>
  T* allocate_() {
    return static_cast<T*>(Alloc(sizeof(T), alignof(T)));
  }
  /*!
   * \brief Create a new instance of type T.
   * \param args The constructor argument.
   * \tparam T the type to be created.
   * \tparam Args Arguments to the constructor.
   *
   * \return The allocated object.
   * \note The type T must be simple type, or only contain
   *  memory allocated from the same arena.
   *  Otherwise the destructor needs to be called explicitly.
   */
  template<typename T, typename... Args>
  T* make(Args&&... args) {
    T* ptr = allocate_<T>();
    new (ptr) T(std::forward<Args>(args)...);
    return ptr;
  }

 private:
  // page size 16 KB
  // The page data type;
  using Page = std::aligned_storage<kArenaPageSize, 1024>::type;
  /*! \brief Page header */
  struct PageHeader {
    /*! \brief points to the next page */
    PageHeader* next;
    /*! \brief memory allocator ptr inside page */
    size_t ptr;
  };
  /* \brief The page header */
  PageHeader* head_{nullptr};
  /*!
   * \brief Align ptr by upper bound.
   * \param ptr The pointer value.
   * \param align The alignment requirement.
   */
  size_t UpperAlign(size_t ptr, size_t align) {
    return ptr + (align - (ptr % align)) % align;
  }
  /*!
   * \brief Internal aligned alloc function.
   * \param size The size of the memory.
   * \param align The alignment requirement.
   */
  void* Alloc(size_t size, size_t align) {
    size_t ptr = UpperAlign(head_->ptr, align);
    if (ptr + size <= kArenaPageSize) {
      head_->ptr = ptr + size;
      return reinterpret_cast<char*>(head_) + ptr;
    } else {
      PageHeader* new_head = reinterpret_cast<PageHeader*>(new Page());
      new_head->next = head_;
      ptr = UpperAlign(sizeof(PageHeader), align);
      CHECK_LE(ptr + size, kArenaPageSize);
      new_head->ptr = ptr + size;
      head_ = new_head;
      return reinterpret_cast<char*>(head_) + ptr;
    }
  }
};

/*!
 * \brief Link list node
 * \tparam T the content data type
 */
template<typename T>
struct LinkNode {
  /*! \brief The content value */
  T value;
  /*! \brief pointer to the next location */
  LinkNode<T>* next{nullptr};
};
/*!
 * \brief LinkedList structure
 * \tparam T the content data type
 * \note This is a simple data structure that can be used together with the arena.
 * \sa LinkNode
 */
template<typename T>
struct LinkedList {
  /*! \brief Head pointer */
  LinkNode<T>* head{nullptr};
  /*! \brief Tail pointer */
  LinkNode<T>* tail{nullptr};
  /*!
   * \brief Push a new node to the end of the linked list.
   * \param node The node to be pushed.
   */
  void Push(LinkNode<T>* node) {
    node->next = nullptr;
    if (this->tail != nullptr) {
      this->tail->next = node;
      this->tail = node;
    } else {
      head = tail = node;
    }
  }
};

}  // namespace common
}  // namespace tvm
#endif  // TVM_COMMON_ARENA_H_
