package aoc2024

import println
import readInput

/*
--- Day 13: Claw Contraption ---
https://adventofcode.com/2024/day/13
*/
fun main() {

    val inlineTestInput = """
Button A: X+94, Y+34
Button B: X+22, Y+67
Prize: X=8400, Y=5400

Button A: X+26, Y+66
Button B: X+67, Y+21
Prize: X=12748, Y=12176

Button A: X+17, Y+86
Button B: X+84, Y+37
Prize: X=7870, Y=6450

Button A: X+69, Y+23
Button B: X+27, Y+71
Prize: X=18641, Y=10279
"""

    fun part1(input: List<String>): Int {
        var i = 0
        var sum = 0
        while (i < input.size) {
            val (ax, ay) = "Button A: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toInt() }
            val (bx, by) = "Button B: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toInt() }
            val (px, py) = "Prize: X=(\\d+), Y=(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toInt() }
            i++
            val xpos = IntArray(101 * 101)
            val ypos = IntArray(101 * 101)
            val solutions = ArrayList<Int>()
            for (b in 1..100) {
                for (a in 1..100) {
                    val op = (a - 1) + (b - 1) * 101
                    if ((xpos[op] == px) && (ypos[op] == py)) {
                        solutions.add(op)
                    }
                    if ((xpos[op] > px) || (ypos[op] > py)) break
                    xpos[a + (b - 1) * 101] = xpos[op] + ax
                    xpos[(a - 1) + b * 101] = xpos[op] + bx
                    ypos[a + (b - 1) * 101] = ypos[op] + ay
                    ypos[(a - 1) + b * 101] = ypos[op] + by
                }
            }
            val minTokens = solutions.minOfOrNull { (it % 101) * 3 + (it / 101) }
            sum += minTokens ?: 0
        }
        return sum
    }

    fun part2(input: List<String>): Long {
        var i = 0
        var sum = 0L

        // 1. ax * pa + bx * pb = px
        // 2. ay * pa + by * pb = py
        // 3. pa * 3 + pb <- min
        // 4. pa, pb in N (int > 0)

        // ax * pa + bx * pb - px = ay * pa + by * pb - py
        // (ax - ay) * pa + (bx - by) * pb + py - px = 0
        // (ax - ay) * pa + (bx - by) * pb = px - py

        while (i < input.size) {
            val (ax, ay) = "Button A: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toLong() }
            val (bx, by) = "Button B: X\\+(\\d+), Y\\+(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toLong() }
            val (px, py) = "Prize: X=(\\d+), Y=(\\d+)".toRegex().find(input[i++])!!.groupValues.drop(1).map { it.toLong() + 10000000000000L }
            i++

//            val t1 = ax - ay
//            val t2 = bx - by
//            val t3 = px - py

            // Linear Diophantine equations
            /* The simplest linear Diophantine equation takes the form
               a*x + b*x = c
               where a, b and c are given integers.
               The solutions are described by the following theorem:
               This Diophantine equation has a solution (where x and y are integers),
               IFF c is a multiple of the greatest common divisor of a and b.
               Moreover, if (x, y) is a solution, then the other solutions have the
               form (x + kv, y − ku), where
               - k is an arbitrary integer, and
               - u and v are the quotients of a and b (respectively) by the greatest common divisor of a and b.
             */
            // With our input data, all equations have exactly one solution
            val pad = (px * by - py * bx)
            val pan = (ax * by - ay * bx)
            if (pad % pan == 0L) {
                val pa = pad / pan
                val pbd = (px - pa * ax)
                if (pbd % bx == 0L) {
                    val pb = pbd / bx
                    sum += pa * 3 + pb
                }
            }
        }
        return sum
    }

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

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