///|
/// Values decoded from an HDF5 dataset.
#derive(@debug.Debug)
pub(all) enum DatasetValues {
  Integers(Array[Int64])
  UnsignedIntegers(Array[UInt64])
  Floats(Array[Double])
  Strings(Array[String])
  Enums(Array[String])
  Structured(Array[DatasetValue])
}

///|
pub extend DatasetValues with @debug.Debug::{to_repr}

///|
/// One recursively decoded value for structured HDF5 datatypes.
#derive(Eq, @debug.Debug)
pub(all) enum DatasetValue {
  SignedInteger(Int64)
  UnsignedInteger(UInt64)
  FloatingPoint(Double)
  Text(String)
  Enumeration(String)
  Bitfield(UInt64)
  Opaque(Bytes)
  Compound(Array[CompoundFieldValue])
  Elements(Array[DatasetValue])
  /// Writer input for a legacy object reference; readers return `Reference`.
  ObjectReference(String?)
  /// Writer input for a legacy dataset-region reference.
  RegionReference(String?, Hdf5RegionSelection)
  Reference(Bytes)
  Sequence(Array[DatasetValue])
  Time(UInt64)
  Complex(Double, Double)
}

///|
pub extend DatasetValue with Eq::{not_equal, equal}

///|
pub extend DatasetValue with @debug.Debug::{to_repr}

///|
/// A named value decoded from a compound datatype member.
#derive(Eq, @debug.Debug)
pub(all) struct CompoundFieldValue {
  name : String
  value : DatasetValue
}

///|
pub extend CompoundFieldValue with Eq::{not_equal, equal}

///|
pub extend CompoundFieldValue with @debug.Debug::{to_repr}

///|
/// Dataset metadata that can be inspected without reading its stored values.
#derive(@debug.Debug)
pub(all) struct DatasetMetadata {
  dataspace : Dataspace
  datatype : Datatype
  layout : DataLayout
}

///|
pub extend DatasetMetadata with @debug.Debug::{to_repr}

///|
/// A dataset together with its decoded values.
#derive(@debug.Debug)
pub(all) struct Dataset {
  dataspace : Dataspace
  datatype : Datatype
  layout : DataLayout
  values : DatasetValues
}

///|
pub extend Dataset with @debug.Debug::{to_repr}

///|
fn datatype_class_fixed_point() -> Int32 {
  0
}

///|
fn datatype_class_floating_point() -> Int32 {
  1
}

///|
fn datatype_class_string() -> Int32 {
  3
}

