
#ifndef PLS_WORK_STEALING_DEQUE_H_
#define PLS_WORK_STEALING_DEQUE_H_

#include <atomic>

#include "pls/internal/base/error_handling.h"
#include "stamped_integer.h"

#include "aligned_stack.h"

namespace pls {
namespace internal {
namespace scheduling {
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 deque_offset = 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 atomics again.
  // Pointer to the actual data
  std::atomic<pointer_t> data_;
  // Index (relative to stack base) to the next and previous element
  std::atomic<deque_offset> next_item_;
  deque_offset 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);
  }

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

  deque_offset previous_item() const { return previous_item_; }
  void set_previous_item(deque_offset previous_item) { previous_item_ = previous_item; }
};

template<typename Task>
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_;

  std::atomic<stamped_integer> head_;
  std::atomic<deque_offset> tail_;
  deque_offset previous_tail_;

  Task *last_pushed_task_;

 public:
  explicit work_stealing_deque(aligned_stack *stack) : stack_{stack},
                                                       head_{stamped_integer{0, 0}},
                                                       tail_{0},
                                                       previous_tail_{0},
                                                       last_pushed_task_{0} {}

  template<typename T, typename ...ARGS>
  T *push_task(ARGS &&... args);
  template<typename T, typename ...ARGS>
  T *push_object(ARGS &&... args);
  void *push_bytes(size_t size);
  void publish_last_task();

  Task *pop_local_task(bool &cas_fail_out);
  Task *pop_local_task();
  Task *pop_external_task(bool &cas_fail_out);
  Task *pop_external_task();

  void reset_offset(deque_offset offset);
  deque_offset save_offset();

 private:
  work_stealing_deque_item *item_at(deque_offset offset);
  deque_offset current_stack_offset();
};

}
}
}
}
#include "work_stealing_deque_impl.h"

#endif //PLS_WORK_STEALING_DEQUE_H_
