//computer.rs
#[derive(Clone, PartialEq, Eq)]
pub enum ComputerState {
    Normal,
    Halt,
    Input,
}
use std::collections::VecDeque;
#[derive(Clone)]
pub struct Computer {
    pub memory: Vec<isize>,
    pub ip: usize,
    input: VecDeque<isize>,
    output: VecDeque<isize>,
    pub state: ComputerState,
}
impl Computer {
    pub fn is_halted(&self) -> bool {
        self.state == ComputerState::Halt
    }
    pub fn is_input_blocked(&self) -> bool {
        self.state == ComputerState::Input && self.input.is_empty()
    }
    pub fn step(&mut self) {
        if self.is_halted() || self.is_input_blocked() {
            return;
        }
        self.state = ComputerState::Normal;
        let opcode = self.memory[self.ip] % 100;
        match opcode {
            1 => {
                let src1 = self.get_value(1);
                let src2 = self.get_value(2);
                let tgt = self.memory[self.ip + 3];
                self.memory[tgt as usize] = src1 + src2;
                self.ip += 4;
            }
            2 => {
                let src1 = self.get_value(1);
                let src2 = self.get_value(2);
                let tgt = self.memory[self.ip + 3];
                self.memory[tgt as usize] = src1 * src2;
                self.ip += 4;
            }
            3 => {
                if self.input.is_empty() {
                    self.state = ComputerState::Input;
                    return;
                }
                let tgt = self.memory[self.ip + 1];
                self.memory[tgt as usize] = self.input.pop_front().unwrap();
                self.ip += 2;
            }
            4 => {
                let src = self.get_value(1);
                self.output.push_back(src);
                self.ip += 2;
            }
            5 => {
                let test = self.get_value(1);
                let tgt = self.get_value(2);
                self.ip = if test != 0 { tgt as usize } else { self.ip + 3 };
            }
            6 => {
                let test = self.get_value(1);
                let tgt = self.get_value(2);
                self.ip = if test == 0 { tgt as usize } else { self.ip + 3 };
            }
            7 => {
                let test1 = self.get_value(1);
                let test2 = self.get_value(2);
                let tgt = self.memory[self.ip + 3];
                self.memory[tgt as usize] = isize::from(test1 < test2);
                self.ip += 4;
            }
            8 => {
                let test1 = self.get_value(1);
                let test2 = self.get_value(2);
                let tgt = self.memory[self.ip + 3];
                self.memory[tgt as usize] = isize::from(test1 == test2);
                self.ip += 4;
            }
            99 => {
                self.state = ComputerState::Halt;
            }
            _ => unimplemented!(),
        }
    }
    pub fn new(code: &[isize]) -> Computer {
        Computer {
            memory: code.into(),
            ip: 0,
            input: VecDeque::new(),
            output: VecDeque::new(),
            state: ComputerState::Normal,
        }
    }
    fn get_value(&self, index: usize) -> isize {
        let mut mode = self.memory[self.ip] / 100;
        for _ in 1..index {
            mode /= 10;
        }
        match mode % 10 {
            0 => self.memory[self.memory[self.ip + index] as usize],
            1 => self.memory[self.ip + index],
            _ => unimplemented!(),
        }
    }
    pub fn run(&mut self) {
        while !self.is_halted() && !self.is_input_blocked() {
            self.step();
        }
    }
    pub fn write(&mut self, value: isize) {
        self.input.push_back(value);
    }
    pub fn read(&mut self) -> Option<isize> {
        self.output.pop_front()
    }
}
#[cfg(test)]
mod test {
    use super::*;
    fn test_memory(before: &[isize], after: &[isize]) {
        let mut comp = Computer::new(before);
        comp.run();
        assert_eq!(comp.memory, after);
    }
    #[test]
    fn test_day2() {
        test_memory(
            &[1, 9, 10, 3, 2, 3, 11, 0, 99, 30, 40, 50],
            &[3500, 9, 10, 70, 2, 3, 11, 0, 99, 30, 40, 50],
        );
        test_memory(&[1, 0, 0, 0, 99], &[2, 0, 0, 0, 99]);
        test_memory(&[2, 3, 0, 3, 99], &[2, 3, 0, 6, 99]);
        test_memory(&[2, 4, 4, 5, 99, 0], &[2, 4, 4, 5, 99, 9801]);
        test_memory(
            &[1, 1, 1, 4, 99, 5, 6, 0, 99],
            &[30, 1, 1, 4, 2, 5, 6, 0, 99],
        );
    }
    fn test_io(code: &[isize], input: &[isize], output: &[isize], msg: &str) {
        let mut comp = Computer::new(code);
        comp.input.extend(input.iter().copied());
        comp.run();
        assert_eq!(comp.output, output, "{msg}");
    }
    #[test]
    fn test_day5() {
        test_io(&[3, 0, 4, 0, 99], &[42], &[42], "Copy input");
        test_memory(&[1002, 4, 3, 4, 33], &[1002, 4, 3, 4, 99]);
        //equal to 8
        test_io(
            &[3, 9, 8, 9, 10, 9, 4, 9, 99, -1, 8],
            &[8],
            &[1],
            "Equal to 8 - position",
        );
        test_io(
            &[3, 9, 8, 9, 10, 9, 4, 9, 99, -1, 8],
            &[42],
            &[0],
            "Equal to 8 - position",
        );
        test_io(
            &[3, 3, 1108, -1, 8, 3, 4, 3, 99],
            &[8],
            &[1],
            "Equal to 8 - immediate",
        );
        test_io(
            &[3, 3, 1108, -1, 8, 3, 4, 3, 99],
            &[42],
            &[0],
            "Equal to 8 - immediate",
        );
        //less then 8
        test_io(
            &[3, 9, 7, 9, 10, 9, 4, 9, 99, -1, 8],
            &[5],
            &[1],
            "Less than 8 - position",
        );
        test_io(
            &[3, 9, 7, 9, 10, 9, 4, 9, 99, -1, 8],
            &[42],
            &[0],
            "Less than 8 - position",
        );
        test_io(
            &[3, 3, 1107, -1, 8, 3, 4, 3, 99],
            &[5],
            &[1],
            "Less than 8 - immediate",
        );
        test_io(
            &[3, 3, 1107, -1, 8, 3, 4, 3, 99],
            &[42],
            &[0],
            "Less than 8 - immediate",
        );
        //equal to 0
        test_io(
            &[3, 12, 6, 12, 15, 1, 13, 14, 13, 4, 13, 99, -1, 0, 1, 9],
            &[0],
            &[0],
            "Equal to 0 - position",
        );
        test_io(
            &[3, 12, 6, 12, 15, 1, 13, 14, 13, 4, 13, 99, -1, 0, 1, 9],
            &[42],
            &[1],
            "Equal to 0 - position",
        );
        test_io(
            &[3, 3, 1105, -1, 9, 1101, 0, 0, 12, 4, 12, 99, 1],
            &[0],
            &[0],
            "Equal to 0 - immediate",
        );
        test_io(
            &[3, 3, 1105, -1, 9, 1101, 0, 0, 12, 4, 12, 99, 1],
            &[42],
            &[1],
            "Equal to 0 - immediate",
        );
        let larger_example = &[
            3, 21, 1008, 21, 8, 20, 1005, 20, 22, 107, 8, 21, 20, 1006, 20, 31,
            1106, 0, 36, 98, 0, 0, 1002, 21, 125, 20, 4, 20, 1105, 1, 46, 104,
            999, 1105, 1, 46, 1101, 1000, 1, 20, 4, 20, 1105, 1, 46, 98, 99,
        ];
        test_io(larger_example, &[5], &[999], "Large - less than");
        test_io(larger_example, &[8], &[1000], "Large - equal");
        test_io(larger_example, &[42], &[1001], "Large - greater");
    }
}
//day7.rs
use super::computer::Computer;
pub fn parse_input(input: &str) -> Vec<isize> {
    input
        .trim()
        .split(',')
        .map(|x| x.parse().unwrap())
        .collect()
}
use itertools::Itertools;
pub fn task1(code: &[isize]) -> isize {
    (0..5)
        .permutations(5)
        .map(|perm| {
            let mut data = 0;
            for phase in perm {
                let mut amplifier = Computer::new(code);
                amplifier.write(phase);
                amplifier.write(data);
                amplifier.run();
                data = amplifier.read().unwrap();
            }
            data
        })
        .max()
        .unwrap()
}
pub fn task2(code: &[isize]) -> isize {
    (5..10)
        .permutations(5)
        .map(|perm| {
            let mut amplifiers = Vec::new();
            for phase in perm {
                let mut amplifier = Computer::new(code);
                amplifier.write(phase);
                amplifiers.push(amplifier);
            }
            amplifiers[0].write(0);
            let mut changes = true;
            let mut last_data = -1;
            while changes {
                changes = false;
                for i in 0..5 {
                    amplifiers[i].run();
                    while let Some(data) = amplifiers[i].read() {
                        if i == 4 {
                            last_data = data;
                        }
                        amplifiers[(i + 1) % 5].write(data);
                        changes = true;
                    }
                }
            }
            last_data
        })
        .max()
        .unwrap()
}