
#ifndef PLS_WORK_STEALING_DEQUE_IMPL_H_
#define PLS_WORK_STEALING_DEQUE_IMPL_H_

#include <utility>
#include <new>

namespace pls {
namespace internal {
namespace data_structures {

template<typename Item>
void work_stealing_deque<Item>::reset_base_pointer() {
  base_pointer_ = reinterpret_cast<pointer_t >(stack_->save_state()); // Keep the base of our region in the stack
}

template<typename Item>
work_stealing_deque_item *work_stealing_deque<Item>::item_at(offset_t offset) {
  return reinterpret_cast<work_stealing_deque_item *>(base_pointer_
      + (base::system_details::CACHE_LINE_SIZE * offset));
}

template<typename Item>
offset_t work_stealing_deque<Item>::current_stack_offset() {
  return (stack_->save_state() - base_pointer_) / base::system_details::CACHE_LINE_SIZE;
}

template<typename Item>
template<typename T, typename ...ARGS>
std::pair<work_stealing_deque_item, T> *work_stealing_deque<Item>::allocate_item(ARGS &&... args) {
  // 'Union' type to push both on stack
  using pair_t = std::pair<work_stealing_deque_item, T>;
  // Allocate space on stack
  auto new_pair = reinterpret_cast<pair_t *>(stack_->push_bytes<pair_t>());
  // Initialize memory on stack
  new((void *) &(new_pair->first)) work_stealing_deque_item();
  new((void *) &(new_pair->second)) T(std::forward<ARGS>(args)...);

  return new_pair;
}

template<typename Item>
template<typename T, typename ...ARGS>
T *work_stealing_deque<Item>::push_tail(ARGS &&... args) {
  return push_tail_cb<T>([](T *) {}, std::forward<ARGS>(args)...);
}

template<typename Item>
template<typename T, typename Function, typename ...ARGS>
T *work_stealing_deque<Item>::push_tail_cb(const Function &after_creation, ARGS &&... args) {
  static_assert(std::is_same<Item, T>::value || std::is_base_of<Item, T>::value,
                "Must only push types of <Item> onto work_stealing_deque<Item>");

  offset_t local_tail = tail_;

  auto new_pair = allocate_item<T>(std::forward<ARGS>(args)...);
  after_creation(&(new_pair->second)); // callback for time after creation but before being visible to others

  // Prepare current tail to point to correct next items
  auto tail_deque_item = item_at(local_tail);
  tail_deque_item->set_data(&(new_pair->second));
  tail_deque_item->set_next_item(current_stack_offset());
  tail_deque_item->set_previous_item(previous_tail_);
  previous_tail_ = local_tail;

  // Linearization point, item appears after this write
  offset_t new_tail = current_stack_offset();
  tail_ = new_tail;

  return &(new_pair->second);
}

template<typename Item>
Item *work_stealing_deque<Item>::pop_tail() {
  offset_t local_tail = tail_;
  stamped_integer local_head = head_;

  if (local_tail <= local_head.value) {
    return nullptr; // EMPTY
  }

  work_stealing_deque_item *previous_tail_item = item_at(previous_tail_);
  offset_t new_tail = previous_tail_;
  previous_tail_ = previous_tail_item->previous_item();

  // Publish our wish to set the tail back
  tail_ = new_tail;
  // Get the state of local head AFTER we published our wish
  local_head = head_; // Linearization point, outside knows list is empty

  if (local_head.value < new_tail) {
    return previous_tail_item->data<Item>(); // Success, enough distance to other threads
  }

  if (local_head.value == new_tail) {
    stamped_integer new_head = stamped_integer{local_head.stamp + 1, new_tail};
    // Try competing with consumers by updating the head's stamp value
    if (head_.compare_exchange_strong(local_head, new_head)) {
      return previous_tail_item->data<Item>(); // SUCCESS, we won the competition with other threads
    }
  }

  // Some other thread either won the competition or it already set the head further than we are
  // before we even tried to compete with it.
  // Reset the queue into an empty state => head_ = tail_
  tail_ = local_head.value; // ...we give up to the other winning thread

  return nullptr; // EMPTY, we lost the competition with other threads
}

template<typename Item>
Item *work_stealing_deque<Item>::pop_head() {
  stamped_integer local_head = head_;
  offset_t local_tail = tail_;

  if (local_tail <= local_head.value) {
    return nullptr; // EMPTY
  }
  // Load info on current deque item.
  // In case we have a race with a new (aba) overwritten item at this position,
  // there has to be a competition over the tail -> the stamp increased and our next
  // operation will fail anyways!
  work_stealing_deque_item *head_deque_item = item_at(local_head.value);
  offset_t next_item_offset = head_deque_item->next_item();
  Item *head_data_item = head_deque_item->data<Item>();

  // We try to set the head to this new position.
  // Possible outcomes:
  // 1) no one interrupted us, we win this competition
  // 2) other thread took the head, we lose to this
  // 3) owning thread removed tail, we lose to this
  stamped_integer new_head = stamped_integer{local_head.stamp + 1, next_item_offset};
  if (head_.compare_exchange_strong(local_head, new_head)) {
    return head_data_item; // SUCCESS, we won the competition
  }

  return nullptr; // EMPTY, we lost the competition
}

template<typename Item>
void work_stealing_deque<Item>::release_memory_until(state state) {
  unsigned long item_offset = (state - base_pointer_) / base::system_details::CACHE_LINE_SIZE;

  stamped_integer local_head = head_;
  offset_t local_tail = tail_;

  stack_->reset_state(state);

  if (item_offset < local_tail) {
    tail_ = item_offset;
    if (local_head.value >= local_tail) {
      head_ = stamped_integer{local_head.stamp + 1, item_offset};
    }
  }
}

template<typename Item>
typename work_stealing_deque<Item>::state work_stealing_deque<Item>::save_state() {
  return stack_->save_state();
}

}
}
}

#endif //PLS_WORK_STEALING_DEQUE_IMPL_H_
