///|
/// Bit-level counters for a vector signal.
pub struct BitStatistics {
  signal_id : String
  bits : Int
  zeros : Int
  ones : Int
  unknown : Int
  high_impedance : Int
} derive(Debug, Eq)

///|
/// Count every observed bit in scalar and vector values.
pub fn bit_statistics(trace : Trace, signal_id : String) -> BitStatistics {
  let mut bits = 0
  let mut zeros = 0
  let mut ones = 0
  let mut unknown = 0
  let mut high_impedance = 0
  for change in signal_changes(trace, signal_id) {
    let text = change.value.bits()
    for bit in text {
      bits += 1
      match bit {
        Zero => zeros += 1
        One => ones += 1
        Unknown => unknown += 1
        HighImpedance => high_impedance += 1
      }
    }
  }
  { signal_id, bits, zeros, ones, unknown, high_impedance }
}

///|
/// Return the fraction of observed bits that are one.
pub fn one_bit_ratio(stats : BitStatistics) -> Double {
  if stats.bits == 0 {
    0.0
  } else {
    stats.ones.to_double() / stats.bits.to_double()
  }
}

///|
/// Return the fraction of observed bits that are unknown or high impedance.
pub fn uncertain_bit_ratio(stats : BitStatistics) -> Double {
  if stats.bits == 0 {
    0.0
  } else {
    (stats.unknown + stats.high_impedance).to_double() / stats.bits.to_double()
  }
}

///|
/// Return the first occurrence of each distinct value token.
pub fn distinct_value_tokens(
  trace : Trace,
  signal_id : String,
) -> Array[String] {
  let result : Array[String] = []
  for change in signal_changes(trace, signal_id) {
    let token = canonical_value_text(change.value)
    if !result.contains(token) {
      result.push(token)
    }
  }
  result
}

///|
/// Return run lengths for consecutive equal values.
pub fn value_run_lengths(
  trace : Trace,
  signal_id : String,
) -> Array[(String, Int)] {
  let result : Array[(String, Int)] = []
  let mut previous : String? = None
  let mut length = 0
  for change in signal_changes(trace, signal_id) {
    let token = canonical_value_text(change.value)
    match previous {
      Some(value) if value == token => length += 1
      Some(value) => {
        result.push((value, length))
        previous = Some(token)
        length = 1
      }
      None => {
        previous = Some(token)
        length = 1
      }
    }
  }
  match previous {
    Some(value) => result.push((value, length))
    None => ()
  }
  result
}

///|
/// Return the longest stable run of one value token.
pub fn longest_value_run(trace : Trace, signal_id : String) -> (String, Int) {
  let mut best = ("", 0)
  for item in value_run_lengths(trace, signal_id) {
    if item.1 > best.1 {
      best = item
    }
  }
  best
}

///|
/// Return all scalar values in source order.
pub fn scalar_sequence(trace : Trace, signal_id : String) -> Array[Char] {
  signal_changes(trace, signal_id).fold(init=[], fn(result, change) {
    match change.value {
      Scalar(value) => {
        result.push(value)
        result
      }
      _ => result
    }
  })
}

///|
/// Return the most common canonical value token.
pub fn dominant_value_token(trace : Trace, signal_id : String) -> String? {
  let counts : Array[(String, Int)] = []
  for token in distinct_value_tokens(trace, signal_id) {
    counts.push(
      (
        token,
        signal_changes(trace, signal_id).count_if(fn(change) {
          canonical_value_text(change.value) == token
        }),
      ),
    )
  }
  let mut best : (String, Int)? = None
  for item in counts {
    match best {
      Some(current) if current.1 >= item.1 => ()
      _ => best = Some(item)
    }
  }
  match best {
    Some(item) => Some(item.0)
    None => None
  }
}

///|
/// Render bit statistics as a stable line.
pub fn bit_statistics_line(stats : BitStatistics) -> String {
  "signal=" +
  stats.signal_id +
  " bits=" +
  stats.bits.to_string() +
  " zero=" +
  stats.zeros.to_string() +
  " one=" +
  stats.ones.to_string() +
  " x=" +
  stats.unknown.to_string() +
  " z=" +
  stats.high_impedance.to_string()
}

///|
/// Render bit statistics as JSON.
pub fn bit_statistics_json(stats : BitStatistics) -> String {
  "{\"signal_id\":\"" +
  escape_text(stats.signal_id) +
  "\",\"bits\":" +
  stats.bits.to_string() +
  ",\"zeros\":" +
  stats.zeros.to_string() +
  ",\"ones\":" +
  stats.ones.to_string() +
  ",\"unknown\":" +
  stats.unknown.to_string() +
  ",\"high_impedance\":" +
  stats.high_impedance.to_string() +
  "}"
}