///|
/// Resource utilization and bottleneck metrics.
pub struct ResourceMetric {
  resource : Int
  capacity : Int
  total_load : Int
  peak_load : Int
  busy_slots : Int
  horizon : Int
}

///|
/// Build a metric from a profile.
pub fn resource_metric(
  resource : Int,
  capacity : Int,
  profile : Array[Int],
) -> ResourceMetric {
  let mut peak = 0
  let mut busy = 0
  for load in profile {
    if load > peak {
      peak = load
    }
    if load > 0 {
      busy += 1
    }
  }
  {
    resource,
    capacity,
    total_load: integer_sum(profile),
    peak_load: peak,
    busy_slots: busy,
    horizon: profile.length(),
  }
}

///|
/// Return peak utilization percentage.
pub fn ResourceMetric::peak_percent(self : ResourceMetric) -> Int {
  if self.capacity <= 0 {
    0
  } else {
    self.peak_load * 100 / self.capacity
  }
}

///|
/// Return average utilization percentage.
pub fn ResourceMetric::average_percent(self : ResourceMetric) -> Int {
  if self.capacity <= 0 || self.horizon == 0 {
    0
  } else {
    self.total_load * 100 / (self.capacity * self.horizon)
  }
}

///|
/// Return idle slot count.
pub fn ResourceMetric::idle_slots(self : ResourceMetric) -> Int {
  self.horizon - self.busy_slots
}

///|
/// Return whether capacity is exceeded.
pub fn ResourceMetric::overloaded(self : ResourceMetric) -> Bool {
  self.peak_load > self.capacity
}

///|
/// Return a bottleneck score.
pub fn ResourceMetric::bottleneck_score(self : ResourceMetric) -> Int {
  self.peak_percent() * 1000 + self.average_percent()
}

///|
/// Return a stable metric line.
pub fn ResourceMetric::describe(self : ResourceMetric) -> String {
  "resource=\{self.resource}, peak=\{self.peak_load}(\{self.peak_percent()}%), average=\{self.average_percent()}%, busy=\{self.busy_slots}, overloaded=\{self.overloaded()}"
}

///|
/// Build metrics for every resource of a timeline.
pub fn timeline_resource_metrics(
  timeline : ResourceTimeline,
) -> Array[ResourceMetric] {
  let result : Array[ResourceMetric] = []
  for resource in 0.. Array[Int] {
  let result : Array[Int] = []
  for metric in metrics {
    result.push(metric.resource)
  }
  for left in 0..
        metrics[result[left]].bottleneck_score() {
        let temporary = result[left]
        result[left] = result[right]
        result[right] = temporary
      }
    }
  }
  result
}

///|
/// Return total capacity across metrics.
pub fn total_resource_capacity(metrics : Array[ResourceMetric]) -> Int {
  let mut result = 0
  for metric in metrics {
    result += metric.capacity
  }
  result
}

///|
/// Return total load across metrics.
pub fn total_resource_load(metrics : Array[ResourceMetric]) -> Int {
  let mut result = 0
  for metric in metrics {
    result += metric.total_load
  }
  result
}

///|
/// Return overloaded resource ids.
pub fn overloaded_resources(metrics : Array[ResourceMetric]) -> Array[Int] {
  let result : Array[Int] = []
  for metric in metrics {
    if metric.overloaded() {
      result.push(metric.resource)
    }
  }
  result
}

///|
/// Return the resource with minimum idle time.
pub fn tightest_resource(metrics : Array[ResourceMetric]) -> Int? {
  if metrics.length() == 0 {
    return None
  }
  let mut result = metrics[0]
  for metric in metrics {
    if metric.idle_slots() < result.idle_slots() {
      result = metric
    }
  }
  Some(result.resource)
}

///|
/// Return the resource with maximum idle time.
pub fn loosest_resource(metrics : Array[ResourceMetric]) -> Int? {
  if metrics.length() == 0 {
    return None
  }
  let mut result = metrics[0]
  for metric in metrics {
    if metric.idle_slots() > result.idle_slots() {
      result = metric
    }
  }
  Some(result.resource)
}

///|
/// Return whether all resources stay below a utilization limit.
pub fn within_resource_limit(
  metrics : Array[ResourceMetric],
  limit_percent : Int,
) -> Bool {
  for metric in metrics {
    if metric.peak_percent() > limit_percent {
      return false
    }
  }
  true
}

///|
/// Return a scaled load balance score.
pub fn resource_balance_score(metrics : Array[ResourceMetric]) -> Int {
  if metrics.length() == 0 {
    return 0
  }
  let mut low = metrics[0].average_percent()
  let mut high = low
  for metric in metrics {
    if metric.average_percent() < low {
      low = metric.average_percent()
    }
    if metric.average_percent() > high {
      high = metric.average_percent()
    }
  }
  high - low
}

///|
/// Return a report of metrics.
pub fn resource_metrics_report(metrics : Array[ResourceMetric]) -> String {
  let builder = StringBuilder()
  for index, metric in metrics {
    if index > 0 {
      builder.write_char('\n')
    }
    builder.write_string(metric.describe())
  }
  builder.to_string()
}

///|
/// Return a stable metrics fingerprint.
pub fn resource_metrics_signature(metrics : Array[ResourceMetric]) -> Int {
  let mut result = 31
  for metric in metrics {
    result = result * 37 +
      metric.resource * 3 +
      metric.capacity * 5 +
      metric.total_load * 7 +
      metric.peak_load
  }
  result
}

///|
/// Return a capacity recommendation for a target utilization percentage.
pub fn recommended_capacity(
  metric : ResourceMetric,
  target_percent : Int,
) -> Int {
  if target_percent <= 0 {
    return metric.peak_load
  }
  (metric.peak_load * 100 + target_percent - 1) / target_percent
}