package aoc2024

import CharGrid
import RelPos
import println
import readInput

/*
--- Day 6: Guard Gallivant ---
https://adventofcode.com/2024/day/6
*/
fun main() {

    val inlineTestInput = """
....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...
"""

    fun part1(input: List<String>): Int {
        val grid = CharGrid(input, '.')
        val (oc, or) = grid.findMatches { it == '^' }[0]

        var pos = RelPos(oc, or)
        var dir = RelPos(0, -1)
        while (grid.isInside(pos)) {
            grid[pos] = 'X'
            while (grid[pos.translate(dir)] == '#') {
                dir = RelPos(-dir.dr, dir.dc)
            }
            pos = pos.translate(dir)
        }
        return grid.findMatches { it == 'X' }.count()
    }

    fun checkLooping(grid: CharGrid, origPos: RelPos): Boolean {
        val dirgrid = Array(grid.height) { IntArray(grid.width) }
        var dir = RelPos(0, -1)
        var dirBit = 0
        var looping = false
        var pos = origPos
        while (grid.isInside(pos)) {
            if ((dirgrid[pos.dc][pos.dr] and (1 shl dirBit)) != 0) {
                looping = true
                break
            }
            dirgrid[pos.dc][pos.dr] = dirgrid[pos.dc][pos.dr] or (1 shl dirBit)
            while (grid[pos.translate(dir)] == '#') {
                dir = RelPos(-dir.dr, dir.dc)
                dirBit = (dirBit + 1) and 3
            }
            pos = pos.translate(dir)
        }
        return looping
    }

    fun part2(input: List<String>): Int {
        val grid = CharGrid(input, '.')
        val obstaclePos = HashSet<RelPos>()
        val solutions = HashSet<RelPos>()
        val (oc, or) = grid.findMatches { it == '^' }[0]

        var pos = RelPos(oc, or)
        var dir = RelPos(0, -1)
        while (true) {
            grid[pos] = 'X'
            val newPos = pos.translate(dir)
            if (!grid.isInside(newPos)) break
            if (grid[newPos] == '#') {
                dir = RelPos(-dir.dr, dir.dc)
            } else {
                if (grid[newPos] == '.') {
                    obstaclePos.add(newPos)
                }
                pos = newPos
            }
        }
        for (op in obstaclePos) {
            grid[op] = '#'
            if (checkLooping(grid, RelPos(oc, or))) {
                solutions.add(op)
            }
            grid[op] = '.'
        }
        return solutions.size
    }

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

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