///|
/// Return the declared signals in source order.
pub fn list_signals(vcd : VcdFile) -> Array[Signal] {
  vcd.signals.copy()
}

///|
/// Find a signal by hierarchical full name.
pub fn find_signal(vcd : VcdFile, full_name : String) -> Signal? {
  for signal in vcd.signals {
    if signal.full_name == full_name {
      return Some(signal)
    }
  }
  None
}

///|
/// Find a signal by raw VCD identifier.
pub fn find_signal_by_identifier(vcd : VcdFile, identifier : String) -> Signal? {
  for signal in vcd.signals {
    if signal.identifier == identifier {
      return Some(signal)
    }
  }
  None
}

///|
/// Get every change for a signal selected by full name.
pub fn signal_changes(
  vcd : VcdFile,
  full_name : String,
) -> Array[SignalChange] raise VcdError {
  let signal = match find_signal(vcd, full_name) {
    Some(signal) => signal
    None => raise VcdError("signal not found: \{full_name}")
  }
  let result : Array[SignalChange] = []
  for change in vcd.changes {
    if change.identifier == signal.identifier {
      result.push(change)
    }
  }
  result
}

///|
/// Get every change for a signal selected by VCD identifier.
pub fn signal_changes_by_identifier(
  vcd : VcdFile,
  identifier : String,
) -> Array[SignalChange] raise VcdError {
  let signal = match find_signal_by_identifier(vcd, identifier) {
    Some(signal) => signal
    None => raise VcdError("signal identifier not found: \{identifier}")
  }
  let result : Array[SignalChange] = []
  for change in vcd.changes {
    if change.identifier == signal.identifier {
      result.push(change)
    }
  }
  result
}

///|
/// Return the most recent value at or before `timestamp`.
pub fn value_at(
  vcd : VcdFile,
  full_name : String,
  timestamp : Int64,
) -> String? raise VcdError {
  if timestamp < 0L {
    raise VcdError("timestamp must be non-negative")
  }
  let changes = signal_changes(vcd, full_name)
  let mut result : String? = None
  for change in changes {
    if change.timestamp <= timestamp {
      result = Some(change.value)
    } else {
      break
    }
  }
  result
}

///|
/// Summarize file-wide counts and time bounds.
pub fn summary(vcd : VcdFile) -> VcdSummary {
  {
    signal_count: vcd.signals.length(),
    change_count: vcd.changes.length(),
    start_time: start_time(vcd),
    end_time: end_time(vcd),
  }
}

///|
/// Compute change count, time bounds, and 1-bit rising/falling edges.
pub fn signal_stats(
  vcd : VcdFile,
  full_name : String,
) -> SignalStats raise VcdError {
  let signal = match find_signal(vcd, full_name) {
    Some(signal) => signal
    None => raise VcdError("signal not found: \{full_name}")
  }
  let changes = signal_changes(vcd, full_name)
  let mut first_change_time : Int64? = None
  let mut last_change_time : Int64? = None
  let mut rising_edges = 0
  let mut falling_edges = 0
  let mut previous : String? = None
  for change in changes {
    if first_change_time is None {
      first_change_time = Some(change.timestamp)
    }
    last_change_time = Some(change.timestamp)
    if signal.width == 1 {
      match previous {
        Some("0") if change.value == "1" => rising_edges = rising_edges + 1
        Some("1") if change.value == "0" => falling_edges = falling_edges + 1
        _ => ()
      }
    }
    previous = Some(change.value)
  }
  {
    change_count: changes.length(),
    first_change_time,
    last_change_time,
    rising_edges,
    falling_edges,
  }
}

///|
/// Earliest timestamp in the file, if any changes exist.
pub fn start_time(vcd : VcdFile) -> Int64? {
  if vcd.changes.is_empty() {
    None
  } else {
    let mut value = vcd.changes[0].timestamp
    for change in vcd.changes {
      if change.timestamp < value {
        value = change.timestamp
      }
    }
    Some(value)
  }
}

///|
/// Latest timestamp in the file, if any changes exist.
pub fn end_time(vcd : VcdFile) -> Int64? {
  if vcd.changes.is_empty() {
    None
  } else {
    let mut value = vcd.changes[0].timestamp
    for change in vcd.changes {
      if change.timestamp > value {
        value = change.timestamp
      }
    }
    Some(value)
  }
}