import Foundation

@main
struct Day24: Puzzle {

    func run() {
        let program = readInput()

        var alus: [(Alu, min: Int, max: Int)] = [(Alu(), 0, 0)]

        for instruction in program {
            if case .inp(let register) = instruction {
                buildNextAlus(&alus, register: register)
            } else {
                for index in alus.indices {
                    alus[index].0.run(instruction)
                }
            }
        }

        let serialNumbers = alus
            .lazy
            .filter { $0.0[Alu.resultRegister] == 0 }

        print("Part 1:", serialNumbers.map(\.max).max()!)
        print("Part 2:", serialNumbers.map(\.min).min()!)
    }

    func buildNextAlus(_ alus: inout [(Alu, min: Int, max: Int)], register: RegisterId) {
        var table: [Alu: Int] = [:]

        var newAlus: [(Alu, Int, Int)] = []

        for digit: Alu.Register in 1...9 {
            for (alu, oldMin, oldMax) in alus {
                var alu = alu
                alu[register] = digit
                let newMin = oldMin * 10 + Int(digit)
                let newMax = oldMax * 10 + Int(digit)

                if let index = table[alu] {
                    newAlus[index].1 = min(newAlus[index].1, newMin)
                    newAlus[index].2 = max(newAlus[index].2, newMax)
                } else {
                    table[alu] = newAlus.count
                    newAlus.append((alu, newMin, newMax))
                }
            }
        }

        print("Alu count", newAlus.count)
        alus = newAlus
    }

    func readInput() -> [Instruction] {
        let scanner = Scanner(string: input)
        var program: [Instruction] = []
        while !scanner.isAtEnd {
            program.append(scanner.instruction())
        }
        return program
    }

}


struct Alu: Hashable {
    static let resultRegister: RegisterId = 3
    typealias Register = Int
    var w: Register = 0
    var x: Register = 0
    var y: Register = 0
    var z: Register = 0

    func getValue(_ operand: Operand) -> Register {
        switch operand {
        case .register(let registerId):
            return self[registerId]
        case .number(let int):
            return Register(int)
        }
    }

    subscript(register: RegisterId) -> Register {
        _read {
            switch register {
            case 0: yield w
            case 1: yield x
            case 2: yield y
            case 3: yield z
            default: fatalError()
            }
        }

        _modify {
            switch register {
            case 0: yield &w
            case 1: yield &x
            case 2: yield &y
            case 3: yield &z
            default: fatalError()
        }
        }

        }

    mutating func run(_ instruction: Instruction) {
        switch instruction {
        case .inp:
            break

        case .add(let registerId, let operand):
            self[registerId] += getValue(operand)

        case .mul(let registerId, let operand):
            self[registerId] *= getValue(operand)

        case .div(let registerId, let operand):
            self[registerId] /= getValue(operand)

        case .mod(let registerId, let operand):
            self[registerId] %= getValue(operand)

        case .eql(let registerId, let operand):
            self[registerId] = (self[registerId] == getValue(operand)) ? 1 : 0
        }
    }

}

typealias RegisterId = Int

enum Operand {
    case register(RegisterId)
    case number(Int)
}

enum Instruction {
    case inp(RegisterId)
    case add(RegisterId, Operand)
    case mul(RegisterId, Operand)
    case div(RegisterId, Operand)
    case mod(RegisterId, Operand)
    case eql(RegisterId, Operand)
}

extension Scanner {
    func register() -> RegisterId? {

        if scanString("w") != nil {
            return 0
        }

        if scanString("x") != nil {
            return 1
        }

        if scanString("y") != nil {
            return 2
        }

        if scanString("z") != nil {
            return 3
        }

        return nil
    }

    func operand() -> Operand {
        if let register = register() {
            return .register(register)
        }

        if let int = scanInt() {
            return .number(int)
        }

        fatalError()
    }

    func instruction() -> Instruction {
        guard let command = scanUpToCharacters(from: .whitespaces),
              let target = register()
        else {
                  fatalError()
              }

        switch command {
        case "inp": return .inp(target)
        case "add": return .add(target, operand())
        case "mul": return .mul(target, operand())
        case "div": return .div(target, operand())
        case "mod": return .mod(target, operand())
        case "eql": return .eql(target, operand())
        default: fatalError()
        }
    }
}

let input = """
inp w
mul x 0
add x z
mod x 26
div z 1
add x 14
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 8
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 1
add x 15
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 11
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 1
add x 13
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 2
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 26
add x -10
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 11
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 1
add x 14
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 1
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 26
add x -3
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 5
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 26
add x -14
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 10
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 1
add x 12
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 6
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 1
add x 14
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 1
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 1
add x 12
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 11
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 26
add x -6
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 9
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 26
add x -6
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 14
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 26
add x -2
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 11
mul y x
add z y
inp w
mul x 0
add x z
mod x 26
div z 26
add x -9
eql x w
eql x 0
mul y 0
add y 25
mul y x
add y 1
mul z y
mul y 0
add y w
add y 2
mul y x
add z y
"""