from dataclasses import dataclass

from goldberg.operators import (
    GoldbergOperator,
    goldberg_operators_to_parameters,
    goldberg_parameters_to_operators,
    verify_recipes,
)


tetrahedral_goldberg_recipes = {
    # Class I
    (1, 0): "T",
    (2, 0): "K300cT",
    (3, 0): "K300tkT",
    (4, 0): "K300duC30uT",
    (5, 0): "C300du5T",
    (6, 0): "K300duK300dtkT",
    # Class II
    (1, 1): "tT",
    (2, 2): "K300duK30dtT",
    (3, 3): "K300tktT",
    (4, 4): "duK300dK300dudtT",
    # Class III
    (2, 1): "K300wT",
    (3, 1): "*",
    (3, 2): "*",
    (4, 1): "K300wK30tT",
    (4, 2): "K300wK30cT",
}


# Assert all the above recipes are correct
verify_recipes(
    {
        param: " = ".join(
            [op + "T" for op in goldberg_parameters_to_operators[param].split(" = ")]
        )
        for param in tetrahedral_goldberg_recipes.keys()
    },
    tetrahedral_goldberg_recipes,
    "Tetrahedral recipe does not match!",
)


@dataclass
class HexagonPath:
    """A path along hexagons on a Goldberg polyhedron."""

    a: int
    b: int

    def __hash__(self):
        return hash((self.a, self.b))

    def __str__(self):
        return str(f"{(self.a, self.b)}")


_P = HexagonPath


@dataclass
class WhirledPath:
    """
    A path on a tetrahedral solution where leaving an initial pentagon from
    the edge not touching another pentagon enters a terminal pentagon through one which is.

    In other words, the initial and terminal pentagons are in some sense "rotated"
    from one another.

    Note that one whirled path signifies two possible paths: (a, b) and (a + b, 0)
    """

    a: int
    b: int

    def __hash__(self):
        return hash((self.a, self.b))

    def __str__(self):
        return str(f"w{(self.a, self.b)}")


_WP = WhirledPath


@dataclass
class TetrahedralAntitruncation:
    conway: str  # Conway polyhedron notation for constructing the polygon
    conway_recipe: str  # The recipe for constructing the polyhedron in a viewer, equivalent to `conway` but stabler
    intercluster_paths: list[
        HexagonPath | WhirledPath
    ]  # Path connecting pentagons of two different clusters
    cluster_path: HexagonPath | WhirledPath = _P(
        1, 0
    )  # Path between pentagons in a cluster


_TA = TetrahedralAntitruncation

tetrahedral_goldberg_antitruncations: dict[
    tuple[int, int], TetrahedralAntitruncation
] = {
    # Class I
    (1, 0): _TA("dT", "dT", []),
    (2, 0): _TA("C", "C", []),
    (3, 0): _TA("t6kT", "K300t6kT", [_P(1, 0)]),
    (4, 0): _TA("t6juT", "C300t6juT", [_P(2, 0)]),
    (5, 0): _TA("**", "**", [_P(3, 0)]),
    (6, 0): _TA("t6kcT", "K300t6kcT", [_P(4, 0)]),
    # Class II
    (1, 1): _TA("T", "T", []),
    (2, 2): _TA("t6uT", "K300t6uT", [_P(1, 1)]),
    (3, 3): _TA("t6ktT", "t6ktT", [_P(2, 2)]),
    (4, 4): _TA("t6uuT", "K300t6uK30dK30duT", [_P(3, 3)]),
    # Class III
    (2, 1): _TA("D", "D", [_P(1, 0)]),
    (3, 1): _TA("*", "*", []),
    (3, 2): _TA("*", "*", []),
    (4, 1): _TA("t6gtT", "K300t6gK30tT", [_WP(2, 1)]),
    (4, 2): _TA("t6guT = t6gcT", "K300t6gK30cT", [_P(3, 1)]),
}

# Ensure the correctness of all tetrahedral antitruncation recipes
verify_recipes(
    {
        parameter: data.conway
        for parameter, data in tetrahedral_goldberg_antitruncations.items()
    },
    {
        parameter: data.conway_recipe
        for parameter, data in tetrahedral_goldberg_antitruncations.items()
    },
    "Tetrahedral antitruncation recipe does not match!",
)


double_goldberg_operators: list[GoldbergOperator] = ["c", "dk", "tk", "w"]

double_goldberg_paths: dict[
    GoldbergOperator, dict[HexagonPath | WhirledPath, list[HexagonPath | WhirledPath]]
] = {
    "c": {
        _P(1, 0): [_P(2, 0)],
        _P(1, 1): [_P(2, 2)],
        _WP(2, 1): [_WP(4, 2)],  # paths get doubled
    },
    "dk": {
        _P(1, 0): [_P(1, 1)],
        _P(1, 1): [_P(3, 0)],
        _WP(2, 1): [
            _P(4, 1),
            _P(3, 3),
        ],  # Whirled path splits into Class II and Class III-types
    },
    "tk": {
        _P(1, 0): [_P(3, 0)],
        _P(1, 1): [_P(3, 3)],
        _WP(2, 1): [_WP(6, 3)],  # paths get tripled
    },
    "w": {
        _P(1, 0): [_P(2, 1)],
        _P(1, 1): [_P(4, 1)],
        _WP(2, 1): [_P(5, 3), _P(6, 3)],  # Whirled path splits into two Class III-types
    },
}


def _apply_double(
    operation: GoldbergOperator,
) -> dict[tuple[int, int], TetrahedralAntitruncation]:
    """Build data for a double-Goldberg tetrahedral solution"""
    regularized_operation = "K300" + operation if operation == "w" else operation
    return {
        entry: TetrahedralAntitruncation(
            conway=" = ".join(
                [operation + i for i in antitruncation.conway.split(" = ")]
            ),
            conway_recipe=(
                "*"
                if antitruncation.conway_recipe.find("*") == 0
                else regularized_operation + antitruncation.conway_recipe
            ),
            intercluster_paths=double_goldberg_paths[operation][
                antitruncation.intercluster_paths[0]
            ],
            cluster_path=_P(*goldberg_operators_to_parameters[operation]),
        )
        for entry in [(3, 0), (2, 2), (4, 1)]
        for antitruncation in (tetrahedral_goldberg_antitruncations[entry],)
    }


double_goldberg_antitruncations: dict[
    GoldbergOperator, dict[tuple[int, int], TetrahedralAntitruncation]
] = {
    operator: _apply_double(operator)
    for operator in double_goldberg_operators  # for some reason, using a literal list causes type errors
}
