///|
pub fn scenario_evidence_digest(stdout : String, stderr : String) -> String {
  let normalized_stdout = normalize_evidence(stdout)
  let normalized_stderr = normalize_evidence(stderr)
  let first = rolling_hash(normalized_stdout, 216613626)
  let second = rolling_hash(normalized_stderr, 16777619)
  "evidence-" + first.to_string() + "-" + second.to_string()
}

///|
pub fn scenario_observation_digest(observation : ScenarioObservation) -> String {
  let body = observation.id +
    "\n" +
    observation.target.name() +
    "\n" +
    observation.status.name() +
    "\n" +
    normalize_evidence(observation.stdout) +
    "\n" +
    normalize_evidence(observation.stderr) +
    "\n" +
    observation.duration_ms.to_string()
  scenario_evidence_digest(body, observation.evidence_digest)
}

///|
pub fn normalize_evidence(value : String) -> String {
  let lines = split_lines(value)
  let mut out = ""
  let mut first = true
  let mut index = 0
  while index < lines.length() {
    let line = trim_ascii(lines[index])
    if line.length() > 0 {
      if !first {
        out = out + "\n"
      }
      out = out + line
      first = false
    }
    index += 1
  }
  out
}

///|
pub fn evidence_equal(left : String, right : String) -> Bool {
  normalize_evidence(left) == normalize_evidence(right)
}

///|
pub fn evidence_contains(value : String, fragment : String) -> Bool {
  contains(normalize_evidence(value), normalize_evidence(fragment))
}

///|
fn rolling_hash(value : String, seed : Int) -> Int {
  let mut hash = seed
  let mut index = 0
  while index < value.length() {
    hash = hash * 16777619 + code_at(value, index)
    hash = hash % 2147483629
    if hash < 0 {
      hash = hash + 2147483629
    }
    index += 1
  }
  hash
}

///|
pub fn digest_is_well_formed(value : String) -> Bool {
  starts_with(value, "evidence-") && value.length() > "evidence-0-0".length()
}

///|
pub fn digest_for_lines(lines : Array[String]) -> String {
  let mut joined = ""
  let mut index = 0
  while index < lines.length() {
    if index > 0 {
      joined = joined + "\n"
    }
    joined = joined + normalize_evidence(lines[index])
    index += 1
  }
  scenario_evidence_digest(joined, "")
}

///|
pub fn digest_for_matrix(matrix : ScenarioMatrix) -> String {
  let lines : Array[String] = []
  lines.push(matrix.name)
  lines.push(matrix.version)
  let mut index = 0
  while index < matrix.specs.length() {
    let spec = matrix.specs[index]
    lines.push(spec.id + ":" + spec.target_names() + ":" + spec.command)
    index += 1
  }
  index = 0
  while index < matrix.observations.length() {
    let observation = matrix.observations[index]
    lines.push(
      observation.id +
      ":" +
      observation.target.name() +
      ":" +
      observation.status.name() +
      ":" +
      observation.evidence_digest,
    )
    index += 1
  }
  digest_for_lines(lines)
}