///|
pub struct ServiceAsyncQueue {
  mut next_value : Int
  mut pending : Array[@backend_host.HostPendingServiceRequest]
  mut in_flight : Array[@backend_host.HostPendingServiceRequest]
  mut completed : Array[@backend_host.HostCompletedServiceResponse]
  priv mut next_completion_handler_value : Int
  priv mut completion_handlers : Array[HostServiceCompletionHandler]
}

///|
priv struct HostServiceCompletionHandler {
  token : Int
  id : @backend_host.HostServiceRequestId
  handle : (@backend_host.HostCompletedServiceResponse) -> Unit
}

///|
pub fn ServiceAsyncQueue::new() -> ServiceAsyncQueue {
  {
    next_value: 1,
    pending: [],
    in_flight: [],
    completed: [],
    next_completion_handler_value: 1,
    completion_handlers: [],
  }
}

///|
pub fn ServiceAsyncQueue::pending_count(self : ServiceAsyncQueue) -> Int {
  self.pending.length()
}

///|
pub fn ServiceAsyncQueue::completed_count(self : ServiceAsyncQueue) -> Int {
  self.completed.length()
}

///|
pub fn ServiceAsyncQueue::in_flight_count(self : ServiceAsyncQueue) -> Int {
  self.in_flight.length()
}

///|
pub fn ServiceAsyncQueue::outstanding_count(self : ServiceAsyncQueue) -> Int {
  self.pending.length() + self.in_flight.length()
}

///|
pub fn ServiceAsyncQueue::submit(
  self : ServiceAsyncQueue,
  request : @backend_host.HostServiceRequest,
) -> @backend_host.HostServiceResponse {
  let id = @backend_host.HostServiceRequestId::new(self.next_value)
  self.next_value = self.next_value + 1
  self.pending.push({ id, request, })
  Pending(id)
}

///|
pub fn ServiceAsyncQueue::drain_pending(
  self : ServiceAsyncQueue,
) -> Array[@backend_host.HostPendingServiceRequest] {
  let pending = self.pending
  for request in pending {
    self.in_flight.push(request)
  }
  self.pending = []
  pending
}

///|
pub fn ServiceAsyncQueue::drain_completed(
  self : ServiceAsyncQueue,
) -> Array[@backend_host.HostCompletedServiceResponse] {
  let completed = self.completed
  self.completed = []
  completed
}

///|
pub fn ServiceAsyncQueue::complete(
  self : ServiceAsyncQueue,
  id : @backend_host.HostServiceRequestId,
  response : @backend_host.HostServiceResponse,
) -> Bool {
  let mut matched = false
  let mut request : @backend_host.HostServiceRequest? = None
  let remaining : Array[@backend_host.HostPendingServiceRequest] = []
  for pending in self.pending {
    if !matched && pending.id == id {
      matched = true
      request = Some(pending.request)
    } else {
      remaining.push(pending)
    }
  }
  self.pending = remaining
  if !matched {
    let remaining_in_flight : Array[@backend_host.HostPendingServiceRequest] = []
    for pending in self.in_flight {
      if !matched && pending.id == id {
        matched = true
        request = Some(pending.request)
      } else {
        remaining_in_flight.push(pending)
      }
    }
    self.in_flight = remaining_in_flight
  }
  match request {
    Some(request) => {
      let completion : @backend_host.HostCompletedServiceResponse = {
        id,
        request,
        response,
      }
      if !self.dispatch_completion_handlers(completion) {
        self.completed.push(completion)
      }
      true
    }
    None => false
  }
}

///|
pub fn ServiceAsyncQueue::on_completed(
  self : ServiceAsyncQueue,
  id : @backend_host.HostServiceRequestId,
  handle : (@backend_host.HostCompletedServiceResponse) -> Unit,
) -> Bool {
  let (registered, _) = self.on_completed_with_cleanup(id, handle)
  registered
}

///|
fn ServiceAsyncQueue::on_completed_with_cleanup(
  self : ServiceAsyncQueue,
  id : @backend_host.HostServiceRequestId,
  handle : (@backend_host.HostCompletedServiceResponse) -> Unit,
) -> (Bool, (() -> Unit)?) {
  match self.take_completed(id) {
    Some(completion) => {
      handle(completion)
      (true, None)
    }
    None =>
      if self.has_outstanding(id) {
        let token = self.next_completion_handler_value
        self.next_completion_handler_value = self.next_completion_handler_value +
          1
        self.completion_handlers.push({ token, id, handle, })
        (true, Some(() => ignore(self.cancel_completion_handler(token))))
      } else {
        (false, None)
      }
  }
}

///|
fn ServiceAsyncQueue::dispatch_completion_handlers(
  self : ServiceAsyncQueue,
  completion : @backend_host.HostCompletedServiceResponse,
) -> Bool {
  let handlers : Array[HostServiceCompletionHandler] = []
  let remaining : Array[HostServiceCompletionHandler] = []
  for handler in self.completion_handlers {
    if handler.id == completion.id {
      handlers.push(handler)
    } else {
      remaining.push(handler)
    }
  }
  self.completion_handlers = remaining
  for handler in handlers {
    (handler.handle)(completion)
  }
  !handlers.is_empty()
}

///|
fn ServiceAsyncQueue::cancel_completion_handler(
  self : ServiceAsyncQueue,
  token : Int,
) -> Bool {
  let mut cancelled = false
  let remaining : Array[HostServiceCompletionHandler] = []
  for handler in self.completion_handlers {
    if !cancelled && handler.token == token {
      cancelled = true
    } else {
      remaining.push(handler)
    }
  }
  self.completion_handlers = remaining
  cancelled
}

///|
fn ServiceAsyncQueue::has_outstanding(
  self : ServiceAsyncQueue,
  id : @backend_host.HostServiceRequestId,
) -> Bool {
  self.pending.any(request => request.id == id) ||
  self.in_flight.any(request => request.id == id)
}

///|
pub fn ServiceAsyncQueue::reject(
  self : ServiceAsyncQueue,
  id : @backend_host.HostServiceRequestId,
  reason : String,
) -> Bool {
  self.complete(id, Unavailable(reason))
}

///|
/// Cancel one outstanding request. Completion is still delivered through the
/// normal exactly-once path so app subscriptions observe the cancellation.
pub fn ServiceAsyncQueue::cancel(
  self : ServiceAsyncQueue,
  id : @backend_host.HostServiceRequestId,
  reason : String,
) -> Bool {
  self.reject(id, reason)
}

///|
/// Drop every outstanding request and completion handler during host teardown.
/// Subsequent completions are rejected because their request ids no longer
/// exist in either pending or in-flight state.
pub fn ServiceAsyncQueue::cancel_all(self : ServiceAsyncQueue) -> Int {
  let count = self.outstanding_count()
  self.pending = []
  self.in_flight = []
  self.completed = []
  self.completion_handlers = []
  count
}

///|
pub fn ServiceAsyncQueue::take_completed(
  self : ServiceAsyncQueue,
  id : @backend_host.HostServiceRequestId,
) -> @backend_host.HostCompletedServiceResponse? {
  let mut found : @backend_host.HostCompletedServiceResponse? = None
  let remaining : Array[@backend_host.HostCompletedServiceResponse] = []
  for completed in self.completed {
    match found {
      None =>
        if completed.id == id {
          found = Some(completed)
        } else {
          remaining.push(completed)
        }
      Some(_) => remaining.push(completed)
    }
  }
  self.completed = remaining
  found
}

///|