///|
/// Reorder axes in row-major storage without converting sample bits.
pub fn ArrayValue::transpose(
  self : ArrayValue,
  axes : Array[Int32],
  limits~ : Limits = Limits::default(),
) -> ArrayValue raise H5Error {
  let shape = match self.space {
    Simple(s) => s
    _ => raise Invalid("transpose requires simple dataspace", 0)
  }
  ignore(self.space.elements(limits~))
  if axes.length() != shape.length() {
    raise Invalid("axis permutation rank", 0)
  }
  let seen = Array::make(shape.length(), false)
  for axis in axes {
    if axis < 0 || axis >= shape.length() || seen[axis] {
      raise Invalid("invalid or repeated axis", axis)
    }
    seen[axis] = true
  }
  let next_shape = axes.map(a => shape[a])
  let count = self.length()
  let width = self.dtype.width()
  let output_bytes = checked_mul(count, width)
  if output_bytes > limits.max_file_bytes {
    raise Limit("transpose bytes")
  }
  let output = Array::make(output_bytes, b'\x00')
  // Empty dimensions need no stride products, which could otherwise overflow.
  if count > 0 {
    let strides = Array::make(shape.length(), 1)
    for i = shape.length() - 2; i >= 0; i = i - 1 {
      strides[i] = checked_mul(strides[i + 1], shape[i + 1])
    }
    for n = 0; n < count; n = n + 1 {
      let mut remainder = n
      let mut source = 0
      for i = axes.length() - 1; i >= 0; i = i - 1 {
        let coordinate = remainder % next_shape[i]
        remainder = remainder / next_shape[i]
        source += coordinate * strides[axes[i]]
      }
      for b = 0; b < width; b = b + 1 {
        output[n * width + b] = self.raw[source * width + b]
      }
    }
  }
  ArrayValue::new(
    self.dtype,
    Simple(next_shape),
    Bytes::from_array(output),
    limits~,
  )
}

///|
/// Concatenate compatible fixed-width arrays on one axis. No implicit casts.
pub fn ArrayValue::concat_axis(
  self : ArrayValue,
  other : ArrayValue,
  axis : Int32,
  limits~ : Limits = Limits::default(),
) -> ArrayValue raise H5Error {
  let left = match self.space {
    Simple(s) => s
    _ => raise Invalid("concat simple dataspace", 0)
  }
  let right = match other.space {
    Simple(s) => s
    _ => raise Invalid("concat simple dataspace", 0)
  }
  ignore(self.space.elements(limits~))
  ignore(other.space.elements(limits~))
  if self.dtype != other.dtype ||
    left.length() != right.length() ||
    axis < 0 ||
    axis >= left.length() {
    raise Invalid("concat type, rank or axis mismatch", axis)
  }
  for i = 0; i < left.length(); i = i + 1 {
    if i != axis && left[i] != right[i] {
      raise Invalid("concat non-axis extent", i)
    }
  }
  if left[axis] > 2147483647 - right[axis] {
    raise Limit("concat extent")
  }
  let shape = left.copy()
  shape[axis] += right[axis]
  let count = Simple(shape).elements(limits~)
  let width = self.dtype.width()
  let size = checked_mul(count, width)
  if size > limits.max_file_bytes {
    raise Limit("concat bytes")
  }
  let bytes = Array::make(size, b'\x00')
  if count > 0 {
    let mut inner = width
    for i = axis + 1; i < left.length(); i = i + 1 {
      inner = checked_mul(inner, left[i])
    }
    let lblock = checked_mul(inner, left[axis])
    let rblock = checked_mul(inner, right[axis])
    let mut outer = 1
    for i = 0; i < axis; i = i + 1 {
      outer = checked_mul(outer, left[i])
    }
    for n = 0; n < outer; n = n + 1 {
      let dest = n * (lblock + rblock)
      for i = 0; i < lblock; i = i + 1 {
        bytes[dest + i] = self.raw[n * lblock + i]
      }
      for i = 0; i < rblock; i = i + 1 {
        bytes[dest + lblock + i] = other.raw[n * rblock + i]
      }
    }
  }
  ArrayValue::new(self.dtype, Simple(shape), Bytes::from_array(bytes), limits~)
}