///|
/// One value in an enumeration datatype, preserving the signedness of its
/// integer base type.
#derive(Eq, @debug.Debug)
pub(all) enum EnumValue {
  Signed(Int64)
  Unsigned(UInt64)
}

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

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

///|
/// A named member of an enumeration datatype.
#derive(@debug.Debug)
pub(all) struct EnumMember {
  name : String
  value : EnumValue
}

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

///|
/// A named field within a compound datatype.
#derive(@debug.Debug)
pub(all) struct CompoundMember {
  name : String
  offset : Int32
  datatype : Datatype
}

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

///|
#derive(@debug.Debug)
pub(all) struct Datatype {
  class : Int32
  version : Int32
  size : Int32
  signed : Bool
  little_endian : Bool
  vlen_is_string : Bool
  enum_members : Array[EnumMember]
  compound_members : Array[CompoundMember]
  base_types : Array[Datatype]
  array_dimensions : Array[UInt64]
  bit_offset : Int32
  bit_precision : Int32
  float_sign_position : Int32
  float_exponent_position : Int32
  float_exponent_size : Int32
  float_mantissa_position : Int32
  float_mantissa_size : Int32
  float_exponent_bias : Int32
  float_normalization : Int32
  string_padding : Int32
  string_charset : Int32
  opaque_tag : String
  reference_kind : Int32
}

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

///|
fn read_datatype_name(
  reader : Reader,
  padded : Bool,
  description : String,
) -> String raise Hdf5Error {
  let start = reader.position()
  let mut end = start
  while end < reader.limit && reader.bytes.unsafe_get(end) != b'\x00' {
    end = end + 1
  }
  guard end < reader.limit else { raise UnexpectedEof(start.to_int64()) }
  guard end > start else { raise InvalidField(description + " is empty") }
  let length = end - start
  let raw = reader.read_bytes(length)
  reader.skip(1)
  if padded {
    let used = length + 1
    reader.skip((used + 7) / 8 * 8 - used)
  }
  @utf8.decode(raw.get_view().unwrap()) catch {
    _ => raise InvalidField("invalid UTF-8 " + description)
  }
}

///|
fn datatype_offset_width(size : Int32) -> Int32 {
  if size <= 0x100 {
    1
  } else if size <= 0x10000 {
    2
  } else {
    4
  }
}

///|
fn datatype_array_size(
  dimensions : Array[UInt64],
  base_size : Int32,
) -> Int32 raise Hdf5Error {
  let mut size = base_size.to_int64()
  for dimension in dimensions {
    guard dimension > 0UL &&
      dimension <= 0x7FFFFFFFUL &&
      size <= 0x7FFFFFFFL / dimension.reinterpret_as_int64() else {
      raise InvalidField("array datatype size is too large")
    }
    size = size * dimension.reinterpret_as_int64()
  }
  size.to_int()
}

///|
fn array_datatype(
  version : Int32,
  dimensions : Array[UInt64],
  base : Datatype,
) -> Datatype raise Hdf5Error {
  {
    class: 10,
    version,
    size: datatype_array_size(dimensions, base.size),
    signed: false,
    little_endian: true,
    vlen_is_string: false,
    enum_members: [],
    compound_members: [],
    base_types: [base],
    array_dimensions: dimensions,
    bit_offset: 0,
    bit_precision: 0,
    float_sign_position: 0,
    float_exponent_position: 0,
    float_exponent_size: 0,
    float_mantissa_position: 0,
    float_mantissa_size: 0,
    float_exponent_bias: 0,
    float_normalization: 0,
    string_padding: 0,
    string_charset: 0,
    opaque_tag: "",
    reference_kind: 0,
  }
}

