///|
/// Affine evaluation in the header's declared space; non-spatial axes contribute zero.
pub fn Tensor::world(
  self : Tensor,
  index : Array[Double],
) -> Array[Double] raise NrrdError {
  if index.length() != self.shape.dimension() {
    raise Invalid("world index dimension")
  }
  let m = self.metadata
  let origin = match m.origin {
    Some(v) => v.copy()
    None => raise Invalid("world requires space origin")
  }
  if m.directions.is_empty() {
    raise Invalid("world requires space directions")
  }
  for axis, value in index {
    if !finite(value) {
      raise Invalid("non-finite world index")
    }
    if m.directions[axis] is Some(direction) {
      for component, step in direction {
        origin[component] = origin[component] + value * step
      }
    }
  }
  for x in origin {
    if !finite(x) {
      raise Invalid("world coordinate overflow")
    }
  }
  origin
}

///|
fn shifted_metadata(
  m : Metadata,
  offsets : Array[Int32],
) -> Metadata raise NrrdError {
  if m.origin != None && m.directions.is_empty() {
    for count in offsets {
      if count != 0 {
        raise Unsupported("cannot shift origin without directions")
      }
    }
  }
  if m.origin is Some(origin) {
    if !m.directions.is_empty() {
      let shifted = origin.copy()
      for axis, count in offsets {
        if m.directions[axis] is Some(direction) {
          for component, step in direction {
            shifted[component] = shifted[component] + count.to_double() * step
          }
        }
      }
      for x in shifted {
        if !finite(x) {
          raise Invalid("origin overflow")
        }
      }
      return { ..m, origin: Some(shifted), }
    }
  }
  m
}

///|
fn remap_metadata(m : Metadata, axes : Array[Int32]) -> Metadata {
  {
    ..m,
    labels: if m.labels.is_empty() {
      []
    } else {
      axes.map(fn(a) { m.labels[a] })
    },
    units: if m.units.is_empty() {
      []
    } else {
      axes.map(fn(a) { m.units[a] })
    },
    kinds: if m.kinds.is_empty() {
      []
    } else {
      axes.map(fn(a) { m.kinds[a] })
    },
    spacings: if m.spacings.is_empty() {
      []
    } else {
      axes.map(fn(a) { m.spacings[a] })
    },
    directions: if m.directions.is_empty() {
      []
    } else {
      axes.map(fn(a) { m.directions[a] })
    },
  }
}