from collections import namedtuple
from dataclasses import dataclass
from typing import Any, Union

# a position inside a grid.
Position = namedtuple("Position", ["y", "x", "value"])


@dataclass
class Position:
    y: int
    x: int
    value: Any

    def __str__(self):
        return f"posiiton-{self.y}-{self.x}-{self.value}"


class Grid:
    """
    A class wrapping a 2-d array of Position instances with some convience methods.
    """

    def __init__(self, data):
        self.data = []
        for y in range(len(data)):
            row = []
            for x in range(len(data[y])):
                row.append(Position(y=y, x=x, value=data[y][x]))
            self.data.append(row)

    def at(self, y: int, x: int, default: Union[Position, None] = None) -> Union[Position, None]:
        """
        Return the Position instance at the given grid coordinates. If the specified position does not exist,
        return the specified default value, or None.
        """
        try:
            return self.data[y][x]
        except IndexError:
            return default

    def north(self, position: Position) -> Union[Position, None]:
        return self.at(position.y - 1, position.x)

    def east(self, position: Position) -> Union[Position, None]:
        return self.at(position.y, position.x + 1)

    def south(self, position: Position) -> Union[Position, None]:
        return self.at(position.y + 1, position.x)

    def west(self, position: Position) -> Union[Position, None]:
        return self.at(position.y, position.x - 1)

    def northwest(self, position: Position) -> Union[Position, None]:
        return self.at(position.y - 1, position.x - 1)

    def northeast(self, position: Position) -> Union[Position, None]:
        return self.at(position.y - 1, position.x + 1)

    def southwest(self, position: Position) -> Union[Position, None]:
        return self.at(position.y + 1, position.x - 1)

    def southeast(self, position: Position) -> Union[Position, None]:
        return self.at(position.y + 1, position.x + 1)

    def adjacent(self, position: Position) -> tuple:
        """
        Return a tuple of all grid positions adjacent to the specified position, including diagonals.
        """
        return (
            self.northwest(position),
            self.north(position),
            self.northeast(position),
            self.east(position),
            self.southeast(position),
            self.south(position),
            self.southwest(position),
            self.west(position),
        )
