///|
/// A state change represented by the display form of a VCD value.
pub struct StateTransition {
  signal_id : String
  time : Int64
  from : String
  to : String
} derive(Debug, Eq)

///|
/// A dwell interval labelled by its digital state.
pub struct StateInterval {
  signal_id : String
  start : Int64
  end : Int64
  state : String
} derive(Debug, Eq)

///|
/// Return all value transitions, including vector and real changes.
pub fn state_transitions(
  trace : Trace,
  signal_id : String,
) -> Array[StateTransition] {
  let result : Array[StateTransition] = []
  let mut previous : Value? = None
  for change in signal_changes(trace, signal_id) {
    match previous {
      Some(before) if before != change.value =>
        result.push({
          signal_id,
          time: change.time,
          from: before.display(),
          to: change.value.display(),
        })
      _ => ()
    }
    previous = Some(change.value)
  }
  result
}

///|
/// Return state intervals for a signal, preserving vector values.
pub fn state_intervals(
  trace : Trace,
  signal_id : String,
) -> Array[StateInterval] {
  value_intervals(trace, signal_id).map(fn(interval) {
    {
      signal_id,
      start: interval.start,
      end: interval.end,
      state: interval.value.display(),
    }
  })
}

///|
/// Count dwell time by state label.
pub fn state_dwell(trace : Trace, signal_id : String) -> Array[(String, Int64)] {
  let result : Array[(String, Int64)] = []
  for interval in state_intervals(trace, signal_id) {
    let duration = Int64::max(0L, interval.end - interval.start)
    match result.search_by(fn(item) { item.0 == interval.state }) {
      Some(index) =>
        result[index] = (interval.state, result[index].1 + duration)
      None => result.push((interval.state, duration))
    }
  }
  result
}

///|
/// Decode a known integer value through an optional state label table.
pub fn decode_state(value : Value, labels : Array[String]) -> String {
  match value.to_int() {
    Some(number) => {
      let index = number.to_int()
      match labels.get(index) {
        Some(label) => label
        None => "state_" + index.to_string()
      }
    }
    None => value.display()
  }
}

///|
/// Decode all observed values with a label table.
pub fn decoded_states(
  trace : Trace,
  signal_id : String,
  labels : Array[String],
) -> Array[(Int64, String)] {
  signal_changes(trace, signal_id).map(fn(change) {
    (change.time, decode_state(change.value, labels))
  })
}

///|
/// Return the sequence of stable state labels without adjacent duplicates.
pub fn state_sequence(trace : Trace, signal_id : String) -> Array[String] {
  let result : Array[String] = []
  for change in signal_changes(trace, signal_id) {
    let state = change.value.display()
    match result.last() {
      Some(previous) if previous == state => ()
      _ => result.push(state)
    }
  }
  result
}

///|
/// Return the most frequently occupied states, ordered by first appearance.
pub fn dominant_states(
  trace : Trace,
  signal_id : String,
) -> Array[(String, Int64)] {
  let result = state_dwell(trace, signal_id)
  result.sort_by(fn(left, right) {
    if left.1 > right.1 {
      -1
    } else if left.1 < right.1 {
      1
    } else {
      0
    }
  })
  result
}

///|
/// Render a compact state transition report.
pub fn state_report(trace : Trace, signal_id : String) -> String {
  let out : Array[String] = ["state report: " + signal_id]
  for item in state_dwell(trace, signal_id) {
    out.push(item.0 + "=" + item.1.to_string())
  }
  out.push(
    "transitions=" + state_transitions(trace, signal_id).length().to_string(),
  )
  out.join("\n")
}