package aoc2022

import println
import readInput

/*
--- Day 7: No Space Left On Device ---
https://adventofcode.com/2022/day/7
*/
fun main() {

    val inlineTestInput = """
$ cd /
$ ls
dir a
14848514 b.txt
8504156 c.dat
dir d
$ cd a
$ ls
dir e
29116 f
2557 g
62596 h.lst
$ cd e
$ ls
584 i
$ cd ..
$ cd ..
$ cd d
$ ls
4060174 j
8033020 d.log
5626152 d.ext
7214296 k
"""

    data class Node(
        val name: String,
        val isDir: Boolean,
        val size: Int = 0,
        var sum: Int = 0,
        val parent: Node? = null,
        val children: MutableList<Node> = ArrayList()
    )


    fun parseInput(input: List<String>): Node {
        val root = Node("/", true)
        var currNode = root
        for (cmd in input) {
            val params = cmd.split(" ")
            when (params[0]) {
                "$" -> when (params[1]) {
                    "cd" -> currNode = when (params[2]) {
                        ".." -> currNode.parent ?: root
                        "/" -> root
                        else -> currNode.children.first { it.name == params[2] }
                    }
                }

                "dir" -> currNode.children.add(Node(params[1], isDir = true, parent = currNode))
                else -> currNode.children.add(Node(params[1], isDir = false, size = params[0].toInt(), parent = currNode))
            }
        }
        return root;
    }

    fun sumChildren(currNode: Node): Int {
        var sum = 0
        for (child in currNode.children) {
            sum += if (child.isDir) sumChildren(child) else child.size
        }
        currNode.sum = sum
        return sum
    }

    fun sumLimitedChildren(currNode: Node, limit: Int): Int {
        var sum = if (currNode.sum <= limit) currNode.sum else 0
        for (child in currNode.children) {
            if (child.isDir) sum += sumLimitedChildren(child, limit)
        }
        return sum
    }

    fun findBestMatch(currNode: Node, toFree: Int, currentBest: Int): Int {
        var newCurrentBest = currentBest
        if ((currNode.sum >= toFree) and (currNode.sum < currentBest)) {
            newCurrentBest = currNode.sum
        }
        for (child in currNode.children) {
            if (child.isDir) newCurrentBest = findBestMatch(child, toFree, newCurrentBest)
        }
        return newCurrentBest
    }

    fun part1(input: List<String>): Int {
        val root = parseInput(input)
        sumChildren(root)
        return sumLimitedChildren(root, 100000)
    }

    fun part2(input: List<String>): Int {
        val root = parseInput(input)
        sumChildren(root)
        val maxSpace = 70000000
        val minFree = 30000000
        val toFree = minFree - (maxSpace - root.sum)
        return findBestMatch(root, toFree, root.sum)
    }

    // test if implementation meets criteria from the description, like:
    val testInput = inlineTestInput.trim().reader().readLines()
    //val testInput = readInput("aoc2022/Day07_test")
    println("Part 1 Test: " + part1(testInput))
    println("Part 2 Test: " + part2(testInput))
    check(part1(testInput) == 95437)
    check(part2(testInput) == 24933642)

    val input = readInput("aoc2022/Day07")
    part1(input).println()
    part2(input).println()
}