///|
fn read_uint_at(
  bytes : Bytes,
  offset : Int32,
  size : Int32,
  little_endian : Bool,
) -> UInt64 raise Hdf5Error {
  guard offset >= 0 &&
    size >= 0 &&
    offset.to_int64() + size.to_int64() <= bytes.length().to_int64() else {
    raise UnexpectedEof(offset.to_int64())
  }
  guard size >= 1 && size <= 8 else {
    raise UnsupportedFeature("datatype width is outside 1..8 bytes")
  }
  let mut value = 0UL
  if little_endian {
    for index in 0.. Int64 {
  if bits == 64 {
    return raw.reinterpret_as_int64()
  }
  let sign_bit = 1UL << (bits - 1)
  let value = raw.reinterpret_as_int64()
  if (raw & sign_bit) != 0UL {
    value - (1L << bits)
  } else {
    value
  }
}

///|
fn float16_to_float(bits : UInt32) -> Float {
  let sign = (bits >> 15) & 1U
  let exponent = ((bits >> 10) & 0x1FU).reinterpret_as_int()
  let fraction = bits & 0x3FFU
  let binary32 = if exponent == 0 {
    if fraction == 0U {
      sign << 31
    } else {
      let mut normalized = fraction
      let mut unbiased = -14
      while (normalized & 0x400U) == 0U {
        normalized = normalized << 1
        unbiased = unbiased - 1
      }
      (sign << 31) |
      ((unbiased + 127).reinterpret_as_uint() << 23) |
      ((normalized & 0x3FFU) << 13)
    }
  } else if exponent == 0x1F {
    (sign << 31) | (0xFFU << 23) | (fraction << 13)
  } else {
    (sign << 31) |
    ((exponent + 112).reinterpret_as_uint() << 23) |
    (fraction << 13)
  }
  Float::reinterpret_from_uint(binary32)
}

///|
fn read_float_at(
  bytes : Bytes,
  offset : Int32,
  size : Int32,
  little_endian : Bool,
) -> Double raise Hdf5Error {
  if size == 2 {
    let bits = if little_endian {
      bytes.unsafe_read_uint16_le(offset)
    } else {
      bytes.unsafe_read_uint16_be(offset)
    }
    float16_to_float(bits.to_uint()).to_double()
  } else if size == 4 {
    let bits = if little_endian {
      bytes.unsafe_read_uint32_le(offset)
    } else {
      bytes.unsafe_read_uint32_be(offset)
    }
    Float::reinterpret_from_uint(bits).to_double()
  } else if size == 8 {
    let bits = read_uint_at(bytes, offset, 8, little_endian)
    bits.reinterpret_as_double()
  } else {
    raise UnsupportedFeature("floating-point width is not 2, 4 or 8 bytes")
  }
}

///|
fn is_ieee_float_layout(datatype : Datatype) -> Bool {
  if datatype.size == 2 {
    datatype.float_sign_position == 15 &&
    datatype.float_exponent_position == 10 &&
    datatype.float_exponent_size == 5 &&
    datatype.float_mantissa_position == 0 &&
    datatype.float_mantissa_size == 10 &&
    datatype.float_exponent_bias == 15 &&
    datatype.bit_offset == 0 &&
    datatype.bit_precision == 16
  } else if datatype.size == 4 {
    datatype.float_sign_position == 31 &&
    datatype.float_exponent_position == 23 &&
    datatype.float_exponent_size == 8 &&
    datatype.float_mantissa_position == 0 &&
    datatype.float_mantissa_size == 23 &&
    datatype.float_exponent_bias == 127 &&
    datatype.bit_offset == 0 &&
    datatype.bit_precision == 32
  } else if datatype.size == 8 {
    datatype.float_sign_position == 63 &&
    datatype.float_exponent_position == 52 &&
    datatype.float_exponent_size == 11 &&
    datatype.float_mantissa_position == 0 &&
    datatype.float_mantissa_size == 52 &&
    datatype.float_exponent_bias == 1023 &&
    datatype.bit_offset == 0 &&
    datatype.bit_precision == 64
  } else {
    false
  }
}

///|
fn binary_float_to_double(raw : UInt64, datatype : Datatype) -> Double {
  let sign_bit = 0x8000000000000000UL
  let fraction_mask = 0x000FFFFFFFFFFFFFUL
  let value = masked_value(raw, datatype.bit_offset, datatype.bit_precision)
  let sign = if ((value >> datatype.float_sign_position) & 1UL) != 0UL {
    sign_bit
  } else {
    0UL
  }
  let exponent = masked_value(
    value,
    datatype.float_exponent_position,
    datatype.float_exponent_size,
  ).reinterpret_as_int64()
  let max_exponent = (1UL << datatype.float_exponent_size) - 1UL
  let mantissa = masked_value(
    value,
    datatype.float_mantissa_position,
    datatype.float_mantissa_size,
  )
  if exponent == max_exponent.reinterpret_as_int64() {
    return if mantissa == 0UL {
      (sign | 0x7FF0000000000000UL).reinterpret_as_double()
    } else {
      (sign | 0x7FF8000000000000UL).reinterpret_as_double()
    }
  }
  if exponent == 0L && mantissa == 0UL {
    return sign.reinterpret_as_double()
  }
  let mantissa_size = datatype.float_mantissa_size.to_int64()
  let mut exponent_field = 0L
  let mut fraction = 0UL
  if exponent == 0L {
    let mut highest = 0
    for bit in 0..> bit) & 1UL) != 0UL {
        highest = bit
      }
    }
    fraction = if highest <= 52 {
      (mantissa << (52 - highest)) & fraction_mask
    } else {
      (mantissa >> (highest - 52)) & fraction_mask
    }
    exponent_field = 1023L +
      1L -
      datatype.float_exponent_bias.to_int64() -
      (mantissa_size - highest.to_int64())
  } else {
    fraction = if mantissa_size <= 52L {
      (mantissa << (52 - mantissa_size.to_int())) & fraction_mask
    } else {
      mantissa >> (mantissa_size.to_int() - 52)
    }
    exponent_field = 1023L + exponent - datatype.float_exponent_bias.to_int64()
  }
  if exponent_field >= 2047L {
    return (sign | 0x7FF0000000000000UL).reinterpret_as_double()
  }
  if exponent_field <= 0L {
    let shift = 1L - exponent_field
    if shift >= 53L {
      return sign.reinterpret_as_double()
    }
    let significand = (1UL << 52) | fraction
    return (sign | (significand >> shift.to_int())).reinterpret_as_double()
  }
  (sign | (exponent_field.reinterpret_as_uint64() << 52) | fraction).reinterpret_as_double()
}

