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

fn main() {
    let mut stdin = stdin();
    let mut buf = String::new();
    stdin.read_to_string(&mut buf).unwrap();
    let mon = buf.split("\n\n").collect::<Vec<&str>>();
    let mut monkeys = mon.iter().map(|n| Monkey::parse(n)).collect::<Vec<Monkey>>();
    
    let needed_mod = monkeys.iter().map(|m| m.test).reduce(|a, b| a*b).unwrap(); // part 2

    // let rounds = 20; // part 1
    let rounds = 10000;
    for i in 0..rounds {
        // Monkey::round(&mut monkeys, 0); // part 1. the modulus argument isn't needed in part 1 but i don't want to write the parse function twice
        Monkey::round(&mut monkeys, needed_mod); // part 2
    }

    let mut exams = monkeys.iter().map(|m| m.exams).collect::<Vec<u128>>();
    exams.sort_by(|a, b| b.cmp(a));

    println!("{:?}", exams[0] * exams[1]);
}

// data for each monkey
struct Monkey {
    items: LinkedList<u128>,
    oper: Operation,
    test: u128,
    t: usize,
    f: usize,
    exams: u128,
}

impl Monkey {
    // get monkey from string
    fn parse(s: &str) -> Self {
        let lines: Vec<&str> = s.split("\n").collect();
        let st = &lines[1][18..];
        let op = &lines[2][23..];
        let ts = lines[3][21..].parse::<u128>().unwrap();
        let tr = lines[4][29..].parse::<usize>().unwrap();
        let fa = lines[5][30..].parse::<usize>().unwrap();
        Monkey {
            items: st.split(", ").map(|x| x.parse::<u128>().unwrap()).collect(),
            oper: Operation {
                ation: op.chars().next().unwrap() == '*', 
                and: match op[2..].parse::<u128>() {
                    Ok(x) => Operand::N(x),
                    Err(_) => Operand::Old
                }
            },
            test: ts,
            t: tr,
            f: fa,
            exams: 0
        }
    }

    // simulate a round of monkeying
    fn round(monkeys: &mut Vec<Monkey>, modulus: u128) {
        for i in 0..monkeys.len() {
            while !monkeys[i].items.is_empty() {
                let item = monkeys[i].items.pop_front().unwrap();
                monkeys[i].exams += 1;
                // let new = monkeys[i].oper.apply(item) / 3; // part 1
                let new = monkeys[i].oper.apply(item) % modulus; // part 2
                let (t, f) = (monkeys[i].t, monkeys[i].f);
                if new % monkeys[i].test == 0 {
                    monkeys[t].items.push_back(new);
                } else {
                    monkeys[f].items.push_back(new);
                }
            }
        }
    }
}

// overengineered way of storing the operators
struct Operation {
    ation: bool,
    and: Operand,
}

impl Operation {
    fn apply(&self, n: u128) -> u128 {
        let a = match self.and {
            Operand::N(x) => x,
            Operand::Old => n
        };
        if self.ation { n * a } else { n + a }
    }
}

enum Operand {
    N(u128),
    Old
}