///| Self-only totals, matched by event name in each part, not by column number.

///|
/// No inferred recursion or inclusive totals. Overflow is a checked error.
pub fn Profile::self_totals(
  self : Profile,
  event : String,
) -> Array[Total] raise ParseError {
  let columns : Array[Int32] = []
  for part in self.parts {
    let mut column = -1
    for i = 0; i < part.events.length(); i = i + 1 {
      if part.events[i] == event {
        column = i
      }
    }
    if column < 0 {
      raise Unsupported(0, "physical event absent from a part: " + event)
    }
    columns.push(column)
  }
  let indices : Map[(String, String, String), Int32] = Map([])
  let totals : Array[Total] = []
  for record in self.records {
    if record.part < 0 || record.part >= columns.length() {
      raise Syntax(record.line, "invalid part index in model")
    }
    let column = columns[record.part]
    if record.values.length() != self.parts[record.part].events.length() {
      raise Syntax(record.line, "invalid event values in model")
    }
    if record.kind is SelfCost {
      let c = record.context
      let key = (c.object, c.file, c.function_name)
      match indices.get(key) {
        None => {
          indices.set(key, totals.length())
          totals.push({
            object: c.object,
            file: c.file,
            function_name: c.function_name,
            value: record.values[column],
          })
        }
        Some(i) =>
          totals[i] = {
            ..totals[i],
            value: add_checked(
              totals[i].value,
              record.values[column],
              record.line,
            ),
          }
      }
    }
  }
  totals
}

///| Compare raw self counters by object/file/function. Callers must ensure that

///|
/// event meaning, build and collection conditions are comparable.
pub fn diff(
  before : Profile,
  after : Profile,
  event : String,
) -> Array[Change] raise ParseError {
  let left = before.self_totals(event)
  let right = after.self_totals(event)
  let changes : Array[Change] = []
  let indices : Map[(String, String, String), Int32] = Map([])
  for total in left {
    indices.set(
      (total.object, total.file, total.function_name),
      changes.length(),
    )
    changes.push({
      object: total.object,
      file: total.file,
      function_name: total.function_name,
      before: total.value,
      after: 0UL,
    })
  }
  for total in right {
    let key = (total.object, total.file, total.function_name)
    match indices.get(key) {
      None =>
        changes.push({
          object: total.object,
          file: total.file,
          function_name: total.function_name,
          before: 0UL,
          after: total.value,
        })
      Some(i) => changes[i] = { ..changes[i], after: total.value, }
    }
  }
  changes
}