///|
/// A logical blob split into independently deduplicated chunks.
pub struct ChunkedBlob {
  chunks : Array[Digest]
  length : Int
}

///|
pub fn ChunkedBlob::chunk_count(self : ChunkedBlob) -> Int {
  self.chunks.length()
}

///|
pub fn ChunkedBlob::length(self : ChunkedBlob) -> Int {
  self.length
}

///|
/// Split a blob into fixed-size chunks and store each chunk by digest.
/// Every chunk is pinned once for the lifetime of the returned manifest.
pub fn MemoryStore::put_chunked(
  self : MemoryStore,
  data : Bytes,
  chunk_size? : Int = 65536,
) -> ChunkedBlob {
  let size = if chunk_size < 1 { 1 } else { chunk_size }
  let ids : Array[Digest] = []
  for start = 0; start < data.length(); start = start + size {
    let end = (start + size).min(data.length())
    let id = self.put(data[start:end].to_owned())
    ignore(self.pin(id))
    ids.push(id)
  }
  ChunkedBlob::{ chunks: ids, length: data.length(), }
}

///|
/// Add one live reference for every chunk in a manifest.
pub fn MemoryStore::pin_chunked(
  self : MemoryStore,
  manifest : ChunkedBlob,
) -> Bool {
  for id in manifest.chunks {
    if !self.has(id) {
      return false
    }
  }
  for id in manifest.chunks {
    ignore(self.pin(id))
  }
  true
}

///|
/// Release the references owned by a chunk manifest.
pub fn MemoryStore::unpin_chunked(
  self : MemoryStore,
  manifest : ChunkedBlob,
) -> Unit {
  for id in manifest.chunks {
    self.unpin(id)
  }
}

///|
/// Reassemble a chunked blob, returning None if any chunk is unavailable.
pub fn MemoryStore::get_chunked(
  self : MemoryStore,
  manifest : ChunkedBlob,
) -> Bytes? {
  let output : Array[Byte] = []
  for id in manifest.chunks {
    match self.get(id) {
      Some(chunk) =>
        for byte in chunk {
          output.push(byte)
        }
      None => return None
    }
  }
  if output.length() != manifest.length {
    return None
  }
  Some(Bytes::from_array(output))
}