///|
/// Find a signal by its VCD identifier.
pub fn find_signal(trace : Trace, signal_id : String) -> Signal? {
  for signal in trace.signals {
    if signal.id == signal_id {
      return Some(signal)
    }
  }
  None
}

///|
/// Find a signal by its fully-qualified hierarchical path.
pub fn find_signal_path(trace : Trace, path : String) -> Signal? {
  for signal in trace.signals {
    if signal_path(signal) == path {
      return Some(signal)
    }
  }
  None
}

///|
/// Return the last value observed at or before a timestamp.
pub fn value_at(trace : Trace, signal_id : String, timestamp : Int64) -> Value? {
  let mut result : Value? = None
  for change in trace.changes {
    if change.id == signal_id && change.time <= timestamp {
      result = Some(change.value)
    }
  }
  result
}

///|
/// Return changes in the inclusive time window `[start, end]`.
pub fn changes_between(
  trace : Trace,
  signal_id : String,
  start~ : Int64,
  end~ : Int64,
) -> Array[Change] {
  trace.changes.filter(fn(change) {
    change.id == signal_id && change.time >= start && change.time <= end
  })
}

///|
/// Return timestamps at which a scalar signal changed logical state.
pub fn transition_times(trace : Trace, signal_id : String) -> Array[Int64] {
  let result : Array[Int64] = []
  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(change.time)
          _ => ()
        }
        previous = Some(current)
      }
      _ => previous = None
    }
  }
  result
}