package aoc2024

import println
import readInput
import splitInts

/*
--- Day 17: Chronospatial Computer ---
https://adventofcode.com/2024/day/17
*/
fun main() {

    val inlineTestInput = """
Register A: 117440
Register B: 0
Register C: 0

Program: 0,3,5,4,3,0
"""
    val regs = LongArray(3)

    fun getComboOperand(op: Int): Long =
        when (op) {
            0, 1, 2, 3 -> op.toLong()
            4, 5, 6 -> regs[op - 4]
            else -> throw IllegalStateException()
        }

    fun part1(input: List<String>): String {
        regs[0] = input[0].split(" ")[2].toLong()
        regs[1] = input[1].split(" ")[2].toLong()
        regs[2] = input[2].split(" ")[2].toLong()
        val ops = input[4].split(" ")[1].splitInts(",")
        val outs = ArrayList<Int>()
        var pc = 0
        while (pc < ops.size) {
            when (ops[pc++]) {
                // adv
                0 -> regs[0] = regs[0] shr getComboOperand(ops[pc++]).toInt()
                // bdv
                6 -> regs[1] = regs[0] shr getComboOperand(ops[pc++]).toInt()
                // cdv
                7 -> regs[2] = regs[0] shr getComboOperand(ops[pc++]).toInt()
                // bxl
                1 -> regs[1] = regs[1] xor ops[pc++].toLong()
                // bst
                2 -> regs[1] = getComboOperand(ops[pc++]) and 7
                // jnz
                3 -> if (regs[0] == 0L) pc++ else pc = ops[pc]
                // bxc
                4 -> {
                    regs[1] = regs[1] xor regs[2]
                    pc++
                }
                // out
                5 -> outs.add((getComboOperand(ops[pc++]) and 7).toInt())
            }
        }
        return outs.joinToString(",")
    }

    fun verify(rega: Long, ops: List<Int>, outs: List<Int>): Boolean {
        regs[0] = rega
        var pc = 0
        var outpos = 0
        while (pc < ops.size) {
            when (ops[pc++]) {
                // adv
                0 -> regs[0] = regs[0] shr getComboOperand(ops[pc++]).toInt()
                // bdv
                6 -> regs[1] = regs[0] shr getComboOperand(ops[pc++]).toInt()
                // cdv
                7 -> regs[2] = regs[0] shr getComboOperand(ops[pc++]).toInt()
                // bxl
                1 -> regs[1] = regs[1] xor ops[pc++].toLong()
                // bst
                2 -> regs[1] = getComboOperand(ops[pc++]) and 7
                // jnz
                3 -> if (regs[0] == 0L) pc++ else pc = ops[pc]
                // bxc
                4 -> {
                    regs[1] = regs[1] xor regs[2]
                    pc++
                }
                // out
                5 -> {
                    val o = (getComboOperand(ops[pc++]) and 7).toInt()
                    if (outs[outpos++] != o) return false
                }
            }
        }
        return true
    }

    fun rec(a: Long, p: Int, ops: List<Int>, outs: List<Int>): Long {
        val partOutput = outs.take(p).reversed()
        // assuming that all programs use 3 bit shifts in the last operation
        for (i in 0L..7L) {
            if (verify((a shl 3) or i, ops, partOutput)) {
                if (p == outs.size) return (a shl 3) or i
                val res = rec((a shl 3) or i, p + 1, ops, outs)
                if (res != 0L) return res
            }
        }
        return 0
    }

    fun part2(input: List<String>): Long {
        val ops = input[4].split(" ")[1].splitInts(",")
        val outs = ops.reversed()
        return rec(0L, 1, ops, outs)
    }

    // test if implementation meets criteria from the description, like:
    val testInput = inlineTestInput.trim().reader().readLines()
    //val testInput = readInput("aoc2024/Day17_test")
    val testInputPart1Result = part1(testInput)
    println("Part 1 Test: $testInputPart1Result")

    val input = readInput("aoc2024/Day17")
    part1(input).println()
    part2(input).println()
}
