///|
/// A trigger condition used by waveform regression checks.
pub enum TriggerCondition {
  AnyChange
  ScalarLevel(Char)
  RisingEdge
  FallingEdge
  UnknownValue
  ExactValue(Value)
} derive(Debug, Eq)

///|
/// A timestamp at which a trigger condition matched.
pub struct TriggerHit {
  signal_id : String
  time : Int64
  value : Value
  condition : String
} derive(Debug, Eq)

///|
/// A decoded bus snapshot assembled from several scalar signals.
pub struct BusSnapshot {
  time : Int64
  value : Value
  signal_ids : Array[String]
} derive(Debug, Eq)

///|
/// Find trigger hits for one signal.
pub fn trigger_hits(
  trace : Trace,
  signal_id : String,
  condition : TriggerCondition,
) -> Array[TriggerHit] {
  let result : Array[TriggerHit] = []
  let events = signal_changes(trace, signal_id)
  let mut previous : Value? = None
  for change in events {
    let matched = match condition {
      AnyChange => true
      ScalarLevel(level) => change.value == Scalar(level)
      RisingEdge =>
        match (previous, change.value) {
          (Some(Scalar(before)), Scalar(after)) => before == '0' && after == '1'
          _ => false
        }
      FallingEdge =>
        match (previous, change.value) {
          (Some(Scalar(before)), Scalar(after)) => before == '1' && after == '0'
          _ => false
        }
      UnknownValue => change.value.is_unknown()
      ExactValue(value) => change.value == value
    }
    if matched {
      result.push({
        signal_id,
        time: change.time,
        value: change.value,
        condition: trigger_name(condition),
      })
    }
    previous = Some(change.value)
  }
  result
}

///|
/// Return all changes within a time-triggered region.
pub fn trigger_window(
  trace : Trace,
  signal_id : String,
  condition : TriggerCondition,
  before : Int64,
  after : Int64,
) -> Array[(Int64, Int64)] {
  trigger_hits(trace, signal_id, condition).map(fn(hit) {
    (hit.time - Int64::max(0L, before), hit.time + Int64::max(0L, after))
  })
}

///|
/// Assemble a bus from signals ordered from most to least significant.
pub fn bus_snapshot(
  trace : Trace,
  signal_ids : Array[String],
  timestamp : Int64,
) -> BusSnapshot {
  let bits : Array[LogicBit] = []
  for signal_id in signal_ids {
    let value = value_at(trace, signal_id, timestamp)
    match value {
      Some(item) => bits.append(item.bits())
      None => bits.push(Unknown)
    }
  }
  { time: timestamp, value: bits_value(bits), signal_ids }
}

///|
/// Assemble bus snapshots at every trace timestamp where a member changes.
pub fn bus_snapshots(
  trace : Trace,
  signal_ids : Array[String],
) -> Array[BusSnapshot] {
  let times : Array[Int64] = [0L]
  for change in changes_for_ids(trace, signal_ids) {
    if !times.contains(change.time) {
      times.push(change.time)
    }
  }
  times.sort()
  times.map(fn(time) { bus_snapshot(trace, signal_ids, time) })
}

///|
/// Return the Hamming distance between two known values.
pub fn hamming_distance(left : Value, right : Value) -> Int? {
  if left.is_unknown() || right.is_unknown() {
    return None
  }
  let a = left.bits()
  let b = right.bits()
  let width = Int::max(a.length(), b.length())
  let mut distance = 0
  for i in 0.. Bool {
  let item = frequency_estimate(trace, signal_id)
  item.period_count >= 2 && stats(trace, signal_id).unknown_values == 0
}

///|
/// Compare activity between two signals.
pub fn activity_distance(
  trace : Trace,
  left_id : String,
  right_id : String,
) -> Int64 {
  let left = activity(trace, left_id)
  let right = activity(trace, right_id)
  (left.active_time - right.active_time).abs()
}

///|
/// Return the fraction of aligned scalar samples that agree.
pub fn signal_agreement(
  trace : Trace,
  left_id : String,
  right_id : String,
  step : Int64,
) -> Double? {
  let options = {
    start: Some(0L),
    end: Some(trace.end_time),
    step: Int64::max(1L, step),
    include_initial: true,
    include_unknown: true,
  }
  let left = sample_signal(trace, left_id, options)
  let right = sample_signal(trace, right_id, options)
  let mut compared = 0
  let mut equal = 0
  for i in 0.. {
        compared += 1
        if values_equal(a, b, true) {
          equal += 1
        }
      }
      _ => ()
    }
  }
  if compared == 0 {
    None
  } else {
    Some(equal.to_double() / compared.to_double())
  }
}

///|
fn trigger_name(condition : TriggerCondition) -> String {
  match condition {
    AnyChange => "any-change"
    ScalarLevel(value) => "level-" + value.to_string()
    RisingEdge => "rising"
    FallingEdge => "falling"
    UnknownValue => "unknown"
    ExactValue(value) => "value-" + value.display()
  }
}

///|
/// Construct an any-change trigger.
pub fn any_change_trigger() -> TriggerCondition {
  AnyChange
}

///|
/// Construct a scalar-level trigger.
pub fn scalar_level_trigger(value : Char) -> TriggerCondition {
  ScalarLevel(value)
}

///|
/// Construct a rising-edge trigger.
pub fn rising_edge_trigger() -> TriggerCondition {
  RisingEdge
}

///|
/// Construct a falling-edge trigger.
pub fn falling_edge_trigger() -> TriggerCondition {
  FallingEdge
}

///|
/// Construct an unknown-value trigger.
pub fn unknown_value_trigger() -> TriggerCondition {
  UnknownValue
}

///|
/// Construct an exact-value trigger.
pub fn exact_value_trigger(value : Value) -> TriggerCondition {
  ExactValue(value)
}