// Vector2D and Point math library for 2D vector graphic kernel operations.

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

///|
pub impl Show for Vec2 with fn output(self, logger) {
  logger.write_string("{x: ")
  logger.write_string(self.x.to_string())
  logger.write_string(", y: ")
  logger.write_string(self.y.to_string())
  logger.write_string("}")
}

///|
pub fn Vec2::new(x : Double, y : Double) -> Vec2 {
  { x, y }
}

///|
pub fn Vec2::zero() -> Vec2 {
  { x: 0.0, y: 0.0 }
}

///|
pub fn Vec2::unit_x() -> Vec2 {
  { x: 1.0, y: 0.0 }
}

///|
pub fn Vec2::unit_y() -> Vec2 {
  { x: 0.0, y: 1.0 }
}

///|
pub fn Vec2::add(self : Vec2, other : Vec2) -> Vec2 {
  { x: self.x + other.x, y: self.y + other.y }
}

///|
pub fn Vec2::sub(self : Vec2, other : Vec2) -> Vec2 {
  { x: self.x - other.x, y: self.y - other.y }
}

///|
pub fn Vec2::scale(self : Vec2, scalar : Double) -> Vec2 {
  { x: self.x * scalar, y: self.y * scalar }
}

///|
pub fn Vec2::negate(self : Vec2) -> Vec2 {
  { x: -self.x, y: -self.y }
}

///|
pub fn Vec2::dot(self : Vec2, other : Vec2) -> Double {
  self.x * other.x + self.y * other.y
}

///|
pub fn Vec2::cross(self : Vec2, other : Vec2) -> Double {
  self.x * other.y - self.y * other.x
}

///|
pub fn Vec2::length_sq(self : Vec2) -> Double {
  self.x * self.x + self.y * self.y
}

///|
pub fn Vec2::length(self : Vec2) -> Double {
  self.length_sq().sqrt()
}

///|
pub fn Vec2::normalize(self : Vec2) -> Vec2 {
  let len = self.length()
  if len < 0.000000000001 {
    Vec2::zero()
  } else {
    self.scale(1.0 / len)
  }
}

///|
pub fn Vec2::distance_sq(self : Vec2, other : Vec2) -> Double {
  self.sub(other).length_sq()
}

///|
pub fn Vec2::distance(self : Vec2, other : Vec2) -> Double {
  self.distance_sq(other).sqrt()
}

///|
pub fn Vec2::lerp(self : Vec2, target : Vec2, t : Double) -> Vec2 {
  { x: self.x + (target.x - self.x) * t, y: self.y + (target.y - self.y) * t }
}

///|
pub fn Vec2::angle(self : Vec2) -> Double {
  @cmath.atan2(self.y, self.x)
}

///|
pub fn Vec2::angle_between(self : Vec2, other : Vec2) -> Double {
  let dot_prod = self.dot(other)
  let len_prod = self.length() * other.length()
  if len_prod < 0.000000000001 {
    0.0
  } else {
    let cos_val = dot_prod / len_prod
    let clamped = if cos_val > 1.0 {
      1.0
    } else if cos_val < -1.0 {
      -1.0
    } else {
      cos_val
    }
    @cmath.acos(clamped)
  }
}

///|
pub fn Vec2::rotate(self : Vec2, angle_radians : Double) -> Vec2 {
  let cos_a = @cmath.cos(angle_radians)
  let sin_a = @cmath.sin(angle_radians)
  { x: self.x * cos_a - self.y * sin_a, y: self.x * sin_a + self.y * cos_a }
}

///|
pub fn Vec2::perpendicular(self : Vec2) -> Vec2 {
  { x: -self.y, y: self.x }
}

///|
pub fn Vec2::reflect(self : Vec2, normal : Vec2) -> Vec2 {
  let n = normal.normalize()
  let d = self.dot(n)
  self.sub(n.scale(2.0 * d))
}

///|
pub fn Vec2::min(self : Vec2, other : Vec2) -> Vec2 {
  {
    x: if self.x < other.x {
      self.x
    } else {
      other.x
    },
    y: if self.y < other.y {
      self.y
    } else {
      other.y
    },
  }
}

///|
pub fn Vec2::max(self : Vec2, other : Vec2) -> Vec2 {
  {
    x: if self.x > other.x {
      self.x
    } else {
      other.x
    },
    y: if self.y > other.y {
      self.y
    } else {
      other.y
    },
  }
}