///|
/// A reproducibility manifest for an imported waveform.
pub struct TraceManifest {
  name : String
  format : String
  timescale : String?
  end_time : Int64
  signal_count : Int
  change_count : Int
  shape_digest : Int64
  content_digest : Int64
  validated : Bool
  tags : Array[String]
} derive(Debug)

///|
/// Create a reproducibility manifest from a trace.
pub fn trace_manifest(
  name : String,
  trace : Trace,
  tags : Array[String],
) -> TraceManifest {
  {
    name,
    format: "VCD",
    timescale: trace.header.timescale,
    end_time: trace.end_time,
    signal_count: trace.signals.length(),
    change_count: trace.changes.length(),
    shape_digest: trace.shape_digest(),
    content_digest: manifest_content_digest(trace),
    validated: analysis_safe(trace),
    tags,
  }
}

///|
/// Return whether two manifests identify the same trace content.
pub fn manifests_equal(left : TraceManifest, right : TraceManifest) -> Bool {
  left.shape_digest == right.shape_digest &&
  left.content_digest == right.content_digest &&
  left.end_time == right.end_time
}

///|
/// Return a stable manifest line for logs.
pub fn TraceManifest::to_text(self : TraceManifest) -> String {
  self.name +
  " format=" +
  self.format +
  " signals=" +
  self.signal_count.to_string() +
  " changes=" +
  self.change_count.to_string() +
  " shape=" +
  self.shape_digest.to_string() +
  " content=" +
  self.content_digest.to_string() +
  " validated=" +
  self.validated.to_string()
}

///|
/// Export a manifest as JSON.
pub fn TraceManifest::to_json(self : TraceManifest) -> String {
  "{\"name\":\"" +
  escape_text(self.name) +
  "\",\"format\":\"" +
  self.format +
  "\",\"timescale\":" +
  manifest_optional_text(self.timescale) +
  ",\"end_time\":" +
  self.end_time.to_string() +
  ",\"signal_count\":" +
  self.signal_count.to_string() +
  ",\"change_count\":" +
  self.change_count.to_string() +
  ",\"shape_digest\":" +
  self.shape_digest.to_string() +
  ",\"content_digest\":" +
  self.content_digest.to_string() +
  ",\"validated\":" +
  self.validated.to_string() +
  ",\"tags\":[" +
  self.tags.map(fn(tag) { "\"" + escape_text(tag) + "\"" }).join(",") +
  "]}"
}

///|
/// Export manifests as CSV.
pub fn trace_manifests_csv(items : Array[TraceManifest]) -> String {
  let out = StringBuilder()
  out.write_string(
    "name,format,timescale,end_time,signals,changes,shape_digest,content_digest,validated,tags\n",
  )
  for item in items {
    out.write_string(
      escape_text(item.name) +
      "," +
      item.format +
      "," +
      manifest_optional_text(item.timescale) +
      "," +
      item.end_time.to_string() +
      "," +
      item.signal_count.to_string() +
      "," +
      item.change_count.to_string() +
      "," +
      item.shape_digest.to_string() +
      "," +
      item.content_digest.to_string() +
      "," +
      item.validated.to_string() +
      "," +
      item.tags.join("|") +
      "\n",
    )
  }
  out.to_string()
}

///|
/// Calculate a content digest independent of source formatting.
fn manifest_content_digest(trace : Trace) -> Int64 {
  trace.changes.fold(init=23L, fn(hash, change) {
    hash * 37L +
    change.time +
    change.id.hash().to_int64() +
    change.value.display().hash().to_int64()
  })
}

///|
/// Encode an optional string as JSON.
fn manifest_optional_text(value : String?) -> String {
  match value {
    Some(item) => "\"" + escape_text(item) + "\""
    None => "null"
  }
}