
#ifndef PLS_WORK_STEALING_DEQUE_H_
#define PLS_WORK_STEALING_DEQUE_H_

#include <atomic>

#include "pls/internal/base/error_handling.h"
#include "pls/internal/data_structures/stamped_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
using data_structures::stamped_integer;
using offset_t = stamped_integer::member_t;

// Single Item in the deque
class work_stealing_deque_item {
  // TODO: In our opinion these atomic's are a pure formality to make the thread sanitizer happy,
  //       as the race occurs in 'pop_head', where ALL CASES reading a corrupt/old value are cases
  //       where the next CAS fails anywas, thus making these corrupted values have no influence on
  //       the overall program execution.
  //       ==> If we find performance problems in this queue, try removing the atoimcs again.
  // Pointer to the actual data
  std::atomic<pointer_t> data_;
  // Index (relative to stack base) to the next and previous element
  std::atomic<offset_t> next_item_;
  offset_t previous_item_;

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

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

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

  offset_t next_item() const { return next_item_.load(); }
  void set_next_item(offset_t next_item) { next_item_ = next_item; }

  offset_t previous_item() const { return previous_item_; }
  void set_previous_item(offset_t 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<stamped_integer> head_;
  std::atomic<offset_t> tail_;
  offset_t previous_tail_;

 public:
  using state = aligned_stack::state;

  explicit work_stealing_deque(aligned_stack *stack) : stack_{stack},
                                                       base_pointer_{0},
                                                       head_{stamped_integer{0, 0}},
                                                       tail_{0},
                                                       previous_tail_{0} {
    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_} {}

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

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

 private:
  void reset_base_pointer();
  work_stealing_deque_item *item_at(offset_t offset);
  offset_t current_stack_offset();

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

}
}
}
#include "work_stealing_deque_impl.h"

#endif //PLS_WORK_STEALING_DEQUE_H_
