///|
pub(all) enum Dataspace {
  Scalar
  Simple(Array[Int32])
  Null
} derive(Debug, Eq)

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

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

///|
/// Scalar has one element, null has none, a zero-length axis has none.
pub fn Dataspace::elements(
  self : Dataspace,
  limits~ : Limits = Limits::default(),
) -> Int32 raise H5Error {
  limits.validate()
  let n = match self {
    Scalar => 1
    Null => 0
    Simple(shape) => {
      if shape.length() == 0 || shape.length() > limits.max_rank {
        raise Limit("dataspace rank")
      }
      let mut total = 1
      for x in shape {
        if x < 0 {
          raise Invalid("negative dimension", 0)
        }
      }
      if shape.contains(0) {
        return 0
      }
      for x in shape {
        total = checked_mul(total, x)
      }
      total
    }
  }
  if n > limits.max_elements {
    raise Limit("dataspace elements")
  }
  n
}

///|
fn Dataspace::copy(self : Dataspace) -> Dataspace {
  match self {
    Simple(shape) => Simple(shape.copy())
    _ => self
  }
}

///|
fn Dataspace::encode(self : Dataspace, limits : Limits) -> Bytes raise H5Error {
  ignore(self.elements(limits~))
  let b = buffer()
  b.u8(2)
  b.u8(
    match self {
      Simple(d) => d.length()
      _ => 0
    },
  )
  b.u8(0)
  b.u8(
    match self {
      Scalar => 0
      Simple(_) => 1
      Null => 2
    },
  )
  if self is Simple(dims) {
    for dim in dims {
      b.uint(dim.to_uint64(), 8)
    }
  }
  b.finish()
}

///|
fn decode_space(
  c : Cursor,
  size_width : Int32,
  limits : Limits,
) -> Dataspace raise H5Error {
  let at = c.pos
  let version = c.u8()
  let rank = c.u8()
  let flags = c.u8()
  if rank > limits.max_rank {
    raise Limit("dataspace rank")
  }
  let kind = match version {
    1 => {
      c.skip(5)
      if rank == 0 {
        0
      } else {
        1
      }
    }
    2 => c.u8()
    _ => raise Unsupported("dataspace version", at)
  }
  if (flags & 0xfe) != 0 {
    raise Unsupported("dataspace flags", at)
  }
  let dims : Array[Int32] = []
  for i = 0; i < rank; i = i + 1 {
    dims.push(c.index(size_width))
  }
  if (flags & 1) != 0 {
    // Maximum dimensions may be unlimited; they do not change current extent.
    for i = 0; i < rank; i = i + 1 {
      ignore(c.uint(size_width))
    }
  }
  let space = match kind {
    0 if rank == 0 => Scalar
    1 if rank > 0 => Simple(dims)
    2 if rank == 0 => Null
    _ => raise Invalid("dataspace kind/rank mismatch", at)
  }
  ignore(space.elements(limits~))
  space
}

///|
test "scalar null and empty arrays are distinct" {
  let limits = Limits::default()
  for space in [Scalar, Null, Simple([2, 3]), Simple([0, 7])] {
    let bytes = space.encode(limits)
    assert_eq(decode_space(cursor(bytes, 0, bytes.length()), 8, limits), space)
  }
  assert_eq(Scalar.elements(), 1)
  assert_eq(Null.elements(), 0)
  assert_eq(Simple([10, 0]).elements(), 0)
  assert_true(
    try {
      ignore(Simple([2147483647, 2]).elements())
      false
    } catch {
      _ => true
    },
  )
}