#pragma once
#include <atomic>

namespace co_context::detail {
#if defined(__clang__) || defined(__GNUC__) || defined(__GNUG__)
#if defined(__i386__) || defined(__x86_64__)
#define CO_CONTEXT_PAUSE() __builtin_ia32_pause()
#elif defined(__riscv)
#define CO_CONTEXT_PAUSE() __asm__ volatile("fence iorw, iorw")
#endif
#elif defined(_MSC_VER)
#define CO_CONTEXT_PAUSE() _mm_pause()
#else
#define CO_CONTEXT_PAUSE() __builtin_ia32_pause()
#endif

class spinlock final {
  private:
    std::atomic_bool occupied{false};

  public:
    void lock() noexcept;

    bool try_lock() noexcept;

    void unlock() noexcept;

    explicit spinlock() noexcept = default;
    ~spinlock() noexcept = default;

    spinlock(const spinlock &) = delete;
    spinlock(spinlock &&) = delete;
    spinlock &operator=(const spinlock &) = delete;
    spinlock &operator=(spinlock &&) = delete;
};

inline void spinlock::lock() noexcept {
    for (;;) {
        if (!occupied.exchange(true, std::memory_order_acquire)) {
            return;
        }
        while (occupied.load(std::memory_order_relaxed)) {
            CO_CONTEXT_PAUSE();
        }
    }
}

inline bool spinlock::try_lock() noexcept {
    return !occupied.load(std::memory_order_relaxed)
           && !occupied.exchange(true, std::memory_order_acquire);
}

inline void spinlock::unlock() noexcept {
    occupied.store(false, std::memory_order_release);
}

} // namespace co_context::detail
