package aoc2024

import CharGrid
import RelPos
import println
import readInput

/*
--- Day 8: Resonant Collinearity ---
https://adventofcode.com/2024/day/8
*/
fun main() {

    val inlineTestInput = """
............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............
"""

    fun part1(input: List<String>): Int {
        val grid = CharGrid(input)

        val antennas = grid.collectMatches { it != '.' }.groupBy({ it.first }) { it.second }
        val antinodes = HashSet<RelPos>()
        for (at in antennas.values) {
            for ((i1, p1) in at.withIndex()) {
                for (i2 in i1 + 1 until at.size) {
                    val p2 = at[i2]
                    val ap1 = RelPos(p2.dc + (p2.dc - p1.dc), p2.dr + (p2.dr - p1.dr))
                    val ap2 = RelPos(p1.dc - (p2.dc - p1.dc), p1.dr - (p2.dr - p1.dr))
                    if (grid.isInside(ap1)) antinodes.add(ap1)
                    if (grid.isInside(ap2)) antinodes.add(ap2)
                }
            }
        }
        return antinodes.size
    }

    fun part2(input: List<String>): Int {
        val grid = CharGrid(input)

        val antennas = grid.collectMatches { it != '.' }.groupBy({ it.first }) { it.second }
        val antinodes = HashSet<Pair<RelPos, RelPos>>()
        for (at in antennas.values) {
            for ((i1, p1) in at.withIndex()) {
                for (i2 in i1 + 1 until at.size) {
                    val p2 = at[i2]
                    val dc = p2.dc - p1.dc
                    val dr = p2.dr - p1.dr
                    val sc = p1.dc
                    val sr = p1.dr
                    antinodes.add(RelPos(sc, sr) to RelPos(dc, dr))
                }
            }
        }

        return grid.generateGridPos().count { pos ->
            antinodes.any {
                val rr = pos.dr - it.first.dr
                val rc = pos.dc - it.first.dc
                val f1 = rr / it.second.dr
                val f2 = rc / it.second.dc
                ((rr % it.second.dr) == 0) &&
                    ((rc % it.second.dc) == 0) &&
                    (f1 == f2)
            }
        }
    }

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

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