
#ifndef PLS_LOCK_FREE_TRADED_CAS_FIELD_H_
#define PLS_LOCK_FREE_TRADED_CAS_FIELD_H_

#include <atomic>

#include "pls/internal/data_structures/stamped_split_integer.h"
#include "pls/internal/base/system_details.h"

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

struct traded_cas_field {
 public:
  // A traded cas field should always go through a specific state space.
  // The field is initially filled with a request to trade the task.
  // If this request is not met, the field is emptied out, keeping the stamp.
  // If this request is met, the field is filled with an traded in task.
  // Next, the traded in task is taken out of the field, leaving it empty, keeping the stamp.
  //
  // After this, the field can be re-used for trading at a different point.
  traded_cas_field() : stamp_{0}, trade_request_thread_id_{0}, traded_task_id_{0}, non_empty_flag_{0} {};

  void fill_with_trade_request(unsigned long stamp, unsigned long trading_thread_id) {
    PLS_ASSERT(is_empty(),
               "traded_cas_field must follow state transitions (to trade request)");

    non_empty_flag_ = 1;
    stamp_ = stamp;
    trade_request_thread_id_ = trading_thread_id + 1;
    traded_task_id_ = 0;
  }
  void make_empty() {
    PLS_ASSERT(is_filled_with_object() || is_filled_with_trade_request(),
               "traded_cas_field must follow state transitions (to empty)");

    non_empty_flag_ = 0;
  }
  void fill_with_task(unsigned long task_id) {
    PLS_ASSERT(is_filled_with_trade_request(),
               "traded_cas_field must follow state transitions (to task)");

    traded_task_id_ = task_id + 1;
  }

  [[nodiscard]] unsigned long get_stamp() const {
    return stamp_;
  }
  [[nodiscard]] unsigned long get_trade_request_thread_id() const {
    PLS_ASSERT(is_filled_with_trade_request() || is_filled_with_object(),
               "Must only read out the tag when the traded field contains one.");
    return trade_request_thread_id_ - 1;
  }

  [[nodiscard]] unsigned long get_task_id() const {
    PLS_ASSERT(is_filled_with_object(), "Must only read out the object when the traded field contains one.");
    return traded_task_id_ - 1;
  }

  [[nodiscard]] bool is_filled_with_trade_request() const {
    return non_empty_flag_ && trade_request_thread_id_ && !traded_task_id_;
  }
  [[nodiscard]] bool is_filled_with_object() const {
    return non_empty_flag_ && traded_task_id_;
  }
  [[nodiscard]] bool is_empty() const {
    return !non_empty_flag_;
  }

  bool operator==(const traded_cas_field &other) const {
    return this->stamp_ == other.stamp_ && this->traded_task_id_ == other.traded_task_id_
        && this->trade_request_thread_id_ == other.trade_request_thread_id_
        && this->non_empty_flag_ == other.non_empty_flag_;
  }
  bool operator!=(const traded_cas_field &other) const {
    return !((*this) == other);
  }

 private:
  base::system_details::cas_integer stamp_: base::system_details::CAS_SIZE / 2;
  base::system_details::cas_integer trade_request_thread_id_: base::system_details::CAS_SIZE / 4;
  base::system_details::cas_integer traded_task_id_: base::system_details::CAS_SIZE / 4 - 1;
  base::system_details::cas_integer non_empty_flag_: 1;
};
static_assert(sizeof(traded_cas_field) * 8 == base::system_details::CAS_SIZE, "Must only have lock free CAS objects");

}

#endif //PLS_LOCK_FREE_TRADED_CAS_FIELD_H_