///|
fn read_float_value(
  bytes : Bytes,
  offset : Int32,
  datatype : Datatype,
) -> Double raise Hdf5Error {
  if is_ieee_float_layout(datatype) {
    read_float_at(bytes, offset, datatype.size, datatype.little_endian)
  } else {
    let raw = read_uint_at(bytes, offset, datatype.size, datatype.little_endian)
    binary_float_to_double(raw, datatype)
  }
}

///|
fn read_fixed_string_at(
  bytes : Bytes,
  offset : Int32,
  length : Int32,
  padding : Int32,
  charset : Int32,
) -> String raise Hdf5Error {
  guard offset >= 0 &&
    length >= 0 &&
    offset.to_int64() + length.to_int64() <= bytes.length().to_int64() else {
    raise UnexpectedEof(offset.to_int64())
  }
  let mut end = offset + length
  if padding == 0 {
    let mut cursor = offset
    while cursor < end && bytes.unsafe_get(cursor) != b'\x00' {
      cursor = cursor + 1
    }
    end = cursor
  } else {
    let pad = if padding == 1 { b'\x00' } else { b' ' }
    while end > offset && bytes.unsafe_get(end - 1) == pad {
      end = end - 1
    }
  }
  if charset == 0 {
    for index in offset.. raise InvalidField("invalid UTF-8 string data")
  }
}

///|
fn enum_value_name(raw : UInt64, datatype : Datatype) -> String raise Hdf5Error {
  guard datatype.base_types.length() == 1 &&
    datatype.base_types[0].class == datatype_class_fixed_point() else {
    raise InvalidField("enum datatype is missing its integer base type")
  }
  let base = datatype.base_types[0]
  let value = if base.signed {
    EnumValue::Signed(decode_integer_signed(raw, base))
  } else {
    EnumValue::Unsigned(decode_integer_unsigned(raw, base))
  }
  for enum_member in datatype.enum_members {
    if enum_member.value == value {
      return enum_member.name
    }
  }
  raise InvalidField("enum value does not match any member")
}

///|
fn copied_value_bytes(
  payload : Bytes,
  offset : Int32,
  size : Int32,
) -> Bytes raise Hdf5Error {
  guard offset >= 0 &&
    size >= 0 &&
    offset.to_int64() + size.to_int64() <= payload.length().to_int64() else {
    raise UnexpectedEof(offset.to_int64())
  }
  payload.get_view(start=offset, end=offset + size).unwrap().to_owned()
}

///|
fn masked_value(raw : UInt64, offset : Int32, precision : Int32) -> UInt64 {
  let shifted = raw >> offset
  if precision == 64 {
    shifted
  } else {
    shifted & ((1UL << precision) - 1UL)
  }
}

///|
fn decode_integer_unsigned(
  raw : UInt64,
  datatype : Datatype,
) -> UInt64 raise Hdf5Error {
  guard datatype.size >= 1 && datatype.size <= 8 else {
    raise UnsupportedFeature("integer width is outside 1..8 bytes")
  }
  guard datatype.bit_offset >= 0 &&
    datatype.bit_precision > 0 &&
    datatype.bit_precision <= 64 &&
    datatype.bit_offset + datatype.bit_precision <= datatype.size * 8 else {
    raise InvalidField("invalid integer offset or precision")
  }
  masked_value(raw, datatype.bit_offset, datatype.bit_precision)
}

///|
fn decode_integer_signed(
  raw : UInt64,
  datatype : Datatype,
) -> Int64 raise Hdf5Error {
  decode_signed_bits(
    decode_integer_unsigned(raw, datatype),
    datatype.bit_precision,
  )
}

