///|
pub(all) struct Position {
  x : Double
  y : Double
  z : Double
} derive(Debug, Eq)

///|
pub fn Position::validate(self : Position) -> Unit raise LasError {
  require(
    finite(self.x) && finite(self.y) && finite(self.z),
    "nonfinite position",
  )
}

///|
pub fn Transform::position(
  self : Transform,
  point : Point,
) -> Position raise LasError {
  self.validate()
  let p = {
    x: point.x.to_double() * self.sx + self.ox,
    y: point.y.to_double() * self.sy + self.oy,
    z: point.z.to_double() * self.sz + self.oz,
  }
  p.validate()
  p
}

///|
fn quantize_axis(
  value : Double,
  scale : Double,
  offset : Double,
) -> Int32 raise LasError {
  let raw = (value - offset) / scale
  require(finite(raw), "coordinate quantization overflow")
  let rounded = if raw >= 0.0 {
    (raw + 0.5).floor()
  } else {
    (raw - 0.5).ceil()
  }
  require(
    rounded >= -2147483648.0 && rounded <= 2147483647.0,
    "coordinate outside signed int32 range",
  )
  rounded.to_int()
}

///|
/// Nearest integer; exact half-way cases round away from zero.
pub fn Transform::quantize(
  self : Transform,
  position : Position,
  attributes : Point,
) -> Point raise LasError {
  self.validate()
  position.validate()
  {
    ..attributes,
    x: quantize_axis(position.x, self.sx, self.ox),
    y: quantize_axis(position.y, self.sy, self.oy),
    z: quantize_axis(position.z, self.sz, self.oz),
  }
}

///|
pub(all) struct Bounds {
  min : Position
  max : Position
} derive(Debug, Eq)

///|
pub fn Bounds::validate(self : Bounds) -> Unit raise LasError {
  self.min.validate()
  self.max.validate()
  require(
    self.min.x <= self.max.x &&
    self.min.y <= self.max.y &&
    self.min.z <= self.max.z,
    "reversed query bounds",
  )
}

///|
pub fn Bounds::contains(self : Bounds, p : Position) -> Bool {
  p.x >= self.min.x &&
  p.x <= self.max.x &&
  p.y >= self.min.y &&
  p.y <= self.max.y &&
  p.z >= self.min.z &&
  p.z <= self.max.z
}

///|
pub fn point_bounds(
  points : Array[Point],
  transform : Transform,
) -> Bounds? raise LasError {
  transform.validate()
  if points.is_empty() {
    return None
  }
  let first = transform.position(points[0])
  let mut low = first
  let mut high = first
  for p in points {
    let q = transform.position(p)
    low = {
      x: if q.x < low.x {
        q.x
      } else {
        low.x
      },
      y: if q.y < low.y {
        q.y
      } else {
        low.y
      },
      z: if q.z < low.z {
        q.z
      } else {
        low.z
      },
    }
    high = {
      x: if q.x > high.x {
        q.x
      } else {
        high.x
      },
      y: if q.y > high.y {
        q.y
      } else {
        high.y
      },
      z: if q.z > high.z {
        q.z
      } else {
        high.z
      },
    }
  }
  Some({ min: low, max: high, })
}

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

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

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

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