///|
/// One FCFS exponential service center in an open Jackson network.
pub(all) struct JacksonNode {
  service : Double
  servers : Int
} derive(Debug, ToJson)

///|
/// Open single-class Jackson network. routing[i][j] is the probability that
/// a completion at node i next visits node j; missing row mass exits the system.
pub(all) struct JacksonNetwork {
  nodes : Array[JacksonNode]
  external : Array[Double]
  routing : Array[Array[Double]]
} derive(Debug, ToJson)

///|
/// Stable traffic and M/M/c results for one network node.
pub(all) struct JacksonNodeMetrics {
  arrival : Double
  utilization : Double
  delay_probability : Double
  mean_queue : Double
  mean_system : Double
  mean_wait : Double
  mean_response : Double
} derive(Debug, ToJson)

///|
/// Aggregate open-network result. Response time is per external customer,
/// including all internal visits and feedback.
pub(all) struct JacksonMetrics {
  nodes : Array[JacksonNodeMetrics]
  external_arrival : Double
  total_mean_customers : Double
  mean_external_response : Double
  relative_flow_residual : Double
} derive(Debug, ToJson)

///|
fn magnitude(value : Double) -> Double {
  if value < 0.0 {
    -value
  } else {
    value
  }
}

///|
fn JacksonNetwork::validate(self : JacksonNetwork) -> Unit raise QueueError {
  let n = self.nodes.length()
  if n == 0 || n > 128 {
    raise Limit("Jackson network must contain 1..128 nodes")
  }
  if self.external.length() != n || self.routing.length() != n {
    raise Invalid("external vector and routing matrix must match node count")
  }
  let mut external_total = 0.0
  for i in 0.. 1.0 {
      raise Invalid("routing row probabilities must sum to at most one")
    }
  }
  rate(external_total, "total external arrival rate")
  self.validate_exit_reachability()
}

///|
/// For a finite substochastic routing matrix, every state reaching a row with
/// positive exit probability implies eventual absorption from every node.
fn JacksonNetwork::validate_exit_reachability(
  self : JacksonNetwork,
) -> Unit raise QueueError {
  let n = self.nodes.length()
  let can_exit = Array::make(n, false)
  for i in 0.. 0.0 && can_exit[j] {
            can_exit[i] = true
            changed = true
          }
          j += 1
        }
      }
    }
    if !changed {
      break
    }
  }
  for i in 0.. Array[Double] raise QueueError {
  self.validate()
  let n = self.nodes.length()
  let augmented : Array[Array[Double]] = Array::make(n, [])
  for i in 0.. pivot_size {
        pivot_size = candidate
        pivot_row = r
      }
    }
    if pivot_size <= 1.0e-14 {
      raise Numerical("traffic equation is singular or ill-conditioned")
    }
    if pivot_row != col {
      let temporary = augmented[col]
      augmented[col] = augmented[pivot_row]
      augmented[pivot_row] = temporary
    }
    let pivot = augmented[col][col]
    for r in (col + 1).. 1.0e-9 {
    raise Numerical("traffic equation residual exceeds tolerance")
  }
  traffic
}

///|
fn JacksonNetwork::traffic_residual(
  self : JacksonNetwork,
  traffic : Array[Double],
) -> Double {
  let n = self.nodes.length()
  let mut worst = 0.0
  for i in 0.. worst {
      worst = relative
    }
  }
  worst
}

///|
/// Analyze an open Jackson network. Every node must have a path to exit and
/// each resulting M/M/c center must be strictly stable.
pub fn JacksonNetwork::analyze(
  self : JacksonNetwork,
) -> JacksonMetrics raise QueueError {
  let arrivals = self.traffic()
  let n = self.nodes.length()
  let node_metrics = Array::make(n, {
    arrival: 0.0,
    utilization: 0.0,
    delay_probability: 0.0,
    mean_queue: 0.0,
    mean_system: 0.0,
    mean_wait: 0.0,
    mean_response: 0.0,
  })
  let mut total_customers = 0.0
  for i in 0.. Array[Double] raise QueueError {
  self.traffic()
}