///|
/// Geometry-validated indexed triangles; manifold validation is done by Mesh.
pub struct IndexedMesh {
  priv vertices : Array[Vec3]
  priv triangles : Array[(Int, Int, Int)]
} derive(Eq, Debug)

///|
/// Validate before copying. Returned data never aliases the supplied arrays.
pub fn IndexedMesh::new(
  vertices : Array[Vec3],
  triangles : Array[(Int, Int, Int)],
  limits? : Limits = Limits::default(),
) -> IndexedMesh raise SurfaceError {
  if vertices.length() > limits.max_vertices {
    raise LimitExceeded("input vertices")
  }
  if triangles.length() > limits.max_input_faces {
    raise LimitExceeded("input faces")
  }
  for i, p in vertices {
    if !finite(p.x) || !finite(p.y) || !finite(p.z) {
      raise NonFiniteCoordinate(i)
    }
  }
  let referenced = Array::make(vertices.length(), false)
  let seen : Map[(Int, Int, Int), Bool] = Map([])
  for fi, (a, b, c) in triangles {
    for v in [a, b, c] {
      if v < 0 || v >= vertices.length() {
        raise InvalidIndex(fi, v)
      }
      referenced[v] = true
    }
    if a == b ||
      b == c ||
      c == a ||
      degenerate(vertices[a], vertices[b], vertices[c]) {
      raise DegenerateFace(fi)
    }
    let key = face_key(a, b, c)
    if seen.contains(key) {
      raise DuplicateFace(fi)
    }
    seen[key] = true
  }
  for v, used in referenced {
    if !used {
      raise IsolatedVertex(v)
    }
  }
  { vertices: vertices.copy(), triangles: triangles.copy(), }
}

///|
/// A copy of the owned positions.
pub fn IndexedMesh::positions(self : IndexedMesh) -> Array[Vec3] {
  self.vertices.copy()
}

///|
/// A copy of the owned triangle index triples.
pub fn IndexedMesh::faces(self : IndexedMesh) -> Array[(Int, Int, Int)] {
  self.triangles.copy()
}

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

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

///|
fn finite(x : Double) -> Bool {
  !x.is_nan() && !x.is_inf()
}

///|
fn abs(x : Double) -> Double {
  if x < 0.0 {
    -x
  } else {
    x
  }
}

///|
fn face_key(a : Int, b : Int, c : Int) -> (Int, Int, Int) {
  let lo = Int::min(a, Int::min(b, c))
  let hi = Int::max(a, Int::max(b, c))
  (lo, a + b + c - lo - hi, hi)
}

///|
/// Scale before subtraction/cross products, including near Double's extremes.
fn degenerate(a : Vec3, b : Vec3, c : Vec3) -> Bool {
  let mut scale = 0.0
  for p in [a, b, c] {
    scale = Double::max(
      scale,
      Double::max(abs(p.x), Double::max(abs(p.y), abs(p.z))),
    )
  }
  if scale == 0.0 {
    return true
  }
  let ux = b.x / scale - a.x / scale
  let uy = b.y / scale - a.y / scale
  let uz = b.z / scale - a.z / scale
  let vx = c.x / scale - a.x / scale
  let vy = c.y / scale - a.y / scale
  let vz = c.z / scale - a.z / scale
  let edge_scale = Double::max(
    Double::max(abs(ux), Double::max(abs(uy), abs(uz))),
    Double::max(abs(vx), Double::max(abs(vy), abs(vz))),
  )
  if edge_scale == 0.0 {
    return true
  }
  let (ux, uy, uz) = (ux / edge_scale, uy / edge_scale, uz / edge_scale)
  let (vx, vy, vz) = (vx / edge_scale, vy / edge_scale, vz / edge_scale)
  let (nx, ny, nz) = (uy * vz - uz * vy, uz * vx - ux * vz, ux * vy - uy * vx)
  let longest_sq = Double::max(
    ux * ux + uy * uy + uz * uz,
    Double::max(
      vx * vx + vy * vy + vz * vz,
      (ux - vx) * (ux - vx) + (uy - vy) * (uy - vy) + (uz - vz) * (uz - vz),
    ),
  )
  nx * nx + ny * ny + nz * nz <= 1.0e-28 * longest_sq * longest_sq
}