
#ifndef PLS_INTERNAL_SCHEDULING_CONT_NODE_H_
#define PLS_INTERNAL_SCHEDULING_CONT_NODE_H_

namespace pls {
namespace internal {
namespace scheduling {

/**
 * A block of memory that can be used to store tasks and continuations.
 * Threads trade these blocks while executing and stealing tasks.
 *
 * Each block has an associated, raw memory buffer. The user can place his object
 * in this memory region as needed. He is responsible for calling deconstructors of the
 * placed objects.
 */
class memory_block {
 public:
  memory_block(char *memory_buffer,
               size_t memory_buffer_size,
               memory_block *prev,
               int depth)
      : prev_{prev},
        next_{nullptr},
        offered_chain_{nullptr},
        results_missing_{2},
        memory_buffer_{memory_buffer},
        memory_buffer_size_{memory_buffer_size},
        memory_buffer_used_{false},
        depth_{depth} {};

  template<typename T, typename ...ARGS>
  T *place_in_buffer(ARGS &&...args) {
    PLS_ASSERT(!memory_buffer_used_, "Must only allocate one continuation at once per node.");

    memory_buffer_used_ = true;
    auto *result = new(memory_buffer_) T(std::forward<ARGS>(args)...);
    continuation_ = result;

    return result;
  }
  void free_buffer() {
    PLS_ASSERT(memory_buffer_used_, "Can only free a memory spot when it is in use.")
    memory_buffer_used_ = false;
  }
  bool is_buffer_used() {
    return memory_buffer_used_;
  }
  base_cont *get_cont() {
    PLS_ASSERT(is_buffer_used(), "Can only read initialized buffer!");
    return continuation_;
  }

  memory_block *get_prev() {
    return prev_;
  }
  void set_prev(memory_block *prev) {
    prev_ = prev;
  }
  memory_block *get_next() {
    return next_;
  }
  void set_next(memory_block *next) {
    next_ = next;
  }

  std::atomic<memory_block *> &get_offered_chain() {
    return offered_chain_;
  }

  std::atomic<unsigned short> &get_results_missing() {
    return results_missing_;
  }

  int get_depth() const noexcept {
    return depth_;
  }

  void reset_state() {
    offered_chain_.store(nullptr);
    results_missing_.store(2);
  }

 private:
  // Linked list property of memory blocks (a complete list represents a threads currently owned memory).
  // Each block knows its chain start to allow stealing a whole chain in O(1)
  // without the need to traverse back to the chain start.
  memory_block *prev_, *next_;

  // When blocked on this chain element, we need to know what other chain of memory we
  // got offered by the stealing thread.
  // For this we need the offered chain's element up to the point we can steal.
  std::atomic<memory_block *> offered_chain_;

  // Management for coordinating concurrent result writing and stealing.
  // The result count decides atomically who gets to execute the continuation
  // and who therefore get's to own this memory block chain.
  std::atomic<unsigned short> results_missing_;

  // Pointer to memory region reserved for the companion continuation.
  // Must be a buffer big enough to hold any continuation encountered in the program.
  // This memory is managed explicitly by the continuation manager and runtime system
  // (they need to make sure to always call de-constructors and never allocate two continuations).
  char *memory_buffer_;
  base_cont *continuation_;

  // These two are only helper properties helping with bugs during development.
  size_t memory_buffer_size_;
  bool memory_buffer_used_;

  // Each element stays at a fixed depth for the entire application run.
  // Swapping parts of a memory chain will not reorder it, as always parts of
  // the same size are exchanged.
  const int depth_;
};

}
}
}

#endif //PLS_INTERNAL_SCHEDULING_CONT_NODE_H_
