package aoc2023

import CharGrid
import RelPos
import println
import readInput

/*
--- Day 16: The Floor Will Be Lava ---
https://adventofcode.com/2023/day/16
*/
fun main() {

    val inlineTestInput = """
.|...\....
|.-.\.....
.....|-...
........|.
..........
.........\
..../.\\..
.-.-/..|..
.|....-|.\
..//.|....
"""

    fun runBeam(origGrid: CharGrid, startPos: Pair<RelPos, RelPos>): Int {
        val grid = origGrid.copyOf()
        val rays = ArrayDeque<Pair<RelPos, RelPos>>()
        rays.add(startPos)
        while (rays.isNotEmpty()) {
            var (rayPos, rayDir) = rays.removeFirst()
            while (origGrid[rayPos.dc, rayPos.dr] == '/' || origGrid[rayPos.dc, rayPos.dr] == '\\' || !grid[rayPos.dc, rayPos.dr].isDigit() || (grid[rayPos.dc, rayPos.dr].code and (if (rayDir.dc != 0) 1 else 2) == 0)) {
                grid[rayPos.dc, rayPos.dr] =
                    ((if (grid[rayPos.dc, rayPos.dr].isDigit()) grid[rayPos.dc, rayPos.dr] else '0').code or (if (rayDir.dc != 0) 1 else 2)).toChar()
                when (origGrid[rayPos.dc, rayPos.dr]) {
                    '\\' -> rayDir = RelPos(rayDir.dr, rayDir.dc)
                    '/' -> rayDir = RelPos(-rayDir.dr, -rayDir.dc)
                    '|' -> if (rayDir.dc != 0) {
                        rayDir = RelPos(0, -1)
                        rays.add(rayPos.translate(0, 1) to RelPos(0, 1))
                    }

                    '-' -> if (rayDir.dr != 0) {
                        rayDir = RelPos(-1, 0)
                        rays.add(rayPos.translate(1, 0) to RelPos(1, 0))
                    }
                }
                rayPos = rayPos.translate(rayDir)
            }
        }
        return grid.findMatches { it.isDigit() }.count()
    }

    fun part1(input: List<String>): Int {
        val origGrid = CharGrid(input, '3')
        return runBeam(origGrid, RelPos(0, 0) to RelPos(1, 0))
    }

    fun part2(input: List<String>): Int {
        val origGrid = CharGrid(input, '3')

        var result = 0
        for (tc in 0 until origGrid.width) {
            result = result.coerceAtLeast(runBeam(origGrid, RelPos(tc, 0) to RelPos(0, 1)))
            result = result.coerceAtLeast(runBeam(origGrid, RelPos(tc, origGrid.height - 1) to RelPos(0, -1)))
        }
        for (tr in 0 until origGrid.height) {
            result = result.coerceAtLeast(runBeam(origGrid, RelPos(0, tr) to RelPos(1, 0)))
            result = result.coerceAtLeast(runBeam(origGrid, RelPos(origGrid.width - 1, tr) to RelPos(-1, 0)))
        }
        return result
    }

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

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