///|
fn parse_datatype_reader(
  reader : Reader,
  depth : Int32,
) -> Datatype raise Hdf5Error {
  guard depth < 16 else { raise InvalidField("datatype nesting is too deep") }
  let class_and_version = reader.read_u8().to_int()
  let class = class_and_version & 0x0F
  let version = (class_and_version >> 4) & 0x0F
  let bit_field_0 = reader.read_u8().to_int()
  let bit_field_1 = reader.read_u8().to_int()
  let bit_field_2 = reader.read_u8().to_int()
  let size = reader.read_u32_le().reinterpret_as_int()
  guard size > 0 else { raise InvalidField("invalid datatype size") }
  let mut signed = if class == 0 { (bit_field_0 & 0x08) != 0 } else { false }
  let mut little_endian = (bit_field_0 & 0x01) == 0
  let mut vlen_is_string = false
  let enum_members : Array[EnumMember] = []
  let compound_members : Array[CompoundMember] = []
  let base_types : Array[Datatype] = []
  let array_dimensions : Array[UInt64] = []
  let mut bit_offset = 0
  let mut bit_precision = 0
  let mut float_sign_position = 0
  let mut float_exponent_position = 0
  let mut float_exponent_size = 0
  let mut float_mantissa_position = 0
  let mut float_mantissa_size = 0
  let mut float_exponent_bias = 0
  let mut float_normalization = 0
  let mut string_padding = 0
  let mut string_charset = 0
  let mut opaque_tag = ""
  let mut reference_kind = 0
  if class == 0 || class == 1 {
    guard version >= 1 && version <= 3 else {
      raise UnsupportedFeature("unsupported numeric datatype version")
    }
    let offset = reader.read_u16_le().to_int()
    let precision = reader.read_u16_le().to_int()
    bit_offset = offset
    bit_precision = precision
    if class == 0 {
      guard size >= 1 && size <= 8 else {
        raise UnsupportedFeature("integer width is outside 1..8 bytes")
      }
      guard (bit_field_0 & 0xF0) == 0 && bit_field_1 == 0 && bit_field_2 == 0 else {
        raise UnsupportedFeature("integer datatype flags are not supported")
      }
      guard precision > 0 && precision <= 64 && offset + precision <= size * 8 else {
        raise InvalidField("invalid integer offset or precision")
      }
    } else {
      let exponent_position = reader.read_u8().to_int()
      let exponent_size = reader.read_u8().to_int()
      let mantissa_position = reader.read_u8().to_int()
      let mantissa_size = reader.read_u8().to_int()
      let exponent_bias = reader.read_u32_le()
      float_normalization = (bit_field_0 >> 4) & 0x03
      guard size >= 1 &&
        size <= 8 &&
        precision > 0 &&
        precision <= 64 &&
        offset + precision <= size * 8 &&
        (bit_field_0 & 0xC0) == 0 &&
        bit_field_2 == 0 else {
        raise InvalidField("invalid floating-point layout")
      }
      guard float_normalization == 2 else {
        raise UnsupportedFeature(
          "floating-point normalization other than implied MSB",
        )
      }
      guard exponent_size > 0 &&
        exponent_size <= 32 &&
        mantissa_size >= 0 &&
        mantissa_size <= 64 &&
        bit_field_1 >= 0 &&
        bit_field_1 < precision &&
        exponent_position >= 0 &&
        exponent_position + exponent_size <= precision &&
        mantissa_position >= 0 &&
        mantissa_position + mantissa_size <= precision &&
        exponent_bias <= 0x7FFFFFFFU else {
        raise InvalidField("invalid floating-point field locations")
      }
      float_sign_position = bit_field_1
      float_exponent_position = exponent_position
      float_exponent_size = exponent_size
      float_mantissa_position = mantissa_position
      float_mantissa_size = mantissa_size
      float_exponent_bias = exponent_bias.reinterpret_as_int()
    }
  } else if class == 2 {
    guard version >= 1 &&
      version <= 3 &&
      (bit_field_0 & 0xFE) == 0 &&
      bit_field_1 == 0 &&
      bit_field_2 == 0 &&
      (size == 1 || size == 2 || size == 4 || size == 8) else {
      raise UnsupportedFeature("unsupported time datatype layout")
    }
    bit_precision = reader.read_u16_le().to_int()
    guard bit_precision > 0 && bit_precision <= size * 8 else {
      raise InvalidField("invalid time datatype precision")
    }
  } else if class == 3 {
    string_padding = bit_field_0 & 0x0F
    string_charset = (bit_field_0 >> 4) & 0x0F
    guard version >= 1 &&
      version <= 3 &&
      string_padding <= 2 &&
      string_charset <= 1 &&
      bit_field_1 == 0 &&
      bit_field_2 == 0 else {
      raise UnsupportedFeature("unsupported string datatype layout")
    }
  } else if class == 4 {
    guard version >= 1 &&
      version <= 3 &&
      (bit_field_0 & 0xF8) == 0 &&
      bit_field_1 == 0 &&
      bit_field_2 == 0 &&
      (size == 1 || size == 2 || size == 4 || size == 8) else {
      raise UnsupportedFeature("unsupported bitfield datatype layout")
    }
    bit_offset = reader.read_u16_le().to_int()
    bit_precision = reader.read_u16_le().to_int()
    guard bit_precision > 0 && bit_offset + bit_precision <= size * 8 else {
      raise InvalidField("invalid bitfield offset or precision")
    }
  } else if class == 5 {
    guard version >= 1 && version <= 3 && bit_field_1 == 0 && bit_field_2 == 0 else {
      raise UnsupportedFeature("unsupported opaque datatype version")
    }
    let tag_length = bit_field_0
    if tag_length > 0 {
      let padded = (tag_length + 7) / 8 * 8
      let raw = reader.read_bytes(padded)
      let mut end = 0
      while end < tag_length && raw.unsafe_get(end) != b'\x00' {
        guard raw.unsafe_get(end).to_int() < 128 else {
          raise InvalidField("opaque datatype tag is not ASCII")
        }
        end = end + 1
      }
      guard end < tag_length else {
        raise InvalidField("opaque datatype tag is not terminated")
      }
      opaque_tag = @utf8.decode(raw.get_view(start=0, end~).unwrap()) catch {
        _ => raise InvalidField("invalid opaque datatype tag")
      }
    }
  } else if class == 6 {
    guard version >= 1 && (version <= 4 || version == 5) && bit_field_2 == 0 else {
      raise UnsupportedFeature("unsupported compound datatype version")
    }
    let member_count = bit_field_0 | (bit_field_1 << 8)
    guard member_count > 0 else {
      raise InvalidField("compound datatype has no members")
    }
    for _ in 0.. 0 {
        array_datatype(version, legacy_dimensions, parsed)
      } else {
        parsed
      }
      guard member_offset + member_type.size.to_uint64() <= size.to_uint64() else {
        raise InvalidField("compound member extends beyond datatype size")
      }
      for existing in compound_members {
        guard existing.name != name else {
          raise InvalidField("duplicate compound member name")
        }
      }
      compound_members.push({
        name,
        offset: member_offset.to_int(),
        datatype: member_type,
      })
    }
  } else if class == 7 {
    reference_kind = bit_field_0 & 0x0F
    if version < 4 {
      guard version >= 1 &&
        reference_kind <= 1 &&
        (bit_field_0 & 0xF0) == 0 &&
        bit_field_1 == 0 &&
        bit_field_2 == 0 else {
        raise UnsupportedFeature("unsupported reference datatype layout")
      }
    } else {
      guard version == 4 &&
        (reference_kind == 2 || reference_kind == 3 || reference_kind == 4) &&
        bit_field_0 >> 4 == 1 &&
        bit_field_1 == 0 &&
        bit_field_2 == 0 else {
        raise UnsupportedFeature("unsupported reference datatype layout")
      }
    }
  } else if class == 8 {
    guard version >= 1 && version <= 4 && bit_field_2 == 0 else {
      raise UnsupportedFeature("unsupported enum datatype version")
    }
    let member_count = bit_field_0 | (bit_field_1 << 8)
    guard member_count > 0 else {
      raise InvalidField("enum datatype has no members")
    }
    let base = parse_datatype_reader(reader, depth + 1)
    guard base.class == 0 else {
      raise UnsupportedFeature("enum base type is not an integer")
    }
    guard base.size == size else {
      raise InvalidField("enum datatype size does not match its base type")
    }
    signed = base.signed
    little_endian = base.little_endian
    base_types.push(base)
    let names : Array[String] = []
    for _ in 0..> 4) & 0x0F
    string_charset = bit_field_1 & 0x0F
    guard version >= 1 &&
      version <= 4 &&
      (kind == 0 || kind == 1) &&
      (bit_field_1 & 0xF0) == 0 &&
      bit_field_2 == 0 &&
      (
        (kind == 0 && string_padding == 0 && string_charset == 0) ||
        (kind == 1 && string_padding <= 2 && string_charset <= 1)
      ) else {
      raise UnsupportedFeature("unsupported variable-length datatype layout")
    }
    base_types.push(parse_datatype_reader(reader, depth + 1))
  } else if class == 10 {
    guard version >= 2 &&
      version <= 5 &&
      bit_field_0 == 0 &&
      bit_field_1 == 0 &&
      bit_field_2 == 0 else {
      raise UnsupportedFeature("unsupported array datatype version")
    }
    let rank = reader.read_u8().to_int()
    guard rank > 0 && rank <= 32 else {
      raise InvalidField("invalid array datatype rank")
    }
    if version == 2 {
      guard reader.read_bytes(3) == b"\x00\x00\x00" else {
        raise InvalidField("invalid array datatype reserved bytes")
      }
    }
    for _ in 0.. Datatype raise Hdf5Error {
  parse_datatype_reader(Reader::new(data), 0)
}

///|
fn parse_datatype(message : HeaderMessage) -> Datatype raise Hdf5Error {
  guard message.msg_type == msg_datatype else {
    raise InvalidField("message is not a datatype message")
  }
  parse_datatype_data(message.data)
}