#pragma once
#include "machine.h"
#include <concepts>
#include <cstdint>
#include <emscripten/atomic.h>
#include <functional>

class alignas(CACHE_LINE_SIZE) Grip {
  std::function<bool()> realtimePredicate;
  alignas(CACHE_LINE_SIZE) uint32_t loc;

public:
  Grip(const Grip &) = delete;
  Grip &operator=(const Grip &) = delete;
  Grip(Grip &&) = delete;
  Grip &operator=(Grip &&) = delete;

  Grip(std::function<bool()> &&pred)
      : realtimePredicate(std::move(pred)), loc(0) {}

  template <typename F>
    requires requires(F &&f) {
      { f() } -> std::same_as<bool>;
    }
  void with(F &&f) {
    // try once optimistically
    if (f()) {
      return;
    }
    bool realtime = realtimePredicate();
    if (realtime) {
      // increment count of realtime threads using this grip
      emscripten_atomic_add_u32(&loc, 1);
    }
    while (true) {
      if (!realtime) {
        // block until no more realtime threads are using this grip
        while (true) {
          const auto currentVal = emscripten_atomic_load_u32(&loc);
          if (currentVal == 0) {
            break;
          }
          emscripten_atomic_wait_u32(&loc, currentVal,
                                     ATOMICS_WAIT_DURATION_INFINITE);
        }
      }
      if (f()) {
        break;
      }
    }
    if (realtime) {
      // decrement count of realtime threads using this grip
      emscripten_atomic_sub_u32(&loc, 1);
      // wake up all threads waiting for this grip to become available again
      emscripten_atomic_notify(&loc, EMSCRIPTEN_NOTIFY_ALL_WAITERS);
    }
  }
};