package aoc2024

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.runBlocking
import println
import readInput
import java.util.*

/*
--- Day 22: Monkey Market ---
https://adventofcode.com/2024/day/22
*/
fun main() {

    val inlineTestInput = """
1
10
100
2024
"""

    val inlineTestInput2 = """
1
2
3
2024
"""

    fun priceSequence(seed: Int) = generateSequence(seed) {
        val r1 = (it xor (it shl 6)) and 0xff_ff_ff
        val r2 = r1 xor (r1 shr 5)
        (r2 xor (r2 shl 11)) and 0xff_ff_ff
    }

    fun part1(input: List<String>) =
        input.map(String::toInt).sumOf { priceSequence(it).elementAt(2000).toLong() }

    fun part2bruteforce(input: List<String>): Int {
        val strings = input.map(String::toInt).map {
            val normalString = StringBuffer(2024)
            val deltaString = StringBuffer(2024)
            var lastd = it % 10
            priceSequence(it).take(2001).forEach {
                val d = it % 10
                val delta = d - lastd
                normalString.append(d)
                deltaString.append('j' + delta)
                lastd = d
            }
            deltaString.toString() to normalString.toString()
        }

        // it's not my fault that brute force is still working
        // note that there are less than 19^4 valid combinations, but we don't care
        return runBlocking {
            (0..(19 * 19 * 19 * 19)).map {
                async(Dispatchers.Default) {
                    val token = StringBuffer(4)
                        .append('a' + (it / (19 * 19 * 19)))
                        .append('a' + ((it / (19 * 19)) % 19))
                        .append('a' + ((it / 19) % 19))
                        .append('a' + ((it % 19))).toString()
                    strings.sumOf {
                        val idx = it.first.indexOf(token)
                        if (idx >= 0) it.second[idx + 3].digitToInt() else 0
                    }
                }
            }.awaitAll().max()
        }
    }

    fun part2(input: List<String>): Int {
        val map = HashMap<Int, MutableList<Int>>()
        input.map(String::toInt).forEach {
            var lastd = 0
            var code = 0
            var countDown = 4
            val seenArray = BitSet(19 * 19 * 19 * 19)
            for (v in priceSequence(it).take(2001)) {
                val d = v % 10
                val delta = d - lastd
                code = ((code * 19) % (19 * 19 * 19 * 19)) + (delta + 9)
                if (--countDown < 0 && !seenArray[code]) {
                    map.getOrPut(code) { ArrayList() }.add(d)
                    seenArray[code] = true
                }
                lastd = d
            }
        }
        return map.maxOf { it.value.sum() }
    }

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

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