use log::info;

use super::{Attractor, Node, Point};

pub fn calculate_new_node_position(
    node: &Node,
    attractors: &Vec<*mut Attractor>,
    segment_length: u16,
) -> Option<Point> {
    calculate_new_node_position_with_skip(node, attractors, segment_length, 0)
}

pub fn calculate_new_node_position_with_skip(
    node: &Node,
    attractors: &Vec<*mut Attractor>,
    segment_length: u16,
    attractors_to_skip: usize,
) -> Option<Point> {
    assert!(
        attractors.len() > 0,
        "There must be at least one attractor!"
    );
    let mut attraction_sum_x = 0;
    let mut attraction_sum_y = 0;

    for a in attractors.iter().skip(attractors_to_skip) {
        if attractors_to_skip > 0 {
            info!("working on attractor {:?}", a);
        }
        let attractor: &Attractor;
        unsafe {
            attractor = &**a;
        }

        if attractors_to_skip > 0 {
            info!("successfully worked with attractor {:?}", a);
        }
        attraction_sum_x += attractor.position.x - node.position.x;
        attraction_sum_y += attractor.position.y - node.position.y;
    }

    // Here this fixes what happens when the sum of the vectors towards attractors is 0
    // See test new_node_ignores_attractors_when_average_results_in_no_movement
    if attraction_sum_y == 0 && attraction_sum_x == 0 {
        if attractors.len() == 1 {
            let a = attractors.first().unwrap();
            unsafe {
                (**a).dead = true;
            }
            return None;
        }
        assert!(
            attractors.len() > attractors_to_skip + 1,
            "There must be more attractors in the array than the number to skip"
        );

        return calculate_new_node_position_with_skip(
            node,
            attractors,
            segment_length,
            attractors_to_skip + 1,
        );
    }

    let point = Point {
        x: node.position.x + attraction_sum_x as i32,
        y: node.position.y + attraction_sum_y as i32,
    };

    let new_point = node.position.movement(point, segment_length);
    println!("just did movement");
    dbg!(node.position, point, segment_length, new_point);

    let new_distance = new_point.distance(&node.position);
    if new_distance > (segment_length * 2) as f64 {
        info!(
            "new node {:?} is far from parent {:?}",
            new_point, node.position
        );
        let mut readable_attractors: Vec<&Attractor> = Vec::new();
        unsafe {
            attractors.iter().for_each(|a| {
                readable_attractors.push(&**a);
            });
        }
        info!(
            "Calculations inputs are 
              attractors {:?}
              node.position {:?}
              segment_length {}
              point {:?}",
            readable_attractors, node.position, segment_length, point
        );

        info!(
            "Calculations outputs are 
              distance {}
              attraction_sum x {} , y {}
              new_point {:?}",
            new_distance, attraction_sum_x, attraction_sum_y, new_point
        );
    }

    Some(new_point)
}

#[cfg(test)]
mod tests {
    use super::*;
    const SEGMENT_LENGTH: u16 = 5;

    #[test]
    fn new_node_moves_toward_single_attractor() {
        let mut growth_cell = GrowthCell::from_positions((0, 0), [(0, 10)].to_vec());
        let mut attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut();

        let point = calculate_new_node_position(
            &growth_cell.node,
            &mut attractors_as_ptr_mut,
            SEGMENT_LENGTH,
        )
        .unwrap();
        assert_eq!(point, Point::new((0, 5)));
    }

    #[test]
    fn new_node_ignores_attractors_when_average_results_in_no_movement() {
        let mut growth_cell = GrowthCell::from_positions((10, 0), [(0, 0), (20, 0)].to_vec());
        let mut attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut();

        let point = calculate_new_node_position(
            &growth_cell.node,
            &mut attractors_as_ptr_mut,
            SEGMENT_LENGTH,
        )
        .unwrap();
        assert_eq!(point, Point::new((15, 0)));
    }

    #[test]
    fn new_node_ignores_dead_attractor() {
        // TODO
        assert!(false);
    }

    #[test_log::test]
    fn large_number_of_attractors() {
        let attractors = [
            (1048, 241),
            (935, 268),
            (953, 363),
            (978, 333),
            (988, 355),
            (894, 234),
            (892, 331),
            (1050, 275),
            (972, 261),
            (882, 280),
            (1013, 212),
            (1023, 233),
            (899, 197),
            (911, 285),
            (879, 291),
            (977, 293),
            (938, 233),
            (975, 196),
            (964, 295),
            (967, 197),
            (869, 242),
            (945, 323),
            (1035, 287),
            (962, 243),
            (1023, 297),
            (952, 185),
            (984, 261),
            (1011, 264),
            (931, 209),
            (900, 281),
            (1006, 319),
            (929, 295),
            (936, 325),
            (1001, 182),
            (995, 225),
        ];
        let node = (960, 266);

        let mut growth_cell = GrowthCell::from_positions(node, attractors.to_vec());
        let mut attractors_as_ptr_mut = growth_cell.attractors_as_ptr_mut();

        let point = calculate_new_node_position(
            &growth_cell.node,
            &mut attractors_as_ptr_mut,
            SEGMENT_LENGTH,
        )
        .unwrap();
        assert_eq!(point, Point::new((965, 266)));
        /*
                      segment_length 5
        Calculations outputs are
                      distance 996.1706681086329
                      attraction_sum x 17 , y 1
                      resulting point Point { x: 960, y: 266 }
                      */
    }

    struct GrowthCell {
        node: Node,
        attractors: Vec<Attractor>,
    }

    impl GrowthCell {
        pub fn from_positions(node: (i32, i32), attractors_positions: Vec<(i32, i32)>) -> Self {
            assert!(attractors_positions.len() >= 1);
            let node = Node {
                position: Point::new(node),
                children: Vec::new().into(),
                growing: true,
            };
            let mut attractors = Vec::new();
            for p in attractors_positions.iter() {
                attractors.push(Attractor {
                    position: Point::new(*p),
                    dead: false,
                });
            }
            Self { node, attractors }
        }

        fn attractors_as_ptr_mut(&mut self) -> Vec<*mut Attractor> {
            self.attractors
                .iter_mut()
                .map(|a| a as *mut Attractor)
                .collect()
        }
    }
}
