
#ifndef PLS_WORK_STEALING_DEQUE_H_
#define PLS_WORK_STEALING_DEQUE_H_

#include <atomic>

#include "pls/internal/scheduling/thread_state.h"
#include "pls/internal/base/system_details.h"
#include "pls/internal/base/error_handling.h"
#include "pls/internal/helpers/split_integer.h"

#include "aligned_stack.h"

namespace pls {
namespace internal {
namespace data_structures {

using base::system_details::pointer_t;

// Integer split into two halfs, can be used in CAS operations
constexpr unsigned long HALF_CACHE_LINE = base::system_details::CACHE_LINE_SIZE / 2;
using cas_integer = helpers::split_integer<HALF_CACHE_LINE, HALF_CACHE_LINE>;
static unsigned long get_stamp(cas_integer n) { return n.left; }
static unsigned long get_offset(cas_integer n) { return n.right; }
static cas_integer set_stamp(cas_integer n, unsigned long new_value) {
  n.left = new_value;
  return n;
}

// Single Item in the deque
class work_stealing_deque_item {
  // Pointer to the actual data
  pointer_t data_;
  // Index (relative to stack base) to the next and previous element
  unsigned long next_item_;
  unsigned long previous_item_;

 public:
  work_stealing_deque_item() : data_{0}, next_item_{}, previous_item_{} {}

  template<typename Item>
  Item *data() {
    return reinterpret_cast<Item *>(data_);
  }

  template<typename Item>
  void set_data(Item *data) {
    data_ = reinterpret_cast<pointer_t >(data);
  }

  unsigned long next_item() const { return next_item_; }
  void set_next_item(unsigned long next_item) { next_item_ = next_item; }

  unsigned long previous_item() const { return previous_item_; }
  void set_previous_item(unsigned long previous_item) { previous_item_ = previous_item; }
};
static_assert(sizeof(work_stealing_deque_item) < base::system_details::CACHE_LINE_SIZE,
              "Work stealing deque relies on memory layout and requires cache lines to be longer than one 'work_stealing_deque_item' instance!");

template<typename Item>
class work_stealing_deque {
  // Deque 'takes over' stack and handles memory management while in use.
  // At any point in time the deque can stop using more memory and the stack can be used by other entities.
  aligned_stack *stack_;
  pointer_t base_pointer_;

  std::atomic<cas_integer> head_;
  std::atomic<cas_integer> tail_;
  cas_integer previous_tail_;

 public:
  using state = aligned_stack::state;

  explicit work_stealing_deque(aligned_stack *stack) : stack_{stack},
                                                       base_pointer_{0},
                                                       head_{cas_integer{}},
                                                       tail_{cas_integer{}},
                                                       previous_tail_{cas_integer{}} {
    reset_base_pointer();
  }
  work_stealing_deque(const work_stealing_deque &other) : stack_{other.stack_},
                                                          base_pointer_{other.base_pointer_},
                                                          head_{other.head_.load()},
                                                          tail_{other.tail_.load()},
                                                          previous_tail_{other.previous_tail_} {}

  void reset_base_pointer();
  work_stealing_deque_item *item_at(unsigned long offset);
  unsigned long current_stack_offset();

  template<typename T>
  std::pair<work_stealing_deque_item, T> *allocate_item(const T &new_item);

  template<typename T>
  Item *push_tail(const T &new_item);
  Item *pop_tail();
  Item *pop_head();

  void release_memory_until(state state);
  state save_state();
};

}
}
}
#include "work_stealing_deque_impl.h"

#endif //PLS_WORK_STEALING_DEQUE_H_
