///|
pub(all) struct FileStatus {
  name : String
  healthy : Bool
  missing : Bool
  damaged_slices : Array[Int32]
}

///|
priv struct Scan {
  status : Array[FileStatus]
  blocks : Array[BytesView?]
}

///|
fn RecoverySet::scan(self : RecoverySet, files : Map[String, Bytes]) -> Scan {
  let blocks = []
  let status = []
  for m in self.members {
    let source = files.get(m.name)
    let bad = []
    let count = (m.length + self.width - 1) / self.width
    for i = 0; i < count; i = i + 1 {
      let start = i * self.width
      let end = (start + self.width).min(m.length)
      if source is Some(b) && end <= b.length() {
        let block = if end - start == self.width {
          b[start:end]
        } else {
          slice_at(b[start:end].to_owned(), 0, self.width)
        }
        let offset = 16 + i * 20
        if crc32(block) == le32(m.sums, offset + 16) &&
          md5(block) == m.sums[offset:offset + 16].to_owned() {
          blocks.push(Some(block))
          continue
        }
      }
      bad.push(i)
      blocks.push(None)
    }
    let healthy = source is Some(bytes) &&
      bytes.length() == m.length &&
      bad.is_empty() &&
      md5(bytes) == m.hash
    status.push({
      name: m.name,
      healthy,
      missing: source is None,
      damaged_slices: bad,
    })
  }
  { status, blocks, }
}

///|
pub fn RecoverySet::verify(
  self : RecoverySet,
  files : Map[String, Bytes],
) -> Array[FileStatus] {
  self.scan(files).status
}

///|
test "identify missing, truncated and appended input" {
  let s = read_set([create([("a", b"12345")], width=4)])
  assert_eq(s.verify(Map([]))[0].damaged_slices, [0, 1])
  assert_eq(s.verify(Map([("a", b"1234")]))[0].damaged_slices, [1])
  assert_false(s.verify(Map([("a", b"12345extra")]))[0].healthy)
  assert_true(s.verify(Map([("a", b"12345")]))[0].healthy)
}