///|
/// Extract scalar transitions for one signal.
pub fn edges(trace : Trace, signal_id : String) -> Array[EdgeEvent] {
  let result : Array[EdgeEvent] = []
  let mut previous : Char? = None
  for change in trace.changes {
    if change.id != signal_id {
      continue
    }
    match change.value {
      Scalar(current) =>
        match previous {
          Some(before) if before != current =>
            result.push({
              signal_id,
              time: change.time,
              before,
              after: current,
              kind: edge_kind(before, current),
            })
          _ => ()
        }
      _ => previous = None
    }
    match change.value {
      Scalar(current) => previous = Some(current)
      _ => previous = None
    }
  }
  result
}

///|
/// Extract all scalar transitions, grouped by signal identifier.
pub fn all_edges(trace : Trace) -> Array[(String, Array[EdgeEvent])] {
  trace.signals.map(fn(signal) { (signal.id, edges(trace, signal.id)) })
}

///|
/// Count rising, falling, and non-binary transitions.
pub fn edge_counts(trace : Trace, signal_id : String) -> (Int, Int, Int) {
  let mut rising = 0
  let mut falling = 0
  let mut other = 0
  for event in edges(trace, signal_id) {
    match event.kind {
      Rising => rising += 1
      Falling => falling += 1
      _ => other += 1
    }
  }
  (rising, falling, other)
}

///|
/// Convert changes into stable scalar value intervals.
pub fn value_intervals(
  trace : Trace,
  signal_id : String,
) -> Array[ValueInterval] {
  let result : Array[ValueInterval] = []
  let mut active : ValueInterval? = None
  for change in trace.changes {
    if change.id != signal_id {
      continue
    }
    match active {
      Some(interval) => {
        let end = Int64::max(interval.start, change.time)
        result.push({
          signal_id,
          start: interval.start,
          end,
          value: interval.value,
        })
      }
      None => ()
    }
    active = Some({
      signal_id,
      start: change.time,
      end: trace.end_time,
      value: change.value,
    })
  }
  match active {
    Some(interval) =>
      result.push({
        signal_id,
        start: interval.start,
        end: Int64::max(interval.start, trace.end_time),
        value: interval.value,
      })
    None => ()
  }
  result
}

///|
/// Return intervals with a known scalar level and positive duration.
pub fn driven_intervals(
  trace : Trace,
  signal_id : String,
) -> Array[ValueInterval] {
  value_intervals(trace, signal_id).filter(fn(interval) {
    let known = match interval.value {
      Scalar(value) => is_known_scalar(value)
      _ => false
    }
    interval.end > interval.start && known
  })
}

///|
/// Return pulse widths for scalar high and low levels.
pub fn pulse_widths(trace : Trace, signal_id : String) -> Array[(Char, Int64)] {
  driven_intervals(trace, signal_id).filter_map(fn(interval) {
    match interval.value {
      Scalar(value) => Some((value, interval.end - interval.start))
      _ => None
    }
  })
}

///|
/// Return the intervals occupied by a particular scalar value.
pub fn intervals_for_value(
  trace : Trace,
  signal_id : String,
  value : Char,
) -> Array[ValueInterval] {
  value_intervals(trace, signal_id).filter(fn(interval) {
    match interval.value {
      Scalar(current) => current == value
      _ => false
    }
  })
}

///|
/// Estimate period and frequency from rising edges.
pub fn frequency_estimate(
  trace : Trace,
  signal_id : String,
) -> FrequencyEstimate {
  let rising_times : Array[Int64] = []
  for event in edges(trace, signal_id) {
    if event.kind is Rising {
      rising_times.push(event.time)
    }
  }
  let periods : Array[Int64] = []
  if rising_times.length() > 1 {
    for i in 1.. Some(Int64::min(value, item))
      None => Some(item)
    }
  })
  let max_period = periods.fold(init=None, fn(current, item) {
    match current {
      Some(value) => Some(Int64::max(value, item))
      None => Some(item)
    }
  })
  let frequency = match mean {
    Some(value) if value > 0.0 => Some(1.0 / value)
    _ => None
  }
  {
    signal_id,
    edge_count: rising_times.length(),
    period_count: periods.length(),
    mean_period: mean,
    minimum_period: min_period,
    maximum_period: max_period,
    frequency,
  }
}