import Foundation

typealias Item = Int

class Monkey {
    var items: [Item]
    var operation: Operation
    var operand: Operand
    var test: Int
    var trueTarget: Int
    var falseTarget: Int
    var inspectCount = 0
    
    init(items: [Item], operation: Operation, operand: Operand, test: Int, trueTarget: Int, falseTarget: Int) {
        self.items = items
        self.operation = operation
        self.operand = operand
        self.test = test
        self.trueTarget = trueTarget
        self.falseTarget = falseTarget
    }
    
    func run() {
        for item in items {
            inspectCount += 1
            let newLevel = operation.apply(item, operand.value(item: item)) / 3
            let target = newLevel.isMultiple(of: test) ? trueTarget : falseTarget
            monkeys[target].add(newLevel)
        }
        items.removeAll()
    }
    
    func add(_ item: Item) {
        items.append(item)
    }
}

extension Monkey {
    convenience init?(scanner: Scanner) {
        guard scanner.scanString("Monkey") != nil, scanner.scanInt() != nil, scanner.scanString(":") != nil else {
            return nil
        }
        
        guard scanner.scanString("Starting items:") != nil else { return nil }
        let items = scanner.scanList().map(Item.init(_:))
        
        guard scanner.scanString("Operation: new = old") != nil, let operation = scanner.scanOperation(), let operand = scanner.scanOperand() else { return nil }
        
        guard scanner.scanString("Test: divisible by") != nil, let test = scanner.scanInt() else { return nil }
        guard scanner.scanString("If true: throw to monkey") != nil, let trueTarget = scanner.scanInt() else { return nil }
        guard scanner.scanString("If false: throw to monkey") != nil, let falseTarget = scanner.scanInt() else { return nil }
        
        self.init(items: items, operation: operation, operand: operand, test: test, trueTarget: trueTarget, falseTarget: falseTarget)
    }
}


enum Operation: String, CaseIterable {
    case add = "+"
    case multiply = "*"
    
    func apply(_ lhs: Item, _ rhs: Item) -> Item {
        switch self {
            case .add: return lhs + rhs
            case .multiply: return lhs * rhs
        }
    }
}

enum Operand {
    case number(Int)
    case old
    
    func value(item: Item) -> Item {
        switch self {
            case .number(let number): return Item(number)
            case .old: return item
        }
    }
}

extension Scanner {
    func scanList() -> [Int] {
        var result: [Int] = []
        while true {
            guard let int = scanInt() else {
                break
            }
            result.append(int)
            guard scanString(",") != nil else {
                break
            }
        }
        return result
    }
    
    func scanOperation() -> Operation? {
        for op in Operation.allCases {
            if scanString(op.rawValue) != nil {
                return op
            }
        }
        return nil
    }
    
    func scanOperand() -> Operand? {
        if let int = scanInt() {
            return .number(int)
        }
        
        if scanString("old") != nil {
            return .old
        }
        
        return nil
    }
}

let input = try String(contentsOf: URL(fileURLWithPath: "day11.input"))
let scanner = Scanner(string: input)
var monkeys: [Monkey] = []

while !scanner.isAtEnd, let monkey = Monkey(scanner: scanner) {
    monkeys.append(monkey)
}


for _ in 0..<20 {
    for monkey in monkeys {
        monkey.run()
    }
}


let monkeyBusiness = monkeys.map(\.inspectCount).sorted(by: >).prefix(2).reduce(1, *)
print("Part 1:", monkeyBusiness)