#![feature(iterator_try_reduce)]
#![warn(clippy::pedantic)]

use std::{
    collections::BTreeSet,
    io::{stdin, Read},
};

use aoc::vec2::{Direction, Vec2};

fn knot_movement(previous: Vec2, knot: Vec2) -> Option<Vec2> {
    let (dx, dy) = (previous - knot).into();

    if dx.abs() <= 1 && dy.abs() <= 1 {
        None
    } else {
        Some((dx.signum(), dy.signum()).into())
    }
}

fn simulate_knots(num_knots: usize, instructions: &[(Direction, usize)]) -> usize {
    const START: Vec2 = Vec2::new(0, 0);

    assert!(num_knots >= 2);

    let mut knots = vec![START; num_knots];
    let mut tail_positions = BTreeSet::new();
    tail_positions.insert(START);

    for (direction, distance) in instructions {
        for _i in 0..*distance {
            knots[0] += Vec2::from(*direction);

            if let Some(tail) = knots.iter_mut().try_reduce(|previous, knot| {
                *knot += knot_movement(*previous, *knot)?;
                Some(knot)
            }) {
                tail_positions.insert(tail.copied().unwrap());
            }
        }
    }

    tail_positions.len()
}

fn main() {
    let mut data = String::new();
    stdin().read_to_string(&mut data).unwrap();

    let instructions: Vec<_> = data
        .lines()
        .map(|line| {
            let (direction, distance) = line.split_once(' ').unwrap();
            let direction = direction.parse().unwrap();
            let distance = distance.parse().unwrap();
            (direction, distance)
        })
        .collect();

    println!("{:?}", simulate_knots(2, &instructions));
    println!("{:?}", simulate_knots(10, &instructions));
}
