///|
pub(all) enum AtomicRmwOp {
  Add
  Sub
  And
  Or
  Xor
  Exchange
}

///|
fn Memory::checked_atomic_descriptor(
  self : Memory,
  offset : Int64,
  width : Int32,
) -> @native_bridge.MemoryDescriptor raise RuntimeError {
  let descriptor = self.require_desc_ptr()
  if width != 1 && width != 2 && width != 4 && width != 8 {
    raise Unreachable
  }
  if offset % width.to_int64() != 0L {
    raise UnalignedAtomic
  }
  self.check_bounds_at(offset, width.to_int64())
  descriptor
}

///|
pub fn Memory::atomic_load(
  self : Memory,
  offset : Int64,
  width : Int32,
) -> Int64 raise RuntimeError {
  @native_bridge.memory_atomic_load(
    self.checked_atomic_descriptor(offset, width),
    offset,
    width,
  )
}

///|
pub fn Memory::atomic_store(
  self : Memory,
  offset : Int64,
  width : Int32,
  value : Int64,
) -> Unit raise RuntimeError {
  @native_bridge.memory_atomic_store(
    self.checked_atomic_descriptor(offset, width),
    offset,
    width,
    value,
  )
}

///|
pub fn Memory::atomic_rmw(
  self : Memory,
  operation : AtomicRmwOp,
  offset : Int64,
  width : Int32,
  value : Int64,
) -> Int64 raise RuntimeError {
  let code = match operation {
    Add => 0
    Sub => 1
    And => 2
    Or => 3
    Xor => 4
    Exchange => 5
  }
  @native_bridge.memory_atomic_rmw(
    self.checked_atomic_descriptor(offset, width),
    offset,
    width,
    code,
    value,
  )
}

///|
pub fn Memory::atomic_compare_exchange(
  self : Memory,
  offset : Int64,
  width : Int32,
  expected : Int64,
  replacement : Int64,
) -> Int64 raise RuntimeError {
  @native_bridge.memory_atomic_compare_exchange(
    self.checked_atomic_descriptor(offset, width),
    offset,
    width,
    expected,
    replacement,
  )
}

///|
pub fn atomic_fence() -> Unit {
  @native_bridge.atomic_fence()
}

///|
/// Register a wait without blocking the embedding thread. The returned owner
/// keeps the native shared memory live until cancellation or finalization.
pub fn Memory::atomic_wait_begin(
  self : Memory,
  offset : Int64,
  width : Int32,
  expected : Int64,
  timeout : Int64,
) -> @native_bridge.NativeAtomicWait raise RuntimeError {
  if width != 4 && width != 8 {
    raise Unreachable
  }
  let descriptor = self.checked_atomic_descriptor(offset, width)
  if !self.is_shared {
    raise AtomicWaitNonSharedMemory
  }
  match
    @native_bridge.atomic_wait_begin(
      descriptor, offset, width, expected, timeout,
    ) {
    Some(waiter) => waiter
    None => raise OutOfMemory
  }
}

///|
pub fn Memory::atomic_notify(
  self : Memory,
  offset : Int64,
  count : UInt32,
) -> UInt32 raise RuntimeError {
  @native_bridge.atomic_notify(
    self.checked_atomic_descriptor(offset, 4),
    offset,
    count.reinterpret_as_int(),
  ).reinterpret_as_uint()
}