use std::str::FromStr;

#[derive(Clone, Copy)]
enum Direction {
    Forward,
    Up,
    Down,
}
#[derive(Clone, Copy)]
struct Instruction {
    direction: Direction,
    distance: i64,
}

enum InstructionParseError {
    Direction,
    Distance(std::num::ParseIntError),
}

impl From<std::num::ParseIntError> for InstructionParseError {
    fn from(src: std::num::ParseIntError) -> Self {
        Self::Distance(src)
    }
}

impl FromStr for Instruction {
    type Err = InstructionParseError;

    fn from_str(source: &str) -> Result<Self, Self::Err> {
        let mut words = source.split_whitespace();
        let ds = words.next().ok_or(InstructionParseError::Direction)?;
        let direction = match ds {
            "forward" => Ok(Direction::Forward),
            "down" => Ok(Direction::Down),
            "up" => Ok(Direction::Up),
            _ => Err(InstructionParseError::Direction),
        }?;
        let distance: i64 = words.next().unwrap_or("").parse()?;
        Ok(Self {
            direction,
            distance,
        })
    }
}

fn trace_1<I: Iterator<Item = Instruction>>(source: I) -> (i64, i64) {
    let mut x = 0;
    let mut y = 0;
    for i in source {
        match i.direction {
            Direction::Forward => {
                x += i.distance;
            }
            Direction::Up => {
                y -= i.distance;
            }
            Direction::Down => {
                y += i.distance;
            }
        }
    }
    (x, y)
}

fn trace_2<I: Iterator<Item = Instruction>>(source: I) -> (i64, i64) {
    let mut aim: i64 = 0;
    let mut x = 0;
    let mut y = 0;
    for i in source {
        match i.direction {
            Direction::Forward => {
                x += i.distance;
                y += aim * i.distance;
            }
            Direction::Up => {
                aim -= i.distance;
            }
            Direction::Down => {
                aim += i.distance;
            }
        }
    }
    (x, y)
}

fn main() {
    use std::io::BufRead;
    let args: Vec<_> = std::env::args().collect();
    let file = std::io::BufReader::new(
        std::fs::File::open(<String as AsRef<std::path::Path>>::as_ref(
            &args[1],
        ))
        .unwrap(),
    );
    let instructions: Vec<Instruction> = file
        .lines()
        .flat_map(|line| {
            line.ok()
                .and_then(|line| str::parse::<Instruction>(line.as_ref()).ok())
        })
        .collect();
    let (x1, y1) = trace_1(instructions.iter().copied());
    println!("Version 1:");
    println!("x: {}; y: {}; product: {}", x1, y1, x1 * y1);
    let (x2, y2) = trace_2(instructions.iter().copied());
    println!();
    println!("Version 2:");
    println!("x: {}; y: {}; product: {}", x2, y2, x2 * y2);
}