package aoc2023

import CharGrid
import println
import readInput

/*
--- Day 14: Parabolic Reflector Dish ---
https://adventofcode.com/2023/day/14
*/
fun main() {

    val inlineTestInput = """
O....#....
O.OO#....#
.....##...
OO.#O....O
.O.....O#.
O.#..O.#.#
..O..#O..O
.......O..
#....###..
#OO..#....
"""

    fun part1(input: List<String>): Int {
        val grid = CharGrid(input, borderChar = '#')
        for (r in 0 until grid.height) {
            for (c in 0 until grid.width) {
                var rr = r
                while (grid[c, rr] == '.' && grid[c, rr + 1] == 'O') {
                    grid[c, rr] = 'O'
                    grid[c, rr + 1] = '.'
                    rr--
                }
            }
        }
        var sum = 0
        for (r in 0 until grid.height) {
            for (c in 0 until grid.width) {
                if (grid[c, r] == 'O') {
                    sum += grid.height - r
                }
            }
        }
        return sum
    }

    fun part2(input: List<String>): Int {
        val grid = CharGrid(input, borderChar = '#')
        var cycleCount = 0
        val memo = HashMap<CharGrid, MutableSet<Int>>()
        var trackmode = false
        var cycleOffset = 0
        val cycle = ArrayList<CharGrid>()
        do {
            for (r in 0 until grid.height) {
                for (c in 0 until grid.width) {
                    var rr = r
                    while (grid[c, rr] == '.' && grid[c, rr + 1] == 'O') {
                        grid[c, rr] = 'O'
                        grid[c, rr + 1] = '.'
                        rr--
                    }
                }
            }
            for (c in 0 until grid.width) {
                for (r in 0 until grid.height) {
                    var cc = c
                    while (grid[cc, r] == '.' && grid[cc + 1, r] == 'O') {
                        grid[cc, r] = 'O'
                        grid[cc + 1, r] = '.'
                        cc--
                    }
                }
            }
            for (r in grid.height - 1 downTo 0) {
                for (c in 0 until grid.width) {
                    var rr = r
                    while (grid[c, rr] == '.' && grid[c, rr - 1] == 'O') {
                        grid[c, rr] = 'O'
                        grid[c, rr - 1] = '.'
                        rr++
                    }
                }
            }
            for (c in grid.width - 1 downTo 0) {
                for (r in 0 until grid.height) {
                    var cc = c
                    while (grid[cc, r] == '.' && grid[cc - 1, r] == 'O') {
                        grid[cc, r] = 'O'
                        grid[cc - 1, r] = '.'
                        cc++
                    }
                }
            }
            val set = memo.getOrPut(grid.copyOf()) { HashSet() }
            if (set.isNotEmpty()) {
                if (trackmode == false) {
                    cycleOffset = cycleCount
                }
                trackmode = true
            }
            if (set.size == 2) break
            if (trackmode) {
                cycle.add(grid.copyOf())
            }
            cycleCount++
            set.add(cycleCount)

        } while (true)

        println("cycleCount $cycleCount, cycleOffset $cycleOffset, cycleLength ${cycle.size}")
        val offset = (1_000_000_000 - cycleOffset - 1) % cycle.size
        println("offset $offset")
        val pickGrid = cycle[offset]
        println(cycleCount)
        var sum = 0
        for (r in 0 until pickGrid.height) {
            for (c in 0 until pickGrid.width) {
                if (pickGrid[c, r] == 'O') {
                    sum += pickGrid.height - r
                }
            }
        }
        return sum
    }

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

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