
#ifndef PLS_LOCK_FREE_TRADED_CAS_FIELD_H_
#define PLS_LOCK_FREE_TRADED_CAS_FIELD_H_

#include <atomic>

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

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

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 base::system_details::cas_integer CAS_SIZE = base::system_details::CAS_SIZE;

  // States of the integer (tag indicating current content)
  static constexpr base::system_details::cas_integer EMPTY_TAG = 0x0lu;
  static constexpr base::system_details::cas_integer STAMP_TAG = 0x1lu;
  static constexpr base::system_details::cas_integer TRADE_TAG = 0x2lu;

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

  static constexpr base::system_details::cas_integer TRADED_OBJECT_SIZE = CAS_SIZE - TAG_SIZE;
  static constexpr base::system_details::cas_integer TRADED_OBJECT_SHIFT = TAG_SIZE;
  static constexpr base::system_details::cas_integer
      TRADE_OBJECT_BITS = ~((~0x0ul) << TRADED_OBJECT_SIZE) << TRADED_OBJECT_SHIFT;

  static constexpr base::system_details::cas_integer ID_SIZE = 10ul; // Up to 1024 cores
  static constexpr base::system_details::cas_integer ID_SHIFT = TAG_SIZE;
  static constexpr base::system_details::cas_integer ID_BITS = ~((~0x0ul) << ID_SIZE) << ID_SHIFT;

  static constexpr base::system_details::cas_integer STAMP_SIZE = CAS_SIZE - TAG_SIZE - ID_SIZE;
  static constexpr base::system_details::cas_integer STAMP_SHIFT = TAG_SIZE + ID_SIZE;
  static constexpr base::system_details::cas_integer STAMP_BITS = ~((~0x0ul) << STAMP_SIZE) << STAMP_SHIFT;

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

  void fill_with_trade_object(task *new_task) {
    PLS_ASSERT((((base::system_details::cas_integer) new_task) & TAG_BITS) == 0,
               "Must only store aligned objects in this data structure (last bits are needed for tag bit)");
    cas_integer_ = (((base::system_details::cas_integer) 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 *>(((base::system_details::cas_integer) cas_integer_) & TRADE_OBJECT_BITS);
  }
  bool is_filled_with_object() {
    return (((base::system_details::cas_integer) cas_integer_) & TAG_BITS) == TRADE_TAG;
  }

  bool is_empty() {
    return (((base::system_details::cas_integer) cas_integer_) & TAG_BITS) == EMPTY_TAG;
  }

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

}

#endif //PLS_LOCK_FREE_TRADED_CAS_FIELD_H_
