///|
/// Mutable online counters for a stream of changes.
pub struct OnlineMetrics {
  mut changes : Int
  mut scalar_changes : Int
  mut vector_changes : Int
  mut real_changes : Int
  mut unknown_changes : Int
  mut first_time : Int64?
  mut last_time : Int64?
  mut checksum : Int64
} derive(Debug, Eq)

///|
/// Create an empty online metrics accumulator.
pub fn new_online_metrics() -> OnlineMetrics {
  {
    changes: 0,
    scalar_changes: 0,
    vector_changes: 0,
    real_changes: 0,
    unknown_changes: 0,
    first_time: None,
    last_time: None,
    checksum: 0L,
  }
}

///|
/// Add one change to an online accumulator.
pub fn OnlineMetrics::push(self : OnlineMetrics, change : Change) -> Unit {
  self.changes += 1
  match self.first_time {
    None => self.first_time = Some(change.time)
    _ => ()
  }
  self.last_time = Some(change.time)
  self.checksum += change.time
  match change.value {
    Scalar(value) => {
      self.scalar_changes += 1
      if !is_known_scalar(value) {
        self.unknown_changes += 1
      }
    }
    Vector(bits) => {
      self.vector_changes += 1
      if bits.contains_char('x') ||
        bits.contains_char('X') ||
        bits.contains_char('z') ||
        bits.contains_char('Z') {
        self.unknown_changes += 1
      }
    }
    Real(_) => self.real_changes += 1
  }
}

///|
/// Add all trace changes to an accumulator.
pub fn online_metrics(trace : Trace) -> OnlineMetrics {
  let result = new_online_metrics()
  for change in trace.changes {
    result.push(change)
  }
  result
}

///|
/// Merge two online metric accumulators.
pub fn OnlineMetrics::merge(
  self : OnlineMetrics,
  other : OnlineMetrics,
) -> OnlineMetrics {
  let first_time = match (self.first_time, other.first_time) {
    (Some(a), Some(b)) => Some(Int64::min(a, b))
    (Some(a), None) => Some(a)
    (None, Some(b)) => Some(b)
    (None, None) => None
  }
  let last_time = match (self.last_time, other.last_time) {
    (Some(a), Some(b)) => Some(Int64::max(a, b))
    (Some(a), None) => Some(a)
    (None, Some(b)) => Some(b)
    (None, None) => None
  }
  {
    changes: self.changes + other.changes,
    scalar_changes: self.scalar_changes + other.scalar_changes,
    vector_changes: self.vector_changes + other.vector_changes,
    real_changes: self.real_changes + other.real_changes,
    unknown_changes: self.unknown_changes + other.unknown_changes,
    first_time,
    last_time,
    checksum: self.checksum + other.checksum,
  }
}

///|
/// Render online metrics as JSON.
pub fn OnlineMetrics::to_json_string(self : OnlineMetrics) -> String {
  "{\"changes\":" +
  self.changes.to_string() +
  ",\"scalar\":" +
  self.scalar_changes.to_string() +
  ",\"vector\":" +
  self.vector_changes.to_string() +
  ",\"real\":" +
  self.real_changes.to_string() +
  ",\"unknown\":" +
  self.unknown_changes.to_string() +
  ",\"checksum\":" +
  self.checksum.to_string() +
  "}"
}

///|
/// Return the average time between online changes.
pub fn OnlineMetrics::mean_interval(self : OnlineMetrics) -> Double? {
  match (self.first_time, self.last_time) {
    (Some(first), Some(last)) if self.changes > 1 =>
      Some((last - first).to_double() / (self.changes - 1).to_double())
    _ => None
  }
}