package aoc2023

import println
import readInput

/*
--- Day 12: Hot Springs ---
https://adventofcode.com/2023/day/12
*/
fun main() {

    val inlineTestInput = """
???.### 1,1,3
.??..??...?##. 1,1,3
?#?#?#?#?#?#?#? 1,3,1,6
????.#...#... 4,1,1
????.######..#####. 1,6,5
?###???????? 3,2,1
"""

    val memo = HashMap<Int, Long>()

    fun fits(input: String, regs: IntArray, inputPos: Int, regPos: Int): Boolean {
        if (regPos > regs.lastIndex || inputPos + regs[regPos] > input.length) return false
        var ip = inputPos
        var e = regs[regPos]
        if (input[ip] == '.') ip++
        while ((e > 0) && ip <= input.lastIndex && input[ip++] != '.') {
            e--
        }
        if (e > 0) return false
        return ip > input.lastIndex || input[ip] != '#'
    }

    fun count(input: String, regs: IntArray, inputPos: Int, regPos: Int): Long {
        val memVal = memo[inputPos * 1000 + regPos]
        if (memVal != null) return memVal

        //println("$inputPos, $regPos, ${input.substring(inputPos)}")
        if (inputPos > input.lastIndex && regPos > regs.lastIndex) return 1
        if (inputPos > input.lastIndex) return 0
        var result = 0L
        if (input[inputPos] != '#') {
            result += count(input, regs, inputPos + 1, regPos).also { memo[(inputPos + 1) * 1000 + regPos] = it }
        }
        if (input[inputPos] != '.') {
            if (fits(input, regs, inputPos, regPos)) {
                val p = inputPos + regs[regPos] + 1
                result += count(input, regs, p, regPos + 1).also { memo[p * 1000 + regPos + 1] = it }
            }
        }
        return result
    }

    fun part1(input: List<String>): Long {
        val results = input.map {
            val (springline, parms) = it.split(" ")
            val reduced = springline.replace("\\.+".toRegex(), ".").trim { it == '.' }
            val regs = parms.split(",").map { it.toInt() }.toIntArray()
            memo.clear()
            count(reduced, regs, 0, 0)
        }
        return results.sum()
    }

    fun part2(input: List<String>): Long {
        val results = input.map {
            val (springline, parms) = it.split(" ")
            val reduced = springline.replace("\\.+".toRegex(), ".")
            val regs = parms.split(",").map { it.toInt() }.toList()
            val regs5 = List(5) { regs }.flatten().toIntArray()
            val red5 = List(5) { reduced }.joinToString("?").trim { it == '.' }
            memo.clear()
            count(red5, regs5, 0, 0)
        }
        return results.sum()
    }

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

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