///|
/// Rebuild a complete set in memory. No output is returned until every
/// reconstructed file has passed its packet's whole-file checksum.
pub fn RecoverySet::repair(
  self : RecoverySet,
  files : Map[String, Bytes],
) -> Array[(String, Bytes)] raise {
  let scan = self.scan(files)
  let exps = self.parity.keys().to_array()
  exps.sort()
  let missing = []
  for i = 0; i < scan.blocks.length(); i = i + 1 {
    if scan.blocks[i] is None {
      missing.push(i)
    }
  }
  guard solve(
      scan.blocks.length(),
      missing,
      exps,
      exps.map(fn(e) { self.parity[e] }),
      fn(row, offset, j, weight) {
        if scan.blocks[j] is Some(view) {
          xor_slice(row, offset, view, weight)
        }
      },
    )
    is Some(restored) else {
    fail("insufficient independent recovery slices")
  }
  let outputs = []
  let mut pos = 0
  let mut recovered = 0
  for entry in self.members {
    let buf = @buffer.Buffer(size_hint=entry.length)
    let count = (entry.length + self.width - 1) / self.width
    for i = 0; i < count; i = i + 1 {
      let block = match scan.blocks[pos] {
        Some(known) => known
        None => {
          let value = restored[recovered]
          recovered = recovered + 1
          as_bytes(value)[:]
        }
      }
      pos = pos + 1
      let len = (entry.length - i * self.width).min(self.width)
      buf.write_bytes(block[:len])
    }
    let bytes = buf.to_bytes()
    if md5(bytes) != entry.hash {
      fail("restored checksum mismatch: " + entry.name)
    }
    outputs.push((entry.name, bytes))
  }
  outputs
}

///|
test "recover missing file and the final partial slice of another" {
  let files = [("one", b"abcdefghij"), ("two", b"xyz")]
  let s = read_set([create(files, width=4, recovery_blocks=2)])
  let outputs = Map(s.repair(Map([("one", b"abcdefghXX")])))
  for pair in files {
    assert_eq(outputs.get(pair.0), Some(pair.1))
  }
  let few = read_set([create(files, width=4, recovery_blocks=1)])
  assert_true(
    try {
      ignore(few.repair(Map([("one", b"abcdefghXX")])))
      false
    } catch {
      _ => true
    },
  )
}