Day 08

# puzzle prompt: https://adventofcode.com/2025/day/8

import itertools
import math
import operator
import time
from functools import reduce

import matplotlib.pyplot as plt
import networkx as nx

from ..base.advent import *

class Solution(InputAsCSVSolution):
    _year = 2025
    _day = 8

    _is_debugging = False

    # using networkx from 2024 (days 20, 23)

    def Parse(self, input):
        points = [tuple(map(int, point)) for point in input]

        distances = self.GetDistances(points)

        return points, distances

    def GetDistances(self, points):
        def Euclides(points):
            p1, p2 = points
            return math.dist(p1, p2), p1, p2

        distances = list(map(Euclides, itertools.combinations(points, 2)))
        distances.sort()
        return distances

    def GetLargestCircuits(self, input, howmany):
        _, distances = self.Parse(input)

        graph = nx.Graph()

        for _, p1, p2 in distances[:howmany]:
            graph.add_edge(p1, p2)

        self.debug(graph.edges)

        if self._is_debugging:
            nx.draw(graph, with_labels=True)
            plt.show()

        components = [len(c) for c in nx.connected_components(graph)]
        components.sort(reverse=True)

        res = reduce(operator.mul, components[:3], 1)

        return res

    def BuildOneCircuit(self, input):
        points, distances = self.Parse(input)

        graph = nx.Graph()

        for point in points:
            graph.add_node(point)

        self.debug(graph.nodes)

        for _, p1, p2 in distances:
            graph.add_edge(p1, p2)
            if nx.is_connected(graph):
                break

        self.debug(graph.edges)

        if self._is_debugging:
            nx.draw(graph, with_labels=True)
            plt.show()

        return p1[0] * p2[0]

    def pt1(self, input):
        self.debug(input)

        res = self.GetLargestCircuits(input, 1000)

        return res

    def pt2(self, input):
        self.debug(input)

        res = self.BuildOneCircuit(input)

        return res

    def part_1(self):
        start_time = time.time()

        res = self.pt1(self.input)

        end_time = time.time()

        self.solve("1", res, (end_time - start_time))

    def part_2(self):
        start_time = time.time()

        res = self.pt2(self.input)

        end_time = time.time()

        self.solve("2", res, (end_time - start_time))


if __name__ == "__main__":
    solution = Solution()

    solution.part_1()

    solution.part_2()