package aoc2024

import CharGrid
import RelPos
import println
import readInput
import kotlin.math.abs

/*
--- Day 20: Race Condition ---
https://adventofcode.com/2024/day/20
*/
fun main() {

    val inlineTestInput = """
###############
#...#...#.....#
#.#.#.#.#.###.#
#S#...#.#.#...#
#######.#.#.###
#######.#.#...#
#######.#.###.#
###..E#...#...#
###.#######.###
#...###...#...#
#.#####.#.###.#
#.#...#.#.#...#
#.#.#.#.#.#.###
#...#...#...###
###############
"""

    data class Node(val pos: RelPos, val relLen: Int)

    fun getPathCosts(grid: CharGrid, startPos: RelPos, endPos: RelPos): HashMap<RelPos, Int> {
        val queue = ArrayDeque<Node>()
        queue.add(Node(startPos, 0))
        val bestCosts = HashMap<RelPos, Int>()
        while (queue.isNotEmpty()) {
            val node = queue.removeFirst()
            val pos = node.pos
            val cost = node.relLen
            bestCosts[pos] = cost
            if (pos == endPos) {
                break
            }
            queue.addAll(CharGrid.PLUS_POS
                .map { pos.translate(it) }
                .filter { grid[it] == '.' && !bestCosts.contains(it) }
                .map { Node(it, cost + 1) })
        }
        return bestCosts
    }

    fun part1(input: List<String>): Int {
        val grid = CharGrid(input)
        val start = grid.findMatchesRelPos { it == 'S' }[0]
        val end = grid.findMatchesRelPos { it == 'E' }[0]
        grid[end] = '.'

        val cheats = listOf(
            RelPos(0, -1), RelPos(-1, 0), RelPos(1, 0), RelPos(0, 1),
            RelPos(0, -2), RelPos(-2, 0), RelPos(2, 0), RelPos(0, 2),
        )
        val pathCosts = getPathCosts(grid, start, end)

        return pathCosts.asSequence().sumOf { (pos, cost) ->
            cheats.map { pos.translate(it) to (abs(it.dr) + abs(it.dc)) }
                .filter { (pathCosts[it.first] ?: 0) - (cost + it.second) >= 100 }
                .map { it.first to (pathCosts[it.first]!! - (cost + it.second)) }.count()
        }
    }

    fun part2(input: List<String>): Int {
        val grid = CharGrid(input)
        val start = grid.findMatchesRelPos { it == 'S' }[0]
        val end = grid.findMatchesRelPos { it == 'E' }[0]
        grid[end] = '.'

        val pathCosts = getPathCosts(grid, start, end)

        // manhattan distance!
        val pathCostsList = pathCosts.toList()
        return pathCostsList.sumOf { (pos, cost) ->
            pathCostsList.asSequence().filter { abs(pos.dc - it.first.dc) + abs(pos.dr - it.first.dr) <= 20 }
                .filter { (pathCosts[it.first] ?: 0) - (cost + abs(pos.dc - it.first.dc) + abs(pos.dr - it.first.dr)) >= 100 }
                .map { it.first to (pathCosts[it.first]!! - (cost + it.second)) }.count()
        }
    }

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

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