use ndarray::prelude::*;

#[derive(Debug)]
struct Point {
    x: usize,
    y: usize,
}

fn count_map(input: &str) -> Array2<usize> {
    let rows = input.lines().count();
    let columns = input.lines().next().unwrap().len();
    let mut count_map: Array2<usize> = Array::zeros((rows, columns));

    let s_point = input.lines().enumerate().find_map(|(y,line)| {
        line.chars().position(|character| {character == 'S'}).and_then(|x| {Some(Point{x, y})})
    }).unwrap();
    let initial_directions = [
        (['|','7','F'], (0_i32, -1_i32), s_point.y > 0),
        (['-','7','J'], (1_i32, 0_i32), s_point.x < columns),
        (['|','L','J'], (0_i32, 1_i32), s_point.y < rows),
        (['-','F','L'], (-1_i32, 0_i32), s_point.x > 0),
    ];
    let mut current_nodes = initial_directions.into_iter().filter_map(|(characters, (x, y), condition)| {
        condition.then(|| {
            let next_character = input.as_bytes()[((s_point.y as i32 + y)*(columns as i32+1) + s_point.x as i32 + x) as usize] as char;
            characters.contains(&next_character).then_some(Point{x: (s_point.x as i32 + x) as usize, y: (s_point.y as i32 + y) as usize})
        })
    }).flatten().collect::<Vec<_>>();

    let mut num_steps = 1;
    while current_nodes.len() > 0 {
        let mut next_nodes = vec![];
        current_nodes.iter().for_each(|point| {
            let character = input.lines().nth(point.y).unwrap().chars().nth(point.x).unwrap();
            if ['|', 'L', 'J'].contains(&character) && point.y != 0 {
                next_nodes.push(Point{x: point.x, y: point.y-1});
            }
            if ['-', 'F', 'L'].contains(&character) && point.x != columns-1 {
                next_nodes.push(Point{x: point.x+1, y: point.y});
            }
            if ['|', '7', 'F'].contains(&character) && point.y != rows-1 {
                next_nodes.push(Point{x: point.x, y: point.y+1});
            }
            if ['-', 'J', '7'].contains(&character) && point.x != 0 {
                next_nodes.push(Point{x: point.x-1, y: point.y});
            }
        });
        num_steps += 1;
        current_nodes = next_nodes.iter().filter_map(|point| {
            (count_map[[point.y, point.x]] == 0).then(|| {
                count_map[[point.y, point.x]] = num_steps;
                Point{x: point.x, y: point.y}
            })
        }).collect();
    }
    count_map
}

pub fn part1(input: &str) -> String {
    let count_map = count_map(input);
    count_map.iter().max().unwrap().to_string()
}

pub fn part2(input: &str) -> String {
    let mut count_map = count_map(input);
    let &[rows, columns] = count_map.shape() else { panic!() };
    let s_position = input.lines().enumerate().find_map(|(y,line)| {
        line.chars().position(|character| {character == 'S'}).and_then(|x| {Some(Point{x, y})})
    }).unwrap();
    count_map[[s_position.y, s_position.x]] = 1;
    let mut inside_points = 0;
    for y in 0..rows {
        for x in 0..columns {
            if count_map[[y,x]] == 0 { // It's not a part of the main loop
                // Now we go in one direction
                let mut sum = 0.0_f32;
                let mut count_f = 0_i32;
                let mut count_7 = 0_i32;
                for new_y in (0..y).rev() {
                    // L's cancel F's, J's cancel 7's
                    if count_map[[new_y,x]] != 0 {
                        // We can add either a half or full count
                        let next_character = input.as_bytes()[new_y*(columns+1) + x] as char;
                        if ['7', 'S'].contains(&next_character) {
                            count_7 += 1;
                        } else if next_character == 'J' {
                            count_7 -= 1;
                        } else if next_character == 'L' {
                            count_f -= 1;
                        } else if next_character == 'F' {
                            count_f += 1;
                        } else if next_character == '-' {
                            sum += 1.0;
                        }
                    }
                }
                sum += (count_f.abs() + count_7.abs()) as f32 / 2.0;
                if sum % 2.0 != 0.0 {
                    inside_points += 1;
                }
            }
        }
    }
    inside_points.to_string()
}

pub mod prelude {
    pub use super::part1;
    pub use super::part2;
}

