"""PA4 SoDuckYou.

A pond is split into patches, each one marked by a letter. The player places
ducks so that every row, every column, and every patch has exactly one duck,
and no two ducks touch, not even diagonally. The program reads the levels
from a file and checks the player's pond after every move.

Name: YOUR NAME
Date: THE DATE
"""

# Part D uses json. Delete the noqa comment once your code calls json.load().
import json  # noqa: F401


def count_patches(grid: list[str], ducks: list[tuple[int, int]]) -> dict[str, int]:
    """Count the ducks in each patch of the pond.

    Args:
        grid: The pond, one string per row and one letter per square. The
            squares with the same letter make up one patch.
        ducks: The (row, column) of each duck, all different.

    Returns:
        A new dictionary that maps the letter of every patch in the grid to
        how many ducks are in it, including the patches with no ducks at all.
        Ex: count_patches(["ABBC", "ABBC", "ABBD", "ADDD"],
        [(0, 1), (1, 2), (3, 0)]) returns {"A": 1, "B": 2, "C": 0, "D": 0}.
    """
    pass


def rule_breaks(grid: list[str], ducks: list[tuple[int, int]]) -> list[str]:
    """Describe every row, column, and patch that does not have exactly one duck.

    The rows are listed first, from the top, then the columns, from the
    left, then the patches, in alphabetical order. Rows and columns are
    numbered from 0. Each message says how many ducks there are, such as
    "Row 2 has 0 ducks", "Column 1 has 2 ducks", or "Patch C has 0 ducks".

    Args:
        grid: The pond, as in count_patches().
        ducks: The (row, column) of each duck, all different.

    Returns:
        A new list of the messages, which is empty when every row, column,
        and patch has one duck. Ex: rule_breaks(["ABBC", "ABBC", "ABBD",
        "ADDD"], [(0, 1), (1, 2), (3, 0)]) returns ["Row 2 has 0 ducks",
        "Column 3 has 0 ducks", "Patch B has 2 ducks", "Patch C has 0 ducks",
        "Patch D has 0 ducks"].
    """
    pass


def touching(ducks: list[tuple[int, int]]) -> list[tuple[tuple[int, int], tuple[int, int]]]:
    """Find every pair of ducks that touch, side by side or diagonally.

    Two ducks touch when their rows are at most 1 apart and their columns
    are at most 1 apart.

    Args:
        ducks: The (row, column) of each duck, all different.

    Returns:
        A new list of the pairs that touch. Each pair is listed once, as a
        tuple of the two ducks in the order they appear in ducks, and the
        pairs are in the order of their first duck, then their second. Ex:
        touching([(0, 1), (2, 0), (1, 2), (3, 1)]) returns
        [((0, 1), (1, 2)), ((2, 0), (3, 1))].
    """
    pass


def load_levels(filename: str, size: int) -> dict[str, list[str]]:
    """Read the levels of one size from a JSON file.

    The file holds a list of levels. Each level is a dictionary with a
    "name", a "size", and a "grid" (a list of strings, as in
    count_patches()), such as {"name": "Puddle", "size": 4, "grid": ["ABBC",
    "ABBC", "ABBD", "ADDD"]}. A level still being tested also has "draft"
    set to true; levels without a "draft" key are finished.

    Args:
        filename: The path of the JSON file to read.
        size: How many rows the levels should have.

    Returns:
        A new dictionary that maps the name of each finished level of the
        given size to its grid, in the order of the file. Ex: if the file
        holds Puddle (size 4), Mud Hole (size 4, a draft), and Pond (size 5),
        load_levels(filename, 4) returns {"Puddle": ["ABBC", "ABBC", "ABBD",
        "ADDD"]}.
    """
    pass


if __name__ == "__main__":
    # Do not edit the main block. It runs the game once every part works.
    print("Welcome to SoDuckYou!")
    size = int(input("Pond size (4, 5, or 6): "))
    levels = load_levels("levels.json", size)
    print("Levels:", ", ".join(levels))
    name = input("Level: ")
    while name not in levels:
        name = input("No such level. Level: ")
    grid = levels[name]
    ducks = []
    solved = False
    move = input("Row and column (blank to quit): ")
    while move != "" and not solved:
        numbers = move.split()
        duck = (int(numbers[0]), int(numbers[1]))
        if duck in ducks:
            ducks.remove(duck)
        else:
            ducks.append(duck)
        print()
        print("  " + " ".join([str(col) for col in range(size)]))
        for row in range(size):
            line = f"{row} "
            for col in range(size):
                if (row, col) in ducks:
                    line += "🐥"
                else:
                    line += grid[row][col] + " "
            print(line)
        problems = []
        for pair in touching(ducks):
            problems.append(f"Ducks at {pair[0]} and {pair[1]} are touching")
        problems += rule_breaks(grid, ducks)
        if problems == []:
            print("You solved it! Every duck has its own space.")
            solved = True
        else:
            for problem in problems[:3]:
                print("-", problem)
            if len(problems) > 3:
                print(f"- and {len(problems) - 3} more")
            move = input("Row and column (blank to quit): ")
