///|
/// Create deterministic analysis options for the complete trace.
pub fn default_analysis_options(trace : Trace) -> AnalysisOptions {
  {
    start: Some(0L),
    end: Some(trace.end_time),
    step: 1L,
    include_initial: true,
    include_unknown: true,
  }
}

///|
/// Return a normalized copy of analysis options.
pub fn AnalysisOptions::normalized(
  self : AnalysisOptions,
  trace : Trace,
) -> AnalysisOptions {
  let start = match self.start {
    Some(value) => Int64::max(0L, value)
    None => 0L
  }
  let end = match self.end {
    Some(value) => Int64::min(trace.end_time, Int64::max(start, value))
    None => trace.end_time
  }
  {
    start: Some(start),
    end: Some(end),
    step: Int64::max(1L, self.step),
    include_initial: self.include_initial,
    include_unknown: self.include_unknown,
  }
}

///|
/// Sample one signal at regular timestamps.
pub fn sample_signal(
  trace : Trace,
  signal_id : String,
  options : AnalysisOptions,
) -> Array[Sample] {
  let safe = options.normalized(trace)
  let start = safe.start.unwrap()
  let end = safe.end.unwrap()
  let result : Array[Sample] = []
  let mut cursor = start
  while cursor <= end {
    let current = value_at(trace, signal_id, cursor)
    if safe.include_unknown || current is Some(_) {
      result.push({ signal_id, time: cursor, value: current })
    }
    cursor += safe.step
  }
  if safe.include_initial && result.length() == 0 {
    result.push({
      signal_id,
      time: start,
      value: value_at(trace, signal_id, start),
    })
  }
  result
}

///|
/// Sample every declared signal using the same time grid.
pub fn sample_all(
  trace : Trace,
  options : AnalysisOptions,
) -> Array[(String, Array[Sample])] {
  trace.signals.map(fn(signal) {
    (signal.id, sample_signal(trace, signal.id, options))
  })
}

///|
/// Compute time-weighted activity for one signal.
pub fn activity(trace : Trace, signal_id : String) -> SignalActivity {
  let intervals = value_intervals(trace, signal_id)
  let mut known = 0
  let mut unknown = 0
  let mut active : Int64 = 0L
  let mut high : Int64 = 0L
  let mut low : Int64 = 0L
  for interval in intervals {
    let duration = Int64::max(0L, interval.end - interval.start)
    match interval.value {
      Scalar(value) =>
        if is_known_scalar(value) {
          known += 1
          active += duration
          if value == '1' {
            high += duration
          } else {
            low += duration
          }
        } else {
          unknown += 1
        }
      _ => unknown += 1
    }
  }
  {
    signal_id,
    samples: intervals.length(),
    known_samples: known,
    unknown_samples: unknown,
    active_time: active,
    high_time: high,
    low_time: low,
    transition_count: edges(trace, signal_id).length(),
  }
}

///|
/// Compute activity summaries for all signals.
pub fn all_activity(trace : Trace) -> Array[SignalActivity] {
  trace.signals.map(fn(signal) { activity(trace, signal.id) })
}

///|
/// Return the proportion of the trace spent at logical high.
pub fn high_ratio(trace : Trace, signal_id : String) -> Double? {
  if trace.end_time <= 0L {
    None
  } else {
    Some(
      activity(trace, signal_id).high_time.to_double() /
      trace.end_time.to_double(),
    )
  }
}

///|
/// Return the proportion of the trace spent at logical low.
pub fn low_ratio(trace : Trace, signal_id : String) -> Double? {
  if trace.end_time <= 0L {
    None
  } else {
    Some(
      activity(trace, signal_id).low_time.to_double() /
      trace.end_time.to_double(),
    )
  }
}

///|
/// Return the number of transitions per trace time unit.
pub fn transition_rate(trace : Trace, signal_id : String) -> Double? {
  if trace.end_time <= 0L {
    None
  } else {
    Some(
      edges(trace, signal_id).length().to_double() / trace.end_time.to_double(),
    )
  }
}

///|
/// Calculate the total time for a scalar value.
pub fn dwell_time(trace : Trace, signal_id : String, value : Char) -> Int64 {
  intervals_for_value(trace, signal_id, value).fold(init=0L, fn(
    total,
    interval,
  ) {
    total + Int64::max(0L, interval.end - interval.start)
  })
}

///|
/// Return the number of samples with unknown or high-impedance values.
pub fn unknown_sample_count(trace : Trace, signal_id : String) -> Int {
  value_intervals(trace, signal_id).count_if(fn(interval) {
    interval.value.is_unknown()
  })
}