use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let input = fs::read_to_string("input")?;
    let slope = parse_slope(&input)?;

    // Part 1
    let encounters = count_encounters(&slope, (1, 3));
    println!("{}", encounters);

    // Part 2
    let encounters: usize = [(1, 1), (1, 3), (1, 5), (1, 7), (2, 1)]
        .iter()
        .map(|angle| count_encounters(&slope, *angle))
        .product();
    println!("{}", encounters);

    Ok(())
}

fn parse_slope(input: &str) -> Result<Vec<Vec<usize>>, String> {
    input
        .lines()
        .map(|line| {
            line.chars()
                .map(|c| match c {
                    '.' => Ok(0),
                    '#' => Ok(1),
                    other => Err(format!("Invalid slope; contains character: {}", other)),
                })
                .collect()
        })
        .collect()
}

fn count_encounters(input: &Vec<Vec<usize>>, angle: (usize, usize)) -> usize {
    input
        .iter()
        .enumerate()
        .step_by(angle.0)
        .map(|(i, row)| row[(i * angle.1 / angle.0) % row.len()])
        .sum()
}

#[cfg(test)]
mod tests {
    use super::*;
    use indoc::indoc;

    #[test]
    fn test_simple() -> Result<(), Box<dyn std::error::Error>> {
        let example = indoc!(
            "..##.......
             #...#...#..
             .#....#..#.
             ..#.#...#.#
             .#...##..#.
             ..#.##.....
             .#.#.#....#
             .#........#
             #.##...#...
             #...##....#
             .#..#...#.#"
        );

        let parsed = parse_slope(example)?;
        assert_eq!(count_encounters(&parsed, (1, 3)), 7);
        Ok(())
    }

    #[test]
    fn test_simple2() -> Result<(), Box<dyn std::error::Error>> {
        let example = indoc!(
            "..##.......
             #...#...#..
             .#....#..#.
             ..#.#...#.#
             .#...##..#.
             ..#.##.....
             .#.#.#....#
             .#........#
             #.##...#...
             #...##....#
             .#..#...#.#"
        );

        let parsed = parse_slope(example)?;
        assert_eq!(count_encounters(&parsed, (2, 1)), 2);
        Ok(())
    }
}