// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (c) 2023 lacklustr@protonmail.com https://github.com/eadf
// This file is part of the hronn crate.

use crate::geo::ConvertTo;
use crate::meshanalyzer::SearchResult;
use crate::prelude::MaximumTracker;
use crate::probe::square_end_to_edge::square_end_to_edge_collision;
use crate::probe::{PlaneMetadata, QueryParameters, TriangleMetadata, xy_distance_to_line_squared};
use crate::{HronnError, is_inside_2d_triangle};
use vector_traits::{
    approx,
    prelude::{GenericVector3, HasXYZ},
};

pub(crate) fn shared_square_end_precompute_logic<T: GenericVector3, MESH>(
    vertices: &[MESH],
    triangles: &[u32],
    probe_radius: T::Scalar,
) -> Result<Vec<TriangleMetadata<T, MESH>>, HronnError>
where
    MESH: HasXYZ,
    MESH: ConvertTo<T>,
{
    triangles
        .as_ref()
        .chunks(3)
        .map(|triangle| {
            TriangleMetadata::new_for_square_end(
                probe_radius,
                vertices[triangle[0] as usize].to(),
                vertices[triangle[1] as usize].to(),
                vertices[triangle[2] as usize].to(),
            )
        })
        .collect()
}

// this function must use the CollisionFn signature
pub(crate) fn square_end_compute_collision<T: GenericVector3, MESH: HasXYZ + ConvertTo<T>>(
    qp: &QueryParameters<'_, T, MESH>,
    site_index: u32,
    center: T::Vector2,
    mt: &mut MaximumTracker<SearchResult<T>>,
) where
    T::Scalar: approx::UlpsEq,
{
    let triangle_index = site_index * 3;

    if let Some(PlaneMetadata {
        pft,
        translated_triangle: [p0, p1, p2],
        ..
    }) = &qp.meta_data[site_index as usize].plane
    {
        if is_inside_2d_triangle::<T::Vector2>(center, *p0, *p1, *p2) {
            // if the sphere center is inside the pre-computed triangle, we know that the plane detection
            // will result in the highest point, so there is no need to test the edges.
            mt.insert(SearchResult::new(site_index, pft.compute_z(center)));

            // no need to proceed with the edge detection
            return;
        }
    }

    let p0: T = qp.vertices[qp.indices[triangle_index as usize] as usize].to();
    let p1: T = qp.vertices[qp.indices[(triangle_index + 1) as usize] as usize].to();
    let p2: T = qp.vertices[qp.indices[(triangle_index + 2) as usize] as usize].to();

    let closest_edge = xy_distance_to_line_squared(center, p0, p1).min_two(
        xy_distance_to_line_squared(center, p1, p2),
        xy_distance_to_line_squared(center, p2, p0),
    );

    square_end_to_edge_collision::<T>(closest_edge.0, qp.probe_radius, site_index, mt);
    square_end_to_edge_collision::<T>(closest_edge.1, qp.probe_radius, site_index, mt);
}
