#![feature(generators)]
#![feature(generator_clone)]
#![feature(iter_from_generator)]
#![warn(clippy::pedantic)]

use std::{
    io::{stdin, Read},
    iter,
    str::FromStr,
};

type Word = i64;

#[derive(Debug, Clone, PartialEq, Eq)]
struct MachineState {
    x: Word,
}

impl MachineState {
    const fn new() -> Self {
        Self { x: 1 }
    }

    fn execute(&mut self, op: MicroOp) {
        match op {
            MicroOp::Noop => {}
            MicroOp::Addx(i) => self.x += i,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum MicroOp {
    Noop,
    Addx(Word),
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Instruction {
    Noop,
    Addx(Word),
}

impl Instruction {
    pub fn expand(self) -> impl Iterator<Item = MicroOp> {
        iter::from_generator(move || match self {
            Self::Noop => yield MicroOp::Noop,
            Self::Addx(x) => {
                yield MicroOp::Noop;
                yield MicroOp::Addx(x);
            }
        })
    }
}

impl FromStr for Instruction {
    type Err = ();

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let mut args = s.split_whitespace();
        match args.next().ok_or(())? {
            "noop" => Ok(Self::Noop),
            "addx" => {
                let i = args.next().ok_or(())?.parse().map_err(|_| ())?;
                Ok(Self::Addx(i))
            }
            _ => Err(()),
        }
    }
}

fn main() {
    const START: MachineState = MachineState::new();

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

    let states: Vec<_> = data
        .lines()
        .map(|l| l.parse().unwrap())
        .flat_map(Instruction::expand)
        .scan(START, |state, op| {
            let prev = state.clone();
            state.execute(op);
            Some(prev)
        })
        .enumerate()
        .map(|(i, state)| (Word::try_from(i).unwrap() + 1, state))
        .collect();

    let sum: Word = states
        .iter()
        .skip(20 - 1)
        .step_by(40)
        .map(|(i, state)| i * state.x)
        .sum();
    println!("{:?}", sum);

    let crt: Vec<_> = states
        .iter()
        .map(|(i, state)| ((i - 1) % 40).abs_diff(state.x) < 2)
        .map(|x| if x { '#' } else { '.' })
        .collect();

    for line in crt.chunks_exact(40) {
        println!("{}", line.iter().collect::<String>());
    }
}
