package aoc2024

import CharGrid
import println
import readInput

/*
--- Day 15: Warehouse Woes ---
https://adventofcode.com/2024/day/15
*/
fun main() {

    val inlineTestInput2 = """
##########
#..O..O.O#
#......O.#
#.OO..O.O#
#..O@..O.#
#O#..O...#
#O..O..O.#
#.OO.O.OO#
#....O...#
##########

<vv>^<v^>v>^vv^v>v<>v^v<v<^vv<<<^><<><>>v<vvv<>^v^>^<<<><<v<<<v^vv^v>^
vvv<<^>^v^^><<>>><>^<<><^vv^^<>vvv<>><^^v>^>vv<>v<<<<v<^v>^<^^>>>^<v<v
><>vv>v^v^<>><>>>><^^>vv>v<^^^>>v^v^<^^>v^^>v^<^v>v<>>v^v^<v>v^^<^^vv<
<<v<^>>^^^^>>>v^<>vvv^><v<<<>^^^vv^<vvv>^>v<^^^^v<>^>vvvv><>>v^<<^^^^^
^><^><>>><>^^<<^^v>>><^<v>^<vv>>v>>>^v><>^v><<<<v>>v<v<v>vvv>^<><<>^><
^>><>^v<><^vvv<^^<><v<<<<<><^v<<<><<<^^<v<^^^><^>>^<v^><<<^>>^v<v^v<v^
>^>>^v>vv>^<<^v<>><<><<v<<v><>v<^vv<<<>^^v^>^^>>><<^v>>v^v><^^>>^<>vv^
<><^^>^^^<><vvvvv^v<v<<>^v<v>v<<^><<><<><<<^^<<<^<<>><<><^^^>^^<>^>v<>
^^>vv<^v^v<vv>^<><v<^v>^^^>>>^^vvv^>vvv<>>>^<^>>>>>^<<^v>^vvv<>^<><<v>
v^^>>><<^^<>>^v^<v^vv<>v^<<>^<^v^v><^<<<><<^<v><v<>vv>>v><v^<vv<>v^<<^
"""

    val inlineTestInput = """
########
#..O.O.#
##@.O..#
#...O..#
#.#.O..#
#...O..#
#......#
########

<^^>>>vv<v>>v<<
"""

    val inlineTestInput3 = """
#######
#...#.#
#.....#
#..OO@#
#..O..#
#.....#
#######

<vv<<^^<<^^
"""

    fun part1(input: List<String>): Int {
        val splitp = input.withIndex().find { it.value.isEmpty() }!!.index
        val grid = CharGrid(input.take(splitp))
        val inst = input.drop(splitp).joinToString("")
        var (rc, rd) = grid.findMatches { it == '@' }[0]
        for (m in inst) {
            grid[rc, rd] = '.'
            var dc = 0
            var dr = 0
            when (m) {
                '<' -> dc = -1
                '>' -> dc = 1
                '^' -> dr = -1
                'v' -> dr = 1
            }
            var s = 1
            while (grid[rc + dc * s, rd + dr * s] != '.') {
                if (grid[rc + dc * s, rd + dr * s] == '#') {
                    s = 0
                    break
                }
                s++
            }
            if (s > 0) {
                if (s > 1) {
                    grid[rc + dc * s, rd + dr * s] = 'O'
                }
                rc += dc
                rd += dr
            }
            grid[rc, rd] = '@'
        }
        return grid.generateGridPos().filter { grid[it] == 'O' }.sumOf { it.dc + it.dr * 100 }
    }

    fun canMove(grid: CharGrid, bc: Int, rd: Int, dr: Int): Boolean {
        if ((grid[bc, rd] == '[')) {
            val c1 = grid[bc, rd + dr]
            val c2 = grid[bc + 1, rd + dr]
            if ((c1 == '.') && (c2 == '.')) return true
            if ((c1 == '#') || (c2 == '#')) return false
            if ((c1 == '[')) return canMove(grid, bc, rd + dr, dr)
            return (if (c1 == ']') canMove(grid, bc - 1, rd + dr, dr) else true)
                && (if (c2 == '[') canMove(grid, bc + 1, rd + dr, dr) else true)
        } else if ((grid[bc, rd] == ']')) {
            return canMove(grid, bc - 1, rd, dr)
        } else {
            throw IllegalStateException()
        }
    }

    fun doMove(grid: CharGrid, bc: Int, rd: Int, dr: Int) {
        if ((grid[bc, rd] == '[')) {
            val c1 = grid[bc, rd + dr]
            val c2 = grid[bc + 1, rd + dr]
            if ((c1 == '[')) {
                doMove(grid, bc, rd + dr, dr)
            } else {
                if (c1 == ']') doMove(grid, bc - 1, rd + dr, dr)
                if (c2 == '[') doMove(grid, bc + 1, rd + dr, dr)
            }
            if ((grid[bc, rd + dr] == '.') && (grid[bc + 1, rd + dr] == '.')) {
                grid[bc, rd + dr] = '['
                grid[bc + 1, rd + dr] = ']'
                grid[bc, rd] = '.'
                grid[bc + 1, rd] = '.'
            } else {
                throw IllegalStateException()
            }
        } else if ((grid[bc, rd] == ']')) {
            doMove(grid, bc - 1, rd, dr)
        }
    }

    fun part2(input: List<String>): Int {
        val splitp = input.withIndex().find { it.value.isEmpty() }!!.index
        val sgrid = CharGrid(input.take(splitp))
        val grid = CharGrid(sgrid.width * 2, sgrid.height)
        sgrid.generateGridPos().forEach {
            when (val cc = sgrid[it]) {
                '@' -> {
                    grid[it.dc * 2, it.dr] = cc
                    grid[it.dc * 2 + 1, it.dr] = '.'
                }

                'O' -> {
                    grid[it.dc * 2, it.dr] = '['
                    grid[it.dc * 2 + 1, it.dr] = ']'
                }

                else -> {
                    grid[it.dc * 2, it.dr] = cc
                    grid[it.dc * 2 + 1, it.dr] = cc
                }
            }
        }
        val inst = input.drop(splitp).joinToString("")
        var (rc, rd) = grid.findMatches { it == '@' }[0]
        for (m in inst) {
            grid[rc, rd] = '.'
            var dc = 0
            var dr = 0
            when (m) {
                '<' -> dc = -1
                '>' -> dc = 1
                '^' -> dr = -1
                'v' -> dr = 1
            }
            if (dc != 0) {
                var s = 1
                var bc: Char? = null
                while (grid[rc + dc * s, rd] != '.') {
                    val cc = grid[rc + dc * s, rd]
                    if (cc == '#') {
                        s = 0
                        break
                    } else {
                        if (bc != null && cc == bc) {
                            s = 0
                            break
                        }
                        bc = cc
                    }
                    s++
                }
                if (s > 0) {
                    if (s > 1) {
                        for (p in s downTo 1) {
                            grid[rc + dc * p, rd] = grid[rc + dc * (p - 1), rd]
                        }
                    }
                    rc += dc
                    rd += dr
                }
            } else {
                if (grid[rc, rd + dr] != '.') {
                    if (grid[rc, rd + dr] != '#') {
                        if (canMove(grid, rc, rd + dr, dr)) {
                            doMove(grid, rc, rd + dr, dr)
                            rc += dc
                            rd += dr
                        }
                    }
                } else {
                    rc += dc
                    rd += dr
                }
            }
            grid[rc, rd] = '@'
        }
        return grid.generateGridPos().filter { grid[it] == '[' }.sumOf { it.dc + it.dr * 100 }
    }

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

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