///|
/// Finite queue sizing result; capacity = servers + waiting_places. Targets
/// constrain attempted-customer blocking and admitted-customer abandonment.
pub(all) struct FiniteStaffing {
  servers : Int
  capacity : Int
  metrics : FiniteMetrics
  outcomes : FiniteOutcomes
} derive(Debug, ToJson)

///|
pub fn finite_staffing(
  arrival : Double,
  service : Double,
  waiting_places : Int,
  abandonment : Double,
  maximum_blocking : Double,
  maximum_abandonment : Double,
  maximum_servers : Int,
) -> FiniteStaffing raise QueueError {
  rate(arrival, "staffing arrival")
  rate(service, "staffing service")
  positive(service, "staffing service")
  rate(abandonment, "staffing abandonment")
  probability(maximum_blocking, "blocking target")
  probability(maximum_abandonment, "abandonment target")
  if waiting_places < 0 ||
    maximum_servers < 1 ||
    maximum_servers > 10000 ||
    waiting_places > 10000 - maximum_servers {
    raise Limit("staffing capacity must be at most 10000")
  }
  if maximum_servers.to_double() *
    (waiting_places + maximum_servers).to_double() >
    2.0e6 {
    raise Limit("finite staffing exceeds two million state evaluations")
  }
  for count in 1..<=maximum_servers {
    let q : FiniteQueue = {
      arrival,
      service,
      servers: count,
      capacity: count + waiting_places,
      abandonment,
    }
    let metrics = q.analyze()
    let outcomes = q.outcomes()
    if metrics.blocked_fraction <= maximum_blocking &&
      outcomes.abandoned_given_admission <= maximum_abandonment {
      return { servers: count, capacity: q.capacity, metrics, outcomes, }
    }
  }
  raise Infeasible("finite queue targets not met within server bound")
}

///|
pub extend FiniteStaffing with Debug::{to_repr}

///|
pub extend FiniteStaffing with ToJson::{to_json}