
#ifndef PLS_INTERNAL_SCHEDULING_TRADED_CAS_FIELD_H_
#define PLS_INTERNAL_SCHEDULING_TRADED_CAS_FIELD_H_

#include <atomic>

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

namespace pls {
namespace internal {
namespace scheduling {

struct task;
struct traded_cas_field {
  static_assert(base::system_details::CACHE_LINE_SIZE >= 4,
                "Traded objects must not use their last address bits, as we use them for status flags."
                "As traded objects are usually cache aligned, we need big enough cache lines.");

  // Base size of our CAS integer/pointer
  static constexpr unsigned long CAS_SIZE = base::system_details::CAS_SIZE;

  // States of the integer (tag indicating current content)
  static constexpr unsigned long EMPTY_TAG = 0x0lu;
  static constexpr unsigned long STAMP_TAG = 0x1lu;
  static constexpr unsigned long TRADE_TAG = 0x2lu;

  // Bitmasks and shifts for cas_integer_, two variants:
  // cas_integer_ = traded object | tag
  // cas_integer_ = stamp | id | tag
  static constexpr unsigned long TAG_SIZE = 2ul;
  static constexpr unsigned long TAG_BITS = ~((~0x0ul) << TAG_SIZE);

  static constexpr unsigned long TRADED_OBJECT_SIZE = CAS_SIZE - TAG_SIZE;
  static constexpr unsigned long TRADED_OBJECT_SHIFT = TAG_SIZE;
  static constexpr unsigned long TRADE_OBJECT_BITS = ~((~0x0ul) << TRADED_OBJECT_SIZE) << TRADED_OBJECT_SHIFT;

  static constexpr unsigned long ID_SIZE = 10ul; // Up to 1024 cores
  static constexpr unsigned long ID_SHIFT = TAG_SIZE;
  static constexpr unsigned long ID_BITS = ~((~0x0ul) << ID_SIZE) << ID_SHIFT;

  static constexpr unsigned long STAMP_SIZE = CAS_SIZE - TAG_SIZE - ID_SIZE;
  static constexpr unsigned long STAMP_SHIFT = TAG_SIZE + ID_SIZE;
  static constexpr unsigned long STAMP_BITS = ~((~0x0ul) << STAMP_SIZE) << STAMP_SHIFT;

 public:
  void fill_with_stamp(unsigned long stamp, unsigned long deque_id) {
    cas_integer_ = (((stamp << STAMP_SHIFT) & STAMP_BITS) | ((deque_id << ID_SHIFT) & ID_BITS) | STAMP_TAG);
  }
  unsigned long get_stamp() {
    PLS_ASSERT(is_filled_with_stamp(), "Must only read out the tag when the traded field contains one.");
    return (((unsigned long) cas_integer_) & STAMP_BITS) >> STAMP_SHIFT;
  }
  unsigned long get_deque_id() {
    PLS_ASSERT(is_filled_with_stamp(), "Must only read out the tag when the traded field contains one.");
    return (((unsigned long) cas_integer_) & ID_BITS) >> ID_SHIFT;
  }
  bool is_filled_with_stamp() {
    return (((unsigned long) cas_integer_) & TAG_BITS) == STAMP_TAG;
  }

  void fill_with_trade_object(task *new_task) {
    PLS_ASSERT((((unsigned long) new_task) & TAG_BITS) == 0,
               "Must only store aligned objects in this data structure (last bits are needed for tag bit)");
    cas_integer_ = (((unsigned long) new_task) | TRADE_TAG);
  }
  task *get_trade_object() {
    PLS_ASSERT(is_filled_with_object(), "Must only read out the object when the traded field contains one.");
    return reinterpret_cast<task *>(((unsigned long) cas_integer_) & TRADE_OBJECT_BITS);
  }
  bool is_filled_with_object() {
    return (((unsigned long) cas_integer_) & TAG_BITS) == TRADE_TAG;
  }

  bool is_empty() {
    return (((unsigned long) cas_integer_) & TAG_BITS) == EMPTY_TAG;
  }

 private:
  base::system_details::cas_integer cas_integer_{};
};

}
}
}

#endif //PLS_INTERNAL_SCHEDULING_TRADED_CAS_FIELD_H_