///|
fn decode_dataset_value(
  source : &Hdf5Source?,
  bytes : Bytes,
  payload : Bytes,
  offset : Int32,
  datatype : Datatype,
  offset_size : Int32,
  length_size : Int32,
  base_address : UInt64,
) -> DatasetValue raise Hdf5Error {
  match datatype.class {
    0 => {
      let raw = read_uint_at(
        payload,
        offset,
        datatype.size,
        datatype.little_endian,
      )
      if datatype.signed {
        SignedInteger(decode_integer_signed(raw, datatype))
      } else {
        UnsignedInteger(decode_integer_unsigned(raw, datatype))
      }
    }
    1 => FloatingPoint(read_float_value(payload, offset, datatype))
    2 => {
      let raw = read_uint_at(
        payload,
        offset,
        datatype.size,
        datatype.little_endian,
      )
      Time(masked_value(raw, 0, datatype.bit_precision))
    }
    3 =>
      Text(
        read_fixed_string_at(
          payload,
          offset,
          datatype.size,
          datatype.string_padding,
          datatype.string_charset,
        ),
      )
    4 => {
      let raw = read_uint_at(
        payload,
        offset,
        datatype.size,
        datatype.little_endian,
      )
      Bitfield(masked_value(raw, datatype.bit_offset, datatype.bit_precision))
    }
    5 => Opaque(copied_value_bytes(payload, offset, datatype.size))
    6 => {
      let fields : Array[CompoundFieldValue] = []
      for compound_member in datatype.compound_members {
        fields.push({
          name: compound_member.name,
          value: decode_dataset_value(
            source,
            bytes,
            payload,
            offset + compound_member.offset,
            compound_member.datatype,
            offset_size,
            length_size,
            base_address,
          ),
        })
      }
      Compound(fields)
    }
    7 => Reference(copied_value_bytes(payload, offset, datatype.size))
    8 => {
      let raw = read_uint_at(
        payload,
        offset,
        datatype.size,
        datatype.little_endian,
      )
      Enumeration(enum_value_name(raw, datatype))
    }
    9 => {
      let declared = read_uint_at(payload, offset, 4, true)
      guard declared <= 0x7FFFFFFFUL else {
        let description = if datatype.vlen_is_string {
          "variable-length string is too large"
        } else {
          "variable-length sequence is too large"
        }
        raise InvalidField(description)
      }
      let count = declared.to_int()
      if count == 0 {
        if datatype.vlen_is_string {
          Text("")
        } else {
          Sequence([])
        }
      } else {
        let heap_address = read_uint_at(payload, offset + 4, offset_size, true)
        let object_index = read_uint_at(
          payload,
          offset + 4 + offset_size,
          4,
          true,
        )
        guard object_index <= 0x7FFFFFFFUL else {
          raise InvalidField("global heap object index is too large")
        }
        let data = match source {
          Some(random_access) =>
            read_source_global_heap_object(
              random_access,
              heap_address,
              object_index.to_int(),
              length_size,
              base_address,
            )
          None =>
            read_global_heap_object(
              bytes,
              heap_address,
              object_index.to_int(),
              offset_size,
              length_size,
              base_address,
            )
        }
        if datatype.vlen_is_string {
          guard data.length() >= count else {
            raise InvalidField(
              "global heap string is shorter than declared length",
            )
          }
          Text(read_fixed_string_at(data, 0, count, 1, datatype.string_charset))
        } else {
          guard datatype.base_types.length() == 1 else {
            raise InvalidField(
              "variable-length datatype is missing its base type",
            )
          }
          let base = datatype.base_types[0]
          guard count.to_int64() <= 0x7FFFFFFFL / base.size.to_int64() &&
            data.length().to_int64() >= count.to_int64() * base.size.to_int64() else {
            raise InvalidField(
              "global heap sequence is shorter than declared length",
            )
          }
          let values : Array[DatasetValue] = []
          for index in 0.. {
      guard datatype.base_types.length() == 1 else {
        raise InvalidField("array datatype is missing its base type")
      }
      let base = datatype.base_types[0]
      guard datatype.size % base.size == 0 else {
        raise InvalidField(
          "array datatype size is not divisible by its base type",
        )
      }
      let values : Array[DatasetValue] = []
      for index in 0..<(datatype.size / base.size) {
        values.push(
          decode_dataset_value(
            source,
            bytes,
            payload,
            offset + index * base.size,
            base,
            offset_size,
            length_size,
            base_address,
          ),
        )
      }
      Elements(values)
    }
    11 => {
      guard datatype.base_types.length() == 1 else {
        raise InvalidField("complex datatype is missing its base type")
      }
      let base = datatype.base_types[0]
      Complex(
        read_float_value(payload, offset, base),
        read_float_value(payload, offset + base.size, base),
      )
    }
    _ => raise UnsupportedFeature("datatype class is not supported yet")
  }
}

///|
fn decode_values(
  bytes : Bytes,
  payload : Bytes,
  count : Int64,
  datatype : Datatype,
  offset_size : Int32,
  length_size : Int32,
  base_address : UInt64,
  source~ : &Hdf5Source? = None,
) -> DatasetValues raise Hdf5Error {
  guard count >= 0 && count <= 0x7FFFFFFFL else {
    raise InvalidField("dataset has too many elements")
  }
  let n = count.to_int()
  guard datatype.size > 0 else { raise InvalidField("invalid datatype size") }
  guard payload.length().to_int64() >= count * datatype.size.to_int64() else {
    raise InvalidField("dataset storage is smaller than its declared shape")
  }
  match datatype.class {
    c if c == datatype_class_fixed_point() =>
      if datatype.signed {
        let values : Array[Int64] = []
        for i in 0.. {
      let values : Array[Double] = []
      for i in 0.. {
      let values : Array[String] = []
      for i in 0.. {
      let values : Array[String] = []
      for i in 0.. {
      let values : Array[String] = []
      for i in 0.. values.push(value)
          _ => raise InvalidField("invalid variable-length string value")
        }
      }
      Strings(values)
    }
    _ => {
      let values : Array[DatasetValue] = []
      for i in 0.. HeaderMessage raise Hdf5Error {
  match header.first(msg_type) {
    Some(message) => message
    None =>
      raise InvalidField("dataset is missing a required object header message")
  }
}

///|
/// Parses the dataspace, datatype and storage layout from a dataset object
/// header without reading the stored values.
fn read_dataset_metadata(
  header : ObjectHeader,
  offset_size : Int32,
  length_size : Int32,
) -> DatasetMetadata raise Hdf5Error {
  let dataspace = parse_dataspace(
    message_first(header, msg_dataspace),
    length_size,
  )
  let datatype = parse_datatype(message_first(header, msg_datatype))
  let layout = parse_data_layout(
    message_first(header, msg_data_layout),
    offset_size,
    length_size,
  )
  { dataspace, datatype, layout, }
}

///|
fn read_dataset_values(
  bytes : Bytes,
  header : ObjectHeader,
  metadata : DatasetMetadata,
  offset_size : Int32,
  length_size : Int32,
  base_address : UInt64,
) -> DatasetValues raise Hdf5Error {
  let dataspace = metadata.dataspace
  let datatype = metadata.datatype
  let layout = metadata.layout
  let filters = match header.first(msg_filter_pipeline) {
    Some(message) => parse_filters(message)
    None => []
  }
  guard filters.length() == 0 || layout.layout_class == 2 else {
    raise UnsupportedFeature("filters require chunked storage")
  }
  if header.first(msg_external_data_files) is Some(_) {
    raise UnsupportedFeature("external dataset storage requires Hdf5Resolver")
  }
  let count = dataspace.element_count()
  guard datatype.size > 0 &&
    count >= 0 &&
    count <= 0x7FFFFFFFL / datatype.size.to_int64() else {
    raise InvalidField("dataset byte size is too large")
  }
  let payload = match layout.layout_class {
    0 => layout.compact_data
    1 => {
      let undefined = if offset_size == 8 {
        0xFFFFFFFFFFFFFFFFUL
      } else {
        (1UL << (offset_size * 8)) - 1UL
      }
      if count == 0 {
        b""
      } else if layout.data_address == undefined {
        filled_payload(
          count,
          datatype.size,
          dataset_fill_value(header, datatype),
        ).unsafe_reinterpret_as_bytes()
      } else {
        let stored_size = if layout.data_size > 0UL {
          layout.data_size
        } else {
          count.reinterpret_as_uint64() * datatype.size.to_uint64()
        }
        guard stored_size <= 0x7FFFFFFFUL else {
          raise InvalidField("contiguous dataset is too large")
        }
        let offset = address_to_offset(layout.data_address, base_address)
        let reader = Reader::at(bytes, offset)
        reader.read_bytes(stored_size.to_int())
      }
    }
    2 => {
      let fill_value = dataset_fill_value(header, datatype)
      read_chunked_with_filters(
        bytes, layout, dataspace, datatype, offset_size, base_address, fill_value,
        filters, length_size,
      )
    }
    _ => raise UnsupportedFeature("unknown data layout class")
  }
  let values = decode_values(
    bytes, payload, count, datatype, offset_size, length_size, base_address,
  )
  values
}

///|
/// Parses a dataset object header and decodes compact, contiguous or
/// chunked data with supported indexes and filters.
fn read_dataset(
  bytes : Bytes,
  header : ObjectHeader,
  offset_size : Int32,
  length_size : Int32,
  base_address : UInt64,
) -> Dataset raise Hdf5Error {
  let metadata = read_dataset_metadata(header, offset_size, length_size)
  let values = read_dataset_values(
    bytes, header, metadata, offset_size, length_size, base_address,
  )
  {
    dataspace: metadata.dataspace,
    datatype: metadata.datatype,
    layout: metadata.layout,
    values,
  }
}