// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// Four components: a point in homogeneous coordinates, a colour with alpha, or
/// four numbers that travel together.
///
/// A `Vec4` is what a `Mat4` multiplies, and what a shader writes into a vertex
/// buffer: `to_array` gives the four in the order the buffer wants them.
///
/// Values are immutable; every operation returns a new one.
#derive(Eq)
pub(all) struct Vec4 {
  /// The x component: east.
  x : Float
  /// The y component: north.
  y : Float
  /// The z component: towards the viewer.
  z : Float
  /// The fourth component: one for a point, zero for a direction.
  w : Float
}

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

///|
/// The vector from its four components.
pub fn Vec4::new(x : Float, y : Float, z : Float, w : Float) -> Vec4 {
  { x, y, z, w, }
}

///|
/// The same value in all four components.
pub fn Vec4::splat(value : Float) -> Vec4 {
  { x: value, y: value, z: value, w: value, }
}

///|
/// The zero vector.
pub fn Vec4::zero() -> Vec4 {
  { x: 0.0, y: 0.0, z: 0.0, w: 0.0, }
}

///|
/// The vector with one in every component.
pub fn Vec4::one() -> Vec4 {
  { x: 1.0, y: 1.0, z: 1.0, w: 1.0, }
}

///|
/// The unit vector along +x.
pub fn Vec4::unit_x() -> Vec4 {
  { x: 1.0, y: 0.0, z: 0.0, w: 0.0, }
}

///|
/// The unit vector along +y.
pub fn Vec4::unit_y() -> Vec4 {
  { x: 0.0, y: 1.0, z: 0.0, w: 0.0, }
}

///|
/// The unit vector along +z.
pub fn Vec4::unit_z() -> Vec4 {
  { x: 0.0, y: 0.0, z: 1.0, w: 0.0, }
}

///|
/// The unit vector along +w.
pub fn Vec4::unit_w() -> Vec4 {
  { x: 0.0, y: 0.0, z: 0.0, w: 1.0, }
}

///|
/// The dot product, over all four components.
///
/// In homogeneous coordinates this is what a plane's `dot` does with a point,
/// and what the `w` component of a transformed vector carries.
pub fn Vec4::dot(self : Vec4, other : Vec4) -> Float {
  self.x * other.x + self.y * other.y + self.z * other.z + self.w * other.w
}

///|
/// The length of the vector, in four dimensions.
pub fn Vec4::length(self : Vec4) -> Float {
  self.length_squared().sqrt()
}

///|
/// The square of the length, without the square root.
pub fn Vec4::length_squared(self : Vec4) -> Float {
  self.dot(self)
}

///|
/// The distance between the two points, in four dimensions.
pub fn Vec4::distance(self : Vec4, other : Vec4) -> Float {
  (self - other).length()
}

///|
/// The square of the distance between the two points.
pub fn Vec4::distance_squared(self : Vec4, other : Vec4) -> Float {
  (self - other).length_squared()
}

///|
/// The vector scaled to length one, or `Vec4::zero()` if it has no direction.
pub fn Vec4::normalize(self : Vec4) -> Vec4 {
  let length = self.length()
  if length == 0.0 {
    Vec4::zero()
  } else {
    let scale : Float = 1.0 / length
    self.scale(scale)
  }
}

///|
/// The vector scaled to length one, or `None` if it has no direction.
pub fn Vec4::try_normalize(self : Vec4) -> Vec4? {
  let length = self.length()
  if length == 0.0 {
    None
  } else {
    Some(self.scale(1.0 / length))
  }
}

///|
/// The vector scaled to length one, or `fallback` if it has no direction.
pub fn Vec4::normalize_or(self : Vec4, fallback : Vec4) -> Vec4 {
  match self.try_normalize() {
    Some(unit) => unit
    None => fallback
  }
}

///|
/// The vector multiplied by `factor`.
///
/// This is how a homogeneous position is taken out of projective space: divide
/// by `w` with `divide`, or scale by its reciprocal.
pub fn Vec4::scale(self : Vec4, factor : Float) -> Vec4 {
  {
    x: self.x * factor,
    y: self.y * factor,
    z: self.z * factor,
    w: self.w * factor,
  }
}

///|
/// The vector divided by `divisor`, component by component.
pub fn Vec4::divide(self : Vec4, divisor : Float) -> Vec4 {
  {
    x: self.x / divisor,
    y: self.y / divisor,
    z: self.z / divisor,
    w: self.w / divisor,
  }
}

///|
/// The point a fraction of the way from `self` to `other`, all four components.
pub fn Vec4::lerp(self : Vec4, other : Vec4, t : Float) -> Vec4 {
  {
    x: self.x + (other.x - self.x) * t,
    y: self.y + (other.y - self.y) * t,
    z: self.z + (other.z - self.z) * t,
    w: self.w + (other.w - self.w) * t,
  }
}

///|
/// The component-wise smaller of the two.
pub fn Vec4::min(self : Vec4, other : Vec4) -> Vec4 {
  {
    x: self.x.min(other.x),
    y: self.y.min(other.y),
    z: self.z.min(other.z),
    w: self.w.min(other.w),
  }
}

///|
/// The component-wise larger of the two.
pub fn Vec4::max(self : Vec4, other : Vec4) -> Vec4 {
  {
    x: self.x.max(other.x),
    y: self.y.max(other.y),
    z: self.z.max(other.z),
    w: self.w.max(other.w),
  }
}

