// Reduced OS wait policy used by "When a sleeping spinlock beats an atomic".
// The contended algorithm is the same. The scheduler operation is not.

#include <atomic>
#include <cstdint>

#if defined(_WIN32)
#include <windows.h>
#else
#include <time.h>
#endif

static inline void scheduler_backoff() noexcept {
#if defined(_WIN32)
  // Windows has no sub-tick sleep corresponding to nanosleep({0, 1}).
  // Sleep(1) may give up a full timer tick, so this lane uses the closest
  // scheduler handoff available to the benchmark.
  SwitchToThread();
#else
  const timespec request = {.tv_sec = 0, .tv_nsec = 1};
  nanosleep(&request, nullptr);
#endif
}

void backed_off_cas(std::atomic<std::uint64_t>& counter) noexcept {
  auto current = counter.load(std::memory_order_relaxed);
  while (!counter.compare_exchange_strong(
      current,
      current + std::uint64_t{1},
      std::memory_order_relaxed,
      std::memory_order_relaxed)) {
    scheduler_backoff();
  }
}

class sleeping_lock {
public:
  void lock() noexcept {
    for (;;) {
      for (std::uint32_t probe = std::uint32_t{0};
           probe != std::uint32_t{8};
           ++probe) {
        if (state_.load(std::memory_order_relaxed) == std::uint32_t{0} &&
            state_.exchange(
                std::uint32_t{1},
                std::memory_order_acquire) == std::uint32_t{0}) {
          return;
        }
      }
      scheduler_backoff();
    }
  }

  void unlock() noexcept {
    state_.store(std::uint32_t{0}, std::memory_order_release);
  }

private:
  std::atomic<std::uint32_t> state_{std::uint32_t{0}};
};
