from collections import deque
from enum import Enum
from itertools import islice
from typing import ClassVar, Deque, Dict, FrozenSet, Iterable, Iterator, Tuple

from permuta.patterns.perm import Perm


class PermType(Enum):
    """Collection of structural types for perms."""

    WPP = 0  # W++
    WPM = 1  # W+-
    WMP = 2  # W-+
    WMM = 3  # W--
    WIPP = 4  # Winv++
    WIPM = 5  # Winv+-
    WIMP = 6  # Winv-+
    WIMM = 7  # Winv--
    L2 = 8  # L2
    L2I = 9  # L2inv


class PolyPerms:
    """A static container of methods to check if a perm set has a polynomial growth."""

    _CACHE: ClassVar[Dict[Perm, FrozenSet]] = {}

    @staticmethod
    def _types(perm: Perm) -> FrozenSet[int]:
        interset = PolyPerms._CACHE.get(perm)
        if interset is None:
            interset = frozenset(PolyPerms._find_type(perm))
            PolyPerms._CACHE[perm] = interset
        return interset

    @staticmethod
    def _type_0_3(slice1: Deque[int], slice2: Deque[int]) -> Iterator[PermType]:
        if PolyPerms._is_incr(slice1):
            if PolyPerms._is_incr(slice2):
                yield PermType.WPP
            if PolyPerms._is_decr(slice2):
                yield PermType.WPM
        if PolyPerms._is_decr(slice1):
            if PolyPerms._is_incr(slice2):
                yield PermType.WMP
            if PolyPerms._is_decr(slice2):
                yield PermType.WMM

    @staticmethod
    def _type_4_7(slice1: Deque[int], slice2: Deque[int]) -> Iterator[PermType]:
        if PolyPerms._is_incr(slice1):
            if PolyPerms._is_incr(slice2):
                yield PermType.WIPP
            if PolyPerms._is_decr(slice2):
                yield PermType.WIPM
        if PolyPerms._is_decr(slice1):
            if PolyPerms._is_incr(slice2):
                yield PermType.WIMP
            if PolyPerms._is_decr(slice2):
                yield PermType.WIMM

    @staticmethod
    def _find_type(perm: Perm) -> Iterable[PermType]:
        p_deq1: Deque[int] = deque([])
        p_deq2: Deque[int] = deque(perm)
        fp_deq1: Deque[int] = deque([])
        fp_deq2: Deque[int] = deque(perm.inverse())
        yield from PolyPerms._type_0_3(p_deq1, p_deq2)
        yield from PolyPerms._type_4_7(fp_deq1, fp_deq2)
        for _ in range(len(perm)):
            p_deq1.append(p_deq2.popleft())
            fp_deq1.append(fp_deq2.popleft())
            yield from PolyPerms._type_0_3(p_deq1, p_deq2)
            yield from PolyPerms._type_4_7(fp_deq1, fp_deq2)
        if PolyPerms._of_type_8(perm):
            yield PermType.L2
        if PolyPerms._of_type_8(perm.reverse()):
            yield PermType.L2I

    @staticmethod
    def _is_decr(perm_slice: Deque[int]) -> bool:
        return all(
            prev > curr for prev, curr in zip(perm_slice, islice(perm_slice, 1, None))
        )

    @staticmethod
    def _is_incr(perm_slice: Deque[int]) -> bool:
        return all(
            prev < curr for prev, curr in zip(perm_slice, islice(perm_slice, 1, None))
        )

    @staticmethod
    def _of_type_8(perm_slice: Tuple[int, ...]) -> bool:
        n = len(perm_slice)
        if n < 2:
            return True
        if perm_slice[-1] == n - 1:
            return PolyPerms._of_type_8(perm_slice[0 : n - 1])
        if perm_slice[-1] == n - 2 and perm_slice[-2] == n - 1:
            return PolyPerms._of_type_8(perm_slice[0 : n - 2])
        return False

    @staticmethod
    def is_polynomial(basis: Iterable[Perm]) -> bool:
        """True if the perm set generated by basis has polynomial growth."""
        return (
            len({pol_type for perm in basis for pol_type in PolyPerms._types(perm)})
            == 10
        )

    @staticmethod
    def is_non_polynomial(basis: Iterable[Perm]) -> bool:
        """False if the perm set generated by basis has polynomial growth."""
        return not PolyPerms.is_polynomial(basis)
