import math


class Graph:

    def __init__(self):
        """ Create an empty graph. """

        self.num_nodes = 0
        self.num_edges = 0
        self.adjacency_lists = dict()
        self.nodes = dict()

    def read_weighted_grid(self, file_name):
        """
        Reads a maze file of the form:

            50x30
            x, y, grayscale, weight
            x, y, grayscale, weight
            ...

        where weight may be an integer or INF.

        Returns:
            width, height, grid

        Here grid[(x, y)] = {
            "gray": grayscale,
            "weight": weight
        }
        """

        grid = {}

        with open(file_name, "r", encoding="utf-8") as file:
            first_line = file.readline().strip()
            width, height = map(int, first_line.lower().split("x"))

            for line in file:
                line = line.strip()

                if not line:
                    continue

                x_str, y_str, gray_str, weight_str = [
                    part.strip() for part in line.split(",")
                ]

                x = int(x_str)
                y = int(y_str)
                gray = int(gray_str)

                if weight_str.upper() == "INF":
                    weight = math.inf
                else:
                    weight = int(weight_str)

                grid[(x, y)] = {
                    "gray": gray,
                    "weight": weight
                }

        return width, height, grid

    def construct_graph(self, file_name: str):
        """
        Constructs the directed adjacency list of the maze graph.

        A cell is a node iff its weight is not INF.

        For every pair of horizontally/vertically adjacent passable
        cells u and v, the directed edge u -> v has
        weight equal to the weight of v.
        """

        width, height, grid = self.read_weighted_grid(file_name)

        self.nodes = {}
        self.adjacency_lists = {}
        self.num_edges = 0

        directions = [
            (1, 0),   # right
            (-1, 0),  # left
            (0, 1),   # down
            (0, -1),  # up
        ]

        # First identify all valid nodes
        for y in range(height):
            for x in range(width):
                current = (x, y)
                if grid[current]["weight"] != math.inf:
                    self.nodes[current] = grid[current]
                    self.adjacency_lists[current] = []

        self.num_nodes = len(self.nodes)

        # Now construct the edges
        for current in self.nodes:
            x, y = current
            for dx, dy in directions:
                nx = x + dx
                ny = y + dy
                neighbor = (nx, ny)

                if neighbor in self.nodes:
                    edge_weight = self.nodes[neighbor]["weight"]
                    self.adjacency_lists[current].append((
                        neighbor,
                        edge_weight
                    ))
                    self.num_edges += 1
