///|
fn repeated_fill(
  fill : Bytes,
  count : Int32,
  limits : Limits,
) -> Bytes raise H5Error {
  let size = checked_mul(fill.length(), count)
  if size > limits.max_file_bytes {
    raise Limit("fill allocation")
  }
  let b = buffer()
  for i = 0; i < count; i = i + 1 {
    b.bytes(fill)
  }
  b.finish()
}

///|
/// Byte access is bounded by the declared EOF, not just the transport buffer.
fn H5File::range(
  self : H5File,
  address : Int32,
  length : Int32,
) -> Bytes raise H5Error {
  self.check_range(address, length)
  bytes_at(self.data, address, length)
}

///|
fn H5File::check_range(
  self : H5File,
  address : Int32,
  length : Int32,
) -> Unit raise H5Error {
  if address < self.sb.base ||
    length < 0 ||
    address > self.sb.eof ||
    length > self.sb.eof - address {
    raise Invalid("raw dataset outside declared file", address)
  }
}

///|
pub fn H5File::read(self : H5File, path : String) -> ArrayValue raise H5Error {
  let info = self.dataset_info(path)
  let raw = match info.storage {
    Compact(bytes) => bytes
    Contiguous(Some(address), size) => self.range(address, size)
    Contiguous(None, _) =>
      repeated_fill(
        info.fill,
        info.dataspace.elements(limits=self.limits),
        self.limits,
      )
    Chunked(_, _) | FixedArray(_, _, _) =>
      return self.read_chunked_selection(
        path,
        info,
        Selection::all(info.dataspace, limits=self.limits),
      )
  }
  ArrayValue::new(info.datatype, info.dataspace, raw, limits=self.limits)
}

///|
test "compact contiguous empty scalar and null datasets retain values" {
  let doc = Document::new()
  doc.add_dataset(
    "/a",
    ArrayValue::from_floats(Simple([2, 2]), [1.25, -2.0, 3.0, 4.5]),
  )
  doc.add_dataset(
    "/b",
    ArrayValue::from_signed(Scalar, [9223372036854775807L]),
    compact=true,
  )
  doc.add_dataset(
    "/empty",
    ArrayValue::new(Unsigned(1, Little), Simple([0, 4]), b""),
  )
  doc.add_dataset("/null", ArrayValue::new(Float(8, Little), Null, b""))
  doc.set_attribute("/a", "unit", ArrayValue::from_strings(Scalar, ["Pa"], 2))
  let f = H5File::open(doc.encode())
  assert_eq(f.read("/a").float_at(3), 4.5)
  assert_eq(f.read("/b").signed_at(0), 9223372036854775807L)
  assert_eq(f.read("/empty").dataspace(), Simple([0, 4]))
  assert_eq(f.read("/null").dataspace(), Null)
  assert_eq(f.attributes(path="/a")[0].value.string_at(0), "Pa")
}