///|
/// Immutable, owned row-major-in-NRRD-order shape (axis zero is fastest).
pub struct Shape {
  axes : Array[Int32]
  strides : Array[Int32]
  count : Int32
} derive(Debug, Eq)

///|
pub fn Shape::new(
  sizes : Array[Int32],
  limits~ : Limits = Limits::default(),
) -> Shape raise NrrdError {
  limits.validate()
  if sizes.length() < 1 || sizes.length() > 8 {
    raise Invalid("dimension must be 1..8")
  }
  let axes = sizes.copy()
  let strides = []
  let mut count = 1
  for size in axes {
    if size <= 0 {
      raise Invalid("axis size must be positive")
    }
    if size > limits.max_samples / count {
      raise Limit("sample budget")
    }
    strides.push(count)
    count = count * size
  }
  { axes, strides, count, }
}

///|
pub fn Shape::sizes(self : Shape) -> Array[Int32] {
  self.axes.copy()
}

///|
pub fn Shape::dimension(self : Shape) -> Int32 {
  self.axes.length()
}

///|
pub fn Shape::sample_count(self : Shape) -> Int32 {
  self.count
}

///|
pub fn Shape::offset(
  self : Shape,
  index : Array[Int32],
) -> Int32 raise NrrdError {
  if index.length() != self.dimension() {
    raise Invalid("index dimension")
  }
  let mut offset = 0
  for axis, value in index {
    if value < 0 || value >= self.axes[axis] {
      raise Invalid("index out of bounds")
    }
    offset = offset + value * self.strides[axis]
  }
  offset
}

///|
pub fn Shape::indices(
  self : Shape,
  offset : Int32,
) -> Array[Int32] raise NrrdError {
  if offset < 0 || offset >= self.count {
    raise Invalid("offset out of bounds")
  }
  self.axes.mapi(fn(axis, size) { offset / self.strides[axis] % size })
}

///|
fn Shape::byte_count(
  self : Shape,
  scalar : Scalar,
  limits : Limits,
) -> Int32 raise NrrdError {
  if self.count > limits.max_payload_bytes / scalar.width() {
    raise Limit("payload budget")
  }
  self.count * scalar.width()
}

///|
fn check_axis(shape : Shape, axis : Int32) -> Unit raise NrrdError {
  if axis < 0 || axis >= shape.dimension() {
    raise Invalid("axis out of bounds")
  }
}