///|
pub type Threads = MultithreadPlatform

///|
pub struct SharedStartArgDecoded {
  memory_id : Int
  local_arg : Int
}

///|
fn shared_start_arg_unit() -> Int {
  1_048_576
}

///|
fn shared_start_arg_max_local() -> Int {
  shared_start_arg_unit() - 1
}

///|
pub fn threads_for_backend(backend_kind : ThreadBackendKind) -> Threads {
  multithread_platform_for_backend(backend_kind)
}

///|
pub fn threads_auto() -> Threads {
  multithread_platform_auto()
}

///|
pub fn threads_backend_kind(threads : Threads) -> ThreadBackendKind {
  threads.backend_kind
}

///|
pub fn threads_spawn(
  threads : Threads,
  entry_id : EntryId,
  start_arg : StartArg,
  options? : SpawnOptions = spawn_options(),
) -> Result[ThreadId, ThreadError] {
  (threads.thread.spawn)(spawn_spec(entry_id, start_arg, options~))
}

///|
pub fn threads_spawn_registered(
  threads : Threads,
  entry : RegisteredEntry,
  start_arg : StartArg,
  options? : SpawnOptions = spawn_options(),
) -> Result[ThreadId, ThreadError] {
  (threads.thread.spawn)(spawn_spec_registered(entry, start_arg, options~))
}

///|
pub async fn threads_join(
  threads : Threads,
  thread_id : ThreadId,
  poll_interval_ms? : Int = 1,
) -> Result[Int, ThreadError] {
  thread_join_portable(threads, thread_id, poll_interval_ms~)
}

///|
pub fn threads_join_blocking(
  threads : Threads,
  thread_id : ThreadId,
) -> Result[Int, ThreadError] {
  if threads.backend_kind == backend_web_worker() {
    Err(
      @types.Unsupported(
        "threads_join_blocking: use threads_join(...) on web_worker backend",
      ),
    )
  } else {
    thread_join(threads, thread_id)
  }
}

///|
pub async fn threads_spawn_join(
  threads : Threads,
  entry_id : EntryId,
  start_arg : StartArg,
  options? : SpawnOptions = spawn_options(),
  poll_interval_ms? : Int = 1,
) -> Result[Int, ThreadError] {
  thread_spawn_join_portable(
    threads,
    spawn_spec(entry_id, start_arg, options~),
    poll_interval_ms~,
  )
}

///|
pub async fn threads_spawn_join_registered(
  threads : Threads,
  entry : RegisteredEntry,
  start_arg : StartArg,
  options? : SpawnOptions = spawn_options(),
  poll_interval_ms? : Int = 1,
) -> Result[Int, ThreadError] {
  thread_spawn_join_portable(
    threads,
    spawn_spec_registered(entry, start_arg, options~),
    poll_interval_ms~,
  )
}

///|
pub fn threads_spawn_join_blocking(
  threads : Threads,
  entry_id : EntryId,
  start_arg : StartArg,
  options? : SpawnOptions = spawn_options(),
) -> Result[Int, ThreadError] {
  match threads_spawn(threads, entry_id, start_arg, options~) {
    Ok(thread_id) => threads_join_blocking(threads, thread_id)
    Err(err) => Err(err)
  }
}

///|
pub fn threads_spawn_join_registered_blocking(
  threads : Threads,
  entry : RegisteredEntry,
  start_arg : StartArg,
  options? : SpawnOptions = spawn_options(),
) -> Result[Int, ThreadError] {
  match threads_spawn_registered(threads, entry, start_arg, options~) {
    Ok(thread_id) => threads_join_blocking(threads, thread_id)
    Err(err) => Err(err)
  }
}

///|
pub fn shared_start_arg_encode(
  memory_id : Int,
  local_arg : Int,
) -> Result[Int, ThreadError] {
  if memory_id < 0 {
    Err(
      @types.InvalidArgument("shared_start_arg_encode: memory_id must be >= 0"),
    )
  } else if local_arg < 0 || local_arg > shared_start_arg_max_local() {
    Err(
      @types.InvalidArgument(
        "shared_start_arg_encode: local_arg must be in [0, 1048575]",
      ),
    )
  } else {
    Ok(memory_id * shared_start_arg_unit() + local_arg)
  }
}

///|
pub fn shared_start_arg_decode(encoded : Int) -> SharedStartArgDecoded {
  if encoded < 0 {
    { memory_id: 0, local_arg: 0 }
  } else {
    {
      memory_id: encoded / shared_start_arg_unit(),
      local_arg: encoded % shared_start_arg_unit(),
    }
  }
}