///|
/// Torus geometry (donut shape) in the XZ plane.

pub(all) struct Torus {
  center : Vec3
  major_radius : Double
  minor_radius : Double
  material : Material
} derive(Debug)

pub fn Torus::new(center~ : Vec3, major_radius~ : Double, minor_radius~ : Double, material~ : Material) -> Torus {
  { center, major_radius, minor_radius, material }
}

pub fn Torus::hit_torus(self : Torus, r : Ray, t_min~ : Double, t_max~ : Double) -> HitRecord? {
  let orig = r.orig - self.center
  let dir = r.dir
  let r_major = self.major_radius
  let r_minor = self.minor_radius

  let ox = orig.x
  let oy = orig.y
  let oz = orig.z
  let dx = dir.x
  let dy = dir.y
  let dz = dir.z

  let mut min_t = t_max

  for i in 0..=3 {
    let t_est = (t_min + t_max) / 2.0 + (i - 1).to_double() * 0.1
    let mut t = t_est

    for _ in 0..<50 {
      let x = ox + t * dx
      let y = oy + t * dy
      let z = oz + t * dz
      let sum_p_sq = x * x + y * y + z * z

      let f = sum_p_sq * sum_p_sq - 2.0 * sum_p_sq * (r_major * r_major + r_minor * r_minor) +
              4.0 * r_major * r_major * (x * x + z * z) + (r_major * r_major - r_minor * r_minor) * (r_major * r_major - r_minor * r_minor) / sum_p_sq * sum_p_sq

      let fp = 4.0 * sum_p_sq * (dx * x + dy * y + dz * z) -
               4.0 * (r_major * r_major + r_minor * r_minor) * (dx * x + dy * y + dz * z) +
               8.0 * r_major * r_major * (dx * x + dz * z)

      let dt = f / fp.max(1.0e-8)
      t = t - dt
      if dt.abs() < 1.0e-6 {
        break
      }
    }

    if t >= t_min && t < min_t {
      let p = r.at(t) - self.center
      let param_sq = p.x * p.x + p.y * p.y + p.z * p.z + r_major * r_major - r_minor * r_minor
      let normal = {
        x: 4.0 * p.x * param_sq - 8.0 * r_major * r_major * p.x,
        y: 4.0 * p.y * param_sq,
        z: 4.0 * p.z * param_sq - 8.0 * r_major * r_major * p.z,
      }.normalize()
      let local_p = r.at(t)
      if (local_p - self.center).y > -r_minor - 0.01 {
        if (local_p - self.center).length() > r_major * 0.1 {
          min_t = t
          let rec = { p: r.at(t), normal, t, front_face: false, material: self.material }
          let result = rec.set_face_normal(r, normal)
          return Some(result)
        }
      }
    }
  }

  None
}