package aoc2023

import println
import readInput

/*
--- Day 25: Snowverload ---
https://adventofcode.com/2023/day/25
*/
fun main() {

    val inlineTestInput = """
jqt: rhn xhk nvd
rsh: frs pzl lsr
xhk: hfx
cmg: qnr nvd lhk bvb
rhn: xhk bvb hfx
bvb: xhk hfx
pzl: lsr hfx nvd
qnr: nvd
ntq: jqt hfx bvb xhk
nvd: lhk
lsr: lhk
rzs: qnr cmg lsr rsh
frs: qnr lhk lsr
"""

    data class Node(val id: String, val children: MutableSet<Node> = HashSet(), val orgNodes: MutableSet<Node> = HashSet()) {
        override fun equals(other: Any?): Boolean {
            if (this === other) return true
            if (javaClass != other?.javaClass) return false

            other as Node

            return id == other.id
        }

        override fun hashCode(): Int {
            return id.hashCode()
        }

        override fun toString(): String {
            return "Node(id='$id', children=${children.joinToString { it.id }})"
        }
    }

    fun connectedSize(rootNode: Node): Int {
        val visited = HashSet<Node>()
        val queue = ArrayDeque<Node>()
        queue.add(rootNode)
        while (queue.isNotEmpty()) {
            val n = queue.removeFirst()
            visited.add(n)
            n.children.forEach { if (!visited.contains(it)) queue.add(it) }
        }
        return visited.size
    }

    fun part1(input: List<String>): Int {
        val nodeMap = HashMap<String, Node>()
        for (i in input) {
            val (from, to) = i.split(": ")
            val tos = to.split(" ")
            val fromNode = nodeMap.computeIfAbsent(from) { Node(from) }
            val toNodes = tos.map { nodeMap.computeIfAbsent(it) { Node(it) } }
            fromNode.children.addAll(toNodes)
            toNodes.forEach { it.children.add(fromNode) }
        }

        nodeMap["vps"]!!.children.remove(nodeMap["pzc"])
        nodeMap["pzc"]!!.children.remove(nodeMap["vps"])
        nodeMap["xvk"]!!.children.remove(nodeMap["sgc"])
        nodeMap["sgc"]!!.children.remove(nodeMap["xvk"])
        nodeMap["dph"]!!.children.remove(nodeMap["cvx"])
        nodeMap["cvx"]!!.children.remove(nodeMap["dph"])

        val res = connectedSize(nodeMap["vps"]!!) * connectedSize(nodeMap["pzc"]!!)

//        val edges = nodeMap.values.map { s -> s.children.map { t -> s to t } }.flatten()
//
//        val nodes = nodeMap.values.toMutableList()
//        while (nodes.size > 2) {
//            val n1 = nodes[(Math.random() * nodes.size).toInt().coerceAtMost(nodes.lastIndex)]
//            val n2 = n1.children.keys.toList()[(Math.random() * n1.children.size).toInt().coerceAtMost(n1.children.size - 1)]
//            val superNode = Node(n1.id + "-" + n2.id)
//            if (n1.orgNodes.isEmpty()) {
//                superNode.orgNodes.add(Node(n1.id, children = HashMap(n1.children)))
//            } else {
//                superNode.orgNodes.addAll(n1.orgNodes)
//            }
//            if (n2.orgNodes.isEmpty()) {
//                superNode.orgNodes.add(Node(n2.id, children = HashMap(n2.children)))
//            } else {
//                superNode.orgNodes.addAll(n2.orgNodes)
//            }
//            n1.children.filter { it.key != n2 }
//                .forEach { }
//            superNode.children.addAll(n1.children.filter { it != n2 })
//            superNode.children.addAll(n2.children.filter { it != n1 })
//
//            superNode.children.forEach {
//                it.children.remove(n1)
//                it.children.remove(n2)
//                it.children.add(superNode)
//            }
//            nodes.remove(n1)
//            nodes.remove(n2)
//            nodes.add(superNode)
//        }

        return res
    }

    fun part2(input: List<String>): Int {
        return 0
    }

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

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