///|
fn integer_value(value : Value) -> Int? {
  value.as_int()
}

///|
fn indices_from_value(value : Value) -> Array[Int] {
  match value {
    List(values) =>
      values.fold(init=Array::new(), (result, item) => {
        match integer_value(item) {
          Some(index) => {
            result.push(index)
            result
          }
          None => {
            for nested in indices_from_value(item) {
              result.push(nested)
            }
            result
          }
        }
      })
    Typed(name~, value~) => {
      ignore(name)
      indices_from_value(value)
    }
    _ => []
  }
}

///|
fn triangle_array(indices : Array[Int]) -> Array[(Int, Int, Int)] {
  let triangles : Array[(Int, Int, Int)] = []
  let mut i = 0
  while i + 2 < indices.length() {
    let a = indices[i] - 1
    let b = indices[i + 1] - 1
    let c = indices[i + 2] - 1
    if a >= 0 && b >= 0 && c >= 0 {
      triangles.push((a, b, c))
    }
    i += 3
  }
  triangles
}

///|
fn polygon_points(index : ModelIndex, loop_id : Int) -> Array[Point3] {
  match index.entity(loop_id) {
    Some(loop_entity) if loop_entity.name.to_upper() == "IFCPOLYLOOP" =>
      reference_list(entity_value(loop_entity, 0)).fold(init=Array::new(), (
        result,
        point_id,
      ) => {
        match point_from_entity(index, point_id) {
          Some(point) => {
            result.push(point)
            result
          }
          None => result
        }
      })
    _ => []
  }
}

///|
fn add_fan_triangles(points : Array[Point3], mesh : Mesh) -> Unit {
  if points.length() >= 3 {
    let mut i = 1
    while i + 1 < points.length() {
      let offset = mesh.points.length()
      mesh.points.append(points)
      mesh.triangles.push((offset, offset + i, offset + i + 1))
      i += 1
    }
  }
}

///|
fn faceted_brep_mesh(index : ModelIndex, entity : Entity) -> Mesh {
  let mesh : Mesh = { points: [], triangles: [] }
  let outer_id = optional_reference_argument(entity, 0)
  match outer_id {
    Some(shell_id) =>
      match index.entity(shell_id) {
        Some(shell) =>
          for face_id in reference_list(entity_value(shell, 0)) {
            match index.entity(face_id) {
              Some(face) => {
                let bound_id = optional_reference_argument(face, 0)
                match bound_id {
                  Some(bound_id) =>
                    match index.entity(bound_id) {
                      Some(bound) => {
                        let loop_id = optional_reference_argument(bound, 1)
                        match loop_id {
                          Some(id) =>
                            add_fan_triangles(polygon_points(index, id), mesh)
                          None => ()
                        }
                      }
                      None => ()
                    }
                  None => ()
                }
              }
              None => ()
            }
          }
        None => ()
      }
    None => ()
  }
  mesh
}

///|
pub fn mesh_for_geometry(index : ModelIndex, id : Int) -> Mesh raise ParseError {
  let entity = index.require(id)
  let upper = entity.name.to_upper()
  if upper == "IFCTRIANGULATEDFACESET" {
    let points = match optional_reference_argument(entity, 0) {
      Some(coordinate_id) =>
        match index.entity(coordinate_id) {
          Some(coordinates) => points_from_entity(index, coordinates)
          None => []
        }
      None => []
    }
    let indices = match entity_value(entity, 2) {
      Some(value) => indices_from_value(value)
      None => []
    }
    { points, triangles: triangle_array(indices) }
  } else if upper == "IFCFACETEDBREP" || upper == "IFCMANIFOLDSOLIDBREP" {
    faceted_brep_mesh(index, entity)
  } else {
    { points: [], triangles: [] }
  }
}

///|
pub fn mesh_point_count(mesh : Mesh) -> Int {
  mesh.points.length()
}

///|
pub fn mesh_triangle_count(mesh : Mesh) -> Int {
  mesh.triangles.length()
}

///|
pub fn topology_face_count(
  index : ModelIndex,
  id : Int,
) -> Int raise ParseError {
  let entity = index.require(id)
  match entity.name.to_upper() {
    "IFCFACETEDBREP" | "IFCMANIFOLDSOLIDBREP" =>
      match optional_reference_argument(entity, 0) {
        Some(shell_id) =>
          match index.entity(shell_id) {
            Some(shell) => reference_list(entity_value(shell, 0)).length()
            None => 0
          }
        None => 0
      }
    "IFCTRIANGULATEDFACESET" =>
      match entity_value(entity, 2) {
        Some(value) => indices_from_value(value).length() / 3
        None => 0
      }
    _ => 0
  }
}