package aoc2023

import lcm
import println
import readInput

/*
--- Day 8: Haunted Wasteland ---
https://adventofcode.com/2023/day/8
*/
fun main() {

    val inlineTestInput = """
RL

AAA = (BBB, CCC)
BBB = (DDD, EEE)
CCC = (ZZZ, GGG)
DDD = (DDD, DDD)
EEE = (EEE, EEE)
GGG = (GGG, GGG)
ZZZ = (ZZZ, ZZZ)
"""
    val inlineTestInput2 = """
LR

ABA = (ABB, XXX)
ABB = (XXX, ABZ)
ABZ = (ABB, XXX)
BAA = (BAB, XXX)
BAB = (BAC, BAC)
BAC = (BAZ, BAZ)
BAZ = (BAB, BAB)
XXX = (XXX, XXX)
"""

    fun part1(input: List<String>): Int {
        val cmd = input[0]
        val map = HashMap<String, Pair<String, String>>()
        for (i in input.drop(2)) {
            "([A-Z][A-Z][A-Z]) = .([A-Z][A-Z][A-Z]), ([A-Z][A-Z][A-Z]).".toRegex().matchEntire(i)!!.destructured
                .let { (id, left, right) -> map[id] = left to right }
        }
        var instPos = 0
        var steps = 0
        var loc = "AAA"
        do {
            loc = if (cmd[instPos] == 'L') map[loc]!!.first else map[loc]!!.second
            instPos = (instPos + 1) % cmd.length
            steps++
        } while (loc != "ZZZ")
        return steps
    }

    fun part2(input: List<String>): Long {
        val cmd = input[0]
        val map = HashMap<String, Pair<String, String>>()
        for (i in input.drop(2)) {
            "([A-Z][A-Z][A-Z]) = .([A-Z][A-Z][A-Z]), ([A-Z][A-Z][A-Z]).".toRegex().matchEntire(i)!!.destructured
                .let { (id, left, right) -> map[id] = left to right }
        }
        val starts = map.keys.filter { it.endsWith("A") }
        var ggts = HashSet<Int>()
        for (start in starts) {
            var instPos = 0
            var steps = 0
            var loc = start
            do {
                loc = if (cmd[instPos] == 'L') map[loc]!!.first else map[loc]!!.second
                instPos = (instPos + 1) % cmd.length
                steps++
            } while (!loc.endsWith("Z"))
            ggts.add(steps)
        }
        return ggts.map(Int::toLong).lcm()
//        val primes = ArrayList<Int>()
//        val factors = HashSet<Int>()
//        var prime = 2
//        primes.add(2)
//        do {
//            val newGgts = HashSet<Int>()
//            for (f in ggts) {
//                if (prime * 2 - 1 > f) {
//                    factors.add(f)
//                } else {
//                    if (f % prime == 0) {
//                        newGgts.add(f / prime)
//                        factors.add(prime)
//                    } else {
//                        newGgts.add(f)
//                    }
//                }
//            }
//            ggts = newGgts
//            if (prime == 2) {
//                prime = 3
//            } else {
//                do {
//                    prime += 2
//                } while (primes.any { (prime % it) == 0 })
//            }
//            primes.add(prime)
//        } while (ggts.isNotEmpty())
//
//        return factors.map { it.toLong() }.reduce(Long::times)
    }

    // test if implementation meets criteria from the description, like:
    val testInput = inlineTestInput.trim().reader().readLines()
    val testInput2 = inlineTestInput2.trim().reader().readLines()
    //val testInput = readInput("aoc2023/Day08_test")
    val testInputPart1Result = part1(testInput)
    println("Part 1 Test: $testInputPart1Result")
    val testInputPart2Result = part2(testInput2)
    println("Part 2 Test: $testInputPart2Result")
    check(testInputPart1Result == 2)
    check(testInputPart2Result == 6L)

    val input = readInput("aoc2023/Day08")
    part1(input).println()
    part2(input).println()
}
