use nalgebra::{allocator::Allocator, DefaultAllocator, DimName, DimNameDiff, DimNameSub, U1};

use crate::{
    misc::FloatingPoint, prelude::NurbsSurface, surface::UVDirection,
    tessellation::adaptive_tessellation_node::AdaptiveTessellationNode,
};

use super::{
    adaptive_tessellation_node::{DividableDirection, NeighborDirection},
    adaptive_tessellation_option::AdaptiveTessellationOptions,
};

/// Processor for adaptive tessellation of a surface
pub struct AdaptiveTessellationProcessor<'a, T: FloatingPoint, D: DimName>
where
    D: DimNameSub<U1>,
    DefaultAllocator: Allocator<D>,
    DefaultAllocator: Allocator<DimNameDiff<D, U1>>,
{
    /// The surface to tessellate
    surface: &'a NurbsSurface<T, D>,
    /// The created nodes for the tessellation
    nodes: Vec<AdaptiveTessellationNode<T, D>>,
}

impl<'a, T: FloatingPoint, D: DimName> AdaptiveTessellationProcessor<'a, T, D>
where
    D: DimNameSub<U1>,
    DefaultAllocator: Allocator<D>,
    DefaultAllocator: Allocator<DimNameDiff<D, U1>>,
{
    pub fn new(
        surface: &'a NurbsSurface<T, D>,
        nodes: Vec<AdaptiveTessellationNode<T, D>>,
    ) -> Self {
        Self { surface, nodes }
    }

    pub fn into_nodes(self) -> Vec<AdaptiveTessellationNode<T, D>> {
        self.nodes
    }

    pub fn divide<F>(&mut self, id: usize, options: &AdaptiveTessellationOptions<T, D, F>)
    where
        F: Fn(&AdaptiveTessellationNode<T, D>) -> Option<DividableDirection> + Copy,
    {
        let direction = if self.surface.u_degree() > 1 {
            UVDirection::U
        } else {
            UVDirection::V
        };
        self.iterate(id, options, 0, direction);
    }

    /// iterate over the nodes and divide them if necessary
    fn iterate<F>(
        &mut self,
        id: usize,
        options: &AdaptiveTessellationOptions<T, D, F>,
        current_depth: usize,
        direction: UVDirection,
    ) where
        F: Fn(&AdaptiveTessellationNode<T, D>) -> Option<DividableDirection> + Copy,
    {
        let next_node_id_0 = self.nodes.len();
        let next_node_id_1 = next_node_id_0 + 1;

        let node = self.nodes.get_mut(id).unwrap();
        let dividable_direction = if current_depth < options.min_depth {
            Some(DividableDirection::Both)
        } else if current_depth >= options.max_depth {
            None
        } else {
            // Check min_edge_length: stop subdivision if edges are already short enough
            if let Some(ref min_edge_length) = options.min_edge_length {
                let edge = node.max_edge_length();
                if edge < *min_edge_length {
                    return;
                }
            }

            // Normal-based criterion
            let normal_criterion = options
                .divider
                .and_then(|f| f(node))
                .or(node.should_divide(options.norm_tolerance));

            // Edge length criterion: subdivide if any edge exceeds max_edge_length.
            // Mirror the constraint logic from `should_divide`: a direction is locked
            // when any corner is constrained on that direction, so subdivision must not
            // introduce new vertices on that boundary.
            let edge_criterion = options.max_edge_length.as_ref().and_then(|max_len| {
                if node.max_edge_length() <= *max_len {
                    return None;
                }
                let v_dir_ok = node.corners.iter().all(|c| !c.is_u_constrained());
                let u_dir_ok = node.corners.iter().all(|c| !c.is_v_constrained());
                match (u_dir_ok, v_dir_ok) {
                    (true, true) => Some(DividableDirection::Both),
                    (true, false) => Some(DividableDirection::U),
                    (false, true) => Some(DividableDirection::V),
                    (false, false) => None,
                }
            });

            normal_criterion.or(edge_criterion)
        };

        // set the divided direction of the node
        node.direction = match dividable_direction {
            Some(DividableDirection::Both) => direction,
            Some(DividableDirection::U) => UVDirection::U,
            Some(DividableDirection::V) => UVDirection::V,
            None => {
                return;
            }
        };

        let (c0, c1) = {
            match node.direction {
                UVDirection::U => {
                    let east_mid = node.evaluate_mid_point(self.surface, NeighborDirection::East);
                    let west_mid = node.evaluate_mid_point(self.surface, NeighborDirection::West);

                    // counter-clockwise order [south, east, north, west]
                    let bottom = [
                        node.corners[0].clone(), // left-bottom
                        node.corners[1].clone(), // right-bottom
                        east_mid.clone(),
                        west_mid.clone(),
                    ];
                    let top = [
                        west_mid,
                        east_mid,
                        node.corners[2].clone(), // right-top
                        node.corners[3].clone(), // left-top
                    ];

                    node.assign_children([next_node_id_0, next_node_id_1]);

                    // assign neighbors to bottom node
                    let bottom_neighbors = [
                        *node.neighbors.at(NeighborDirection::South),
                        *node.neighbors.at(NeighborDirection::East),
                        Some(next_node_id_1), // top as north neighbor
                        *node.neighbors.at(NeighborDirection::West),
                    ];

                    // assign neighbors to top node
                    let top_neighbors = [
                        Some(next_node_id_0), // bottom as south neighbor
                        *node.neighbors.at(NeighborDirection::East),
                        *node.neighbors.at(NeighborDirection::North),
                        *node.neighbors.at(NeighborDirection::West),
                    ];

                    (
                        AdaptiveTessellationNode::new(next_node_id_0, bottom, bottom_neighbors),
                        AdaptiveTessellationNode::new(next_node_id_1, top, top_neighbors),
                    )
                }
                UVDirection::V => {
                    let south_mid = node.evaluate_mid_point(self.surface, NeighborDirection::South);
                    let north_mid = node.evaluate_mid_point(self.surface, NeighborDirection::North);

                    let left = [
                        node.corners[0].clone(), // left-bottom
                        south_mid.clone(),
                        north_mid.clone(),
                        node.corners[3].clone(), // left-top
                    ];
                    let right = [
                        south_mid,
                        node.corners[1].clone(), // right-bottom
                        node.corners[2].clone(), // right-top
                        north_mid,
                    ];

                    node.assign_children([next_node_id_0, next_node_id_1]);

                    let left_neighbors = [
                        *node.neighbors.at(NeighborDirection::South),
                        Some(next_node_id_1), // right as east neighbor
                        *node.neighbors.at(NeighborDirection::North),
                        *node.neighbors.at(NeighborDirection::West),
                    ];
                    let right_neighbors = [
                        *node.neighbors.at(NeighborDirection::South),
                        *node.neighbors.at(NeighborDirection::East),
                        *node.neighbors.at(NeighborDirection::North),
                        Some(next_node_id_0), // left as west neighbor
                    ];

                    (
                        AdaptiveTessellationNode::new(next_node_id_0, left, left_neighbors),
                        AdaptiveTessellationNode::new(next_node_id_1, right, right_neighbors),
                    )
                }
            }
        };

        self.nodes.push(c0);
        self.nodes.push(c1);

        // divide all children recursively
        for next_id in [next_node_id_0, next_node_id_1] {
            self.iterate(next_id, options, current_depth + 1, direction.opposite());
        }
    }
}