///|
/// The vector with every component pinned between `low` and `high`.
pub fn Vec4::clamp(self : Vec4, low : Vec4, high : Vec4) -> Vec4 {
  {
    x: self.x.clamp(min=low.x, max=high.x),
    y: self.y.clamp(min=low.y, max=high.y),
    z: self.z.clamp(min=low.z, max=high.z),
    w: self.w.clamp(min=low.w, max=high.w),
  }
}

///|
/// The vector with every component replaced by its absolute value.
pub fn Vec4::abs(self : Vec4) -> Vec4 {
  { x: self.x.abs(), y: self.y.abs(), z: self.z.abs(), w: self.w.abs(), }
}

///|
/// The vector with every component rounded down to a whole number.
pub fn Vec4::floor(self : Vec4) -> Vec4 {
  {
    x: self.x.floor(),
    y: self.y.floor(),
    z: self.z.floor(),
    w: self.w.floor(),
  }
}

///|
/// The vector with every component rounded up to a whole number.
pub fn Vec4::ceil(self : Vec4) -> Vec4 {
  { x: self.x.ceil(), y: self.y.ceil(), z: self.z.ceil(), w: self.w.ceil(), }
}

///|
/// The vector with every component rounded to the nearest whole number, a half
/// going up.
pub fn Vec4::round(self : Vec4) -> Vec4 {
  {
    x: self.x.round(),
    y: self.y.round(),
    z: self.z.round(),
    w: self.w.round(),
  }
}

///|
/// The vector with every component replaced by its fractional part.
pub fn Vec4::fract(self : Vec4) -> Vec4 {
  {
    x: self.x - self.x.floor(),
    y: self.y - self.y.floor(),
    z: self.z - self.z.floor(),
    w: self.w - self.w.floor(),
  }
}

///|
/// The first two components.
pub fn Vec4::xy(self : Vec4) -> Vec2 {
  { x: self.x, y: self.y, }
}

///|
/// The first three components: the position a homogeneous point names, before
/// the division by `w`.
pub fn Vec4::xyz(self : Vec4) -> Vec3 {
  { x: self.x, y: self.y, z: self.z, }
}

///|
/// All four components backwards: `(w, z, y, x)`.
pub fn Vec4::wzyx(self : Vec4) -> Vec4 {
  { x: self.w, y: self.z, z: self.y, w: self.x, }
}

///|
/// The components in order, `[x, y, z, w]`, for handing to a buffer.
pub fn Vec4::to_array(self : Vec4) -> FixedArray[Float] {
  [self.x, self.y, self.z, self.w]
}

///|
/// Whether every component is finite.
pub fn Vec4::is_finite(self : Vec4) -> Bool {
  !(self.x.is_nan() ||
  self.x.is_inf() ||
  self.y.is_nan() ||
  self.y.is_inf() ||
  self.z.is_nan() ||
  self.z.is_inf() ||
  self.w.is_nan() ||
  self.w.is_inf())
}

///|
/// Whether the two vectors agree to within `eps` component by component.
pub fn Vec4::approx(self : Vec4, other : Vec4, eps? : Float) -> Bool {
  approx(self.x, other.x, eps?) &&
  approx(self.y, other.y, eps?) &&
  approx(self.z, other.z, eps?) &&
  approx(self.w, other.w, eps?)
}

///|
pub impl Add for Vec4 with fn add(self, other) {
  {
    x: self.x + other.x,
    y: self.y + other.y,
    z: self.z + other.z,
    w: self.w + other.w,
  }
}

///|
pub extend Vec4 with Add::{add}

///|
pub impl Sub for Vec4 with fn sub(self, other) {
  {
    x: self.x - other.x,
    y: self.y - other.y,
    z: self.z - other.z,
    w: self.w - other.w,
  }
}

///|
pub extend Vec4 with Sub::{sub}

///|
pub impl Mul for Vec4 with fn mul(self, other) {
  {
    x: self.x * other.x,
    y: self.y * other.y,
    z: self.z * other.z,
    w: self.w * other.w,
  }
}

///|
pub extend Vec4 with Mul::{mul}

///|
pub impl Div for Vec4 with fn div(self, other) {
  {
    x: self.x / other.x,
    y: self.y / other.y,
    z: self.z / other.z,
    w: self.w / other.w,
  }
}

///|
pub extend Vec4 with Div::{div}

///|
pub impl Neg for Vec4 with fn neg(self) {
  { x: -self.x, y: -self.y, z: -self.z, w: -self.w, }
}

///|
pub extend Vec4 with Neg::{neg}

///|
pub impl Show for Vec4 with fn output(self, logger) {
  logger.write_string("Vec4(\{self.x}, \{self.y}, \{self.z}, \{self.w})")
}

///|
pub extend Vec4 with Show::{output, to_string}

///|
pub impl Debug for Vec4 with fn to_repr(self) {
  @debug.Repr::ctor("Vec4", [
    (Some("x"), @debug.Repr::float(self.x)),
    (Some("y"), @debug.Repr::float(self.y)),
    (Some("z"), @debug.Repr::float(self.z)),
    (Some("w"), @debug.Repr::float(self.w)),
  ])
}

///|
pub extend Vec4 with Debug::{to_repr}