package aoc2023

import CharGrid
import println
import readInput

/*
--- Day 13: Point of Incidence ---
https://adventofcode.com/2023/day/13
*/
fun main() {

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

#...##..#
#....#..#
..##..###
#####.##.
#####.##.
..##..###
#....#..#
"""

    fun mirrored(bits: IntArray, size: Int): Int {
        for (p1 in 0 until size - 1) {
            var p2 = 0
            var match = true
            while (p1 >= p2 && p1 + p2 + 1 < size) {
                if (bits[p1 - p2] != bits[p1 + p2 + 1]) {
                    match = false
                    break
                }
                p2++
            }
            if (match) return p1 + 1
        }
        return 0
    }

    fun smudged(bits: IntArray, size: Int): Int {
        for (p1 in 0 until size - 1) {
            var smudges = 0
            var p2 = 0
            while (p1 >= p2 && p1 + p2 + 1 < size) {
                smudges += (bits[p1 - p2] xor bits[p1 + p2 + 1]).countOneBits()
                if (smudges > 1) break
                p2++
            }
            if (smudges == 1) return p1 + 1
        }
        return 0
    }

    fun part1(input: List<String>): Int {
        val tmpList = ArrayList<String>()
        var result = 0
        for ((idx, i) in input.withIndex()) {
            if (i.isNotBlank()) {
                tmpList.add(i)
            }
            if (i.isBlank() || (idx == input.lastIndex && tmpList.isNotEmpty())) {
                val grid = CharGrid(tmpList)
                val vertSA = IntArray(grid.width)
                val horzSA = IntArray(grid.height)
                for (c in 0 until grid.width) {
                    for (r in 0 until grid.height) {
                        vertSA[c] = vertSA[c] * 2 + (if (grid[c, r] == '#') 1 else 0)
                    }
                }
                for (r in 0 until grid.height) {
                    for (c in 0 until grid.width) {
                        horzSA[r] = horzSA[r] * 2 + if (grid[c, r] == '#') 1 else 0
                    }
                }
                val vref = mirrored(vertSA, grid.width)
                if (vref > 0) {
                    result += vref
                } else {
                    result += mirrored(horzSA, grid.height) * 100
                }

                tmpList.clear()
            }
        }
        return result
    }

    fun part2(input: List<String>): Int {
        val tmpList = ArrayList<String>()
        var result = 0
        for ((idx, i) in input.withIndex()) {
            if (i.isNotBlank()) {
                tmpList.add(i)
            }
            if (i.isBlank() || (idx == input.lastIndex && tmpList.isNotEmpty())) {
                val grid = CharGrid(tmpList)
                val vertSA = IntArray(grid.width)
                val horzSA = IntArray(grid.height)
                for (c in 0 until grid.width) {
                    for (r in 0 until grid.height) {
                        vertSA[c] = vertSA[c] * 2 + (if (grid[c, r] == '#') 1 else 0)
                    }
                }
                for (r in 0 until grid.height) {
                    for (c in 0 until grid.width) {
                        horzSA[r] = horzSA[r] * 2 + if (grid[c, r] == '#') 1 else 0
                    }
                }
                val vref = smudged(vertSA, grid.width)
                if (vref > 0) {
                    result += vref
                } else {
                    result += smudged(horzSA, grid.height) * 100
                }

                tmpList.clear()
            }
        }
        return result
    }

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

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