
#ifndef PLS_INTERNAL_SCHEDULING_TASK_TRADING_DEQUE_H_
#define PLS_INTERNAL_SCHEDULING_TASK_TRADING_DEQUE_H_

#include <atomic>
#include <utility>
#include <vector>

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

#include "pls/internal/data_structures/stamped_integer.h"

#include "pls/internal/scheduling/lock_free/task.h"

namespace pls::internal::scheduling::lock_free {

using namespace data_structures;

struct trading_deque_entry {
  std::atomic<task *> traded_task_{nullptr};
  std::atomic<unsigned long> forwarding_stamp_{};
};

/**
 * A work stealing deque (single produces/consumer at the end, multiple consumers at the start).
 * A task object can only be acquired by stealing consumers (from the start),
 * when they also offer a task to trade in for it.
 *
 * The exchange of 'goods' (here tasks) happens atomically at a linearization point.
 * This means that the owning thread always gets a tasks for each and every task that was
 * successfully stolen.
 *
 * As each task is associated with memory this suffices to exchange memory blocks needed for execution.
 */
class external_trading_deque {
 public:
  external_trading_deque(unsigned thread_id, size_t num_entries) : thread_id_(thread_id), entries_(num_entries) {}

  static task *peek_traded_object(task *target_task);
  static task *get_trade_object(task *target_task);

  /**
   * Pushes a task on the bottom of the deque.
   * The task itself wil be filled with the unique, synchronizing cas word.
   *
   * @param published_task The task to publish on the bottom of the deque.
   */
  void push_bot(task *published_task);

  /**
   * Tries to pop the last task on the deque.
   *
   * @return optional<task*> holding the popped task if successful.
   */
  task *pop_bot();

  struct peek_result {
    peek_result(task *top_task, stamped_integer top_pointer) : top_task_{std::move(top_task)},
                                                               top_pointer_{top_pointer} {};
    task *top_task_;
    stamped_integer top_pointer_;
  };

  /**
   * Peek at the current task on top of the deque.
   * This is required, as we need to look at the task to figure out what we trade in for it.
   * (Note: we could go without this by doing some tricks with top/bot pointers, but this
   * is simpler and also more flexible if the traded objects are not as trivial as currently).
   *
   * @return a peek result containing the optional top task (if present) and the current head pointer.
   */
  peek_result peek_top();

  /**
   * Tries to pop the task on top of the deque that was
   * previously observed by 'peeking' at the deque.
   *
   * Returns the task if successful, returns nothing if
   * either the peeked task is no longer at the top of the deque
   * or another thread interfered and 'won' the task.
   *
   * @return optional<task*> holding the popped task if successful.
   */
  task *pop_top(task *offered_task, peek_result peek_result);

 private:
  void reset_bot_and_top();

  // info on this deque
  unsigned thread_id_;
  std::vector<trading_deque_entry> entries_;

  // fields for stealing/interacting
  stamped_integer bot_internal_{0, 0};

  PLS_CACHE_ALIGN std::atomic<stamped_integer> top_{{0, 0}};
  PLS_CACHE_ALIGN std::atomic<size_t> bot_{0};
};

}

#endif //PLS_INTERNAL_SCHEDULING_TASK_TRADING_DEQUE_H_
