///|
#derive(@debug.Debug)
priv enum ChunkIndex {
  BTreeV1
  Single(UInt64?, UInt32)
  Implicit
  FixedArray(Int32)
  ExtensibleArray(ExtensibleArrayConfig)
  BTreeV2(BTreeV2Config)
}

///|
/// The storage strategy used for a dataset payload.
#derive(Eq, @debug.Debug)
pub(all) enum DatasetLayoutKind {
  Compact
  Contiguous
  Chunked
  Virtual
  Unknown
}

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

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

///|
#derive(@debug.Debug)
priv struct BTreeV2Config {
  node_size : Int32
  split_percent : Int32
  merge_percent : Int32
}

///|
#derive(Eq, @debug.Debug)
priv struct ExtensibleArrayConfig {
  max_bits : Int32
  index_elements : Int32
  min_pointers : Int32
  min_elements : Int32
  page_bits : Int32
}

///|
#derive(@debug.Debug)
pub struct DataLayout {
  priv version : Int32
  priv layout_class : Int32
  priv data_address : UInt64
  priv data_size : UInt64
  priv chunk_dimensions : Array[UInt32]
  priv element_size : Int32
  priv compact_data : Bytes
  priv chunk_index : ChunkIndex
}

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

///|
/// Returns the dataset's storage strategy.
pub fn DataLayout::kind(self : DataLayout) -> DatasetLayoutKind {
  match self.layout_class {
    0 => Compact
    1 => Contiguous
    2 => Chunked
    3 => Virtual
    _ => Unknown
  }
}

///|
/// Returns the chunk shape, or an empty array for non-chunked datasets.
pub fn DataLayout::chunk_shape(self : DataLayout) -> Array[UInt32] {
  self.chunk_dimensions.copy()
}

///|
fn parse_data_layout(
  message : HeaderMessage,
  offset_size : Int32,
  length_size : Int32,
) -> DataLayout raise Hdf5Error {
  guard message.msg_type == msg_data_layout else {
    raise InvalidField("message is not a data layout message")
  }
  let reader = Reader::new(message.data)
  let version = reader.read_u8().to_int()
  let layout_class = if version == 1 || version == 2 {
    let _dimensionality = reader.read_u8().to_int()
    let layout_class = reader.read_u8().to_int()
    reader.skip(5)
    layout_class
  } else if version == 3 || version == 4 {
    reader.read_u8().to_int()
  } else {
    raise InvalidField("unsupported data layout message version")
  }
  let chunk_dimensions : Array[UInt32] = []
  let mut data_address = 0UL
  let mut data_size = 0UL
  let mut element_size = 0
  let mut compact_data : Bytes = b""
  let mut chunk_index = BTreeV1
  if version == 1 || version == 2 {
    match layout_class {
      0 => {
        let size = reader.read_u32_le().reinterpret_as_int()
        compact_data = reader.read_bytes(size)
      }
      1 | 2 => {
        data_address = reader.read_uint_le(offset_size)
        let dimensionality = message.data.unsafe_get(1).to_int()
        for _ in 1.. raise InvalidField("invalid data layout class")
    }
  } else {
    match layout_class {
      0 => {
        let size = reader.read_u16_le().to_int()
        compact_data = reader.read_bytes(size)
      }
      1 => {
        data_address = reader.read_uint_le(offset_size)
        data_size = reader.read_uint_le(length_size)
      }
      2 =>
        if version == 4 {
          let flags = reader.read_u8().to_int()
          guard (flags & 0xFC) == 0 else {
            raise InvalidField("invalid v4 chunk flags")
          }
          let dimensionality = reader.read_u8().to_int()
          guard dimensionality >= 2 else {
            raise InvalidField("invalid chunk dimensionality")
          }
          let width = reader.read_u8().to_int()
          guard width >= 1 && width <= 8 else {
            raise InvalidField("invalid chunk dimension width")
          }
          for dimension in 0.. 0UL && value <= 0x7FFFFFFFUL else {
              raise InvalidField("invalid chunk dimension")
            }
            if dimension == dimensionality - 1 {
              element_size = value.to_int()
            } else {
              chunk_dimensions.push(value.to_uint())
            }
          }
          let index = reader.read_u8().to_int()
          chunk_index = match index {
            1 =>
              if (flags & 2) != 0 {
                let size = reader.read_uint_le(length_size)
                let mask = reader.read_u32_le()
                Single(Some(size), mask)
              } else {
                Single(None, 0U)
              }
            2 => {
              guard flags == 0 else {
                raise InvalidField("invalid implicit index flags")
              }
              Implicit
            }
            3 => {
              guard (flags & 2) == 0 else {
                raise InvalidField("invalid fixed array flags")
              }
              let page_bits = reader.read_u8().to_int()
              guard page_bits > 0 && page_bits <= 31 else {
                raise InvalidField("invalid fixed array page bits")
              }
              FixedArray(page_bits)
            }
            4 => {
              guard (flags & 2) == 0 else {
                raise InvalidField("invalid extensible array flags")
              }
              let config : ExtensibleArrayConfig = {
                max_bits: reader.read_u8().to_int(),
                index_elements: reader.read_u8().to_int(),
                min_pointers: reader.read_u8().to_int(),
                min_elements: reader.read_u8().to_int(),
                page_bits: reader.read_u8().to_int(),
              }
              validate_extensible_config(config)
              ExtensibleArray(config)
            }
            5 => {
              guard (flags & 2) == 0 else {
                raise InvalidField("invalid v2 B-tree flags")
              }
              let size = reader.read_u32_le()
              guard size <= 0x7FFFFFFFU else {
                raise InvalidField("v2 B-tree node size is too large")
              }
              let config : BTreeV2Config = {
                node_size: size.reinterpret_as_int(),
                split_percent: reader.read_u8().to_int(),
                merge_percent: reader.read_u8().to_int(),
              }
              validate_btree_v2_config(config)
              BTreeV2(config)
            }
            _ =>
              raise UnsupportedFeature(
                "chunk index type " +
                index.to_string() +
                " is not supported yet",
              )
          }
          data_address = reader.read_uint_le(offset_size)
        } else {
          let dimensionality = reader.read_u8().to_int()
          data_address = reader.read_uint_le(offset_size)
          for _ in 1.. raise InvalidField("invalid data layout class")
    }
  }
  {
    version,
    layout_class,
    data_address,
    data_size,
    chunk_dimensions,
    element_size,
    compact_data,
    chunk_index,
  }
}