from collections import namedtuple
from itertools import combinations
from typing import List, Tuple, Any, Dict, Iterable, Set

from interegular import InvalidSyntax
from interegular.fsm import FSM
from interegular.patterns import Pattern, Unsupported, parse_pattern
from interegular.utils import logger


Example = namedtuple("Example", "prefix main_text postfix")


class Comparator:
    def __init__(self, patterns: Dict[Any, Pattern]):
        self._patterns = patterns
        if not patterns:  # `isdisjoint` can not be called anyway, so we don't need to create a valid state
            return
        self._alphabet = frozenset.union(*(p.alphabet for p in patterns.values()))
        prefix_postfix_s = [p.prefix_postfix for p in patterns.values()]
        self._prefix_postfix = max(p[0] for p in prefix_postfix_s), max(p[1] for p in prefix_postfix_s)
        self._fsms: Dict[Any, FSM] = {}
        self._know_pairs: Dict[Tuple[Any, Any], bool] = {}
        self._marked_pairs: Set[Tuple[Any, Any]] = set()

    def get_fsm(self, a: Any) -> FSM:
        if a not in self._fsms:
            try:
                self._fsms[a] = self._patterns[a].to_fsm(self._alphabet, self._prefix_postfix)
            except Unsupported as e:
                self._fsms[a] = None
                logger.warning(f"Can't compile Pattern to fsm for {a}\n     {repr(e)}")
            except KeyError:
                self._fsms[a] = None # In case it was thrown away in `from_regexes`
        return self._fsms[a]

    def isdisjoint(self, a: Any, b: Any) -> bool:
        if (a, b) not in self._know_pairs:
            fa, fb = self.get_fsm(a), self.get_fsm(b)
            if fa is None or fb is None:
                self._know_pairs[a, b] = True  # We can't know. Assume they are disjoint
            else:
                self._know_pairs[a, b] = fa.isdisjoint(fb)
        return self._know_pairs[a, b]

    def check(self, keys: Iterable[Any], skip_marked: bool = False) -> Iterable[Tuple[Any, Any]]:
        for a, b in combinations(keys, 2):
            if skip_marked and self.is_marked(a,b):
                continue
            if not self.isdisjoint(a, b):
                yield a, b

    def get_example_overlap(self, a: Any, b: Any) -> Example:
        fa, fb = self.get_fsm(a), self.get_fsm(b)
        intersection = fa.intersection(fb)
        text = ''.join(next(intersection.strings()))
        i, j = self._prefix_postfix
        return Example(text[:i], text[i:-j], text[-j])


    def is_marked(self, a: Any, b: Any) -> bool:
        return frozenset({a, b}) in self._marked_pairs

    def mark(self, a: Any, b: Any):
        self._marked_pairs.add(frozenset({a, b}))

    @classmethod
    def from_regexes(cls, regexes: Dict[Any, str]):
        patterns = {}
        for k, r in regexes.items():
            try:
                patterns[k] = parse_pattern(r)
            except (Unsupported, InvalidSyntax) as e:
                logger.warning(f"Can't compile regex to Pattern for {k}\n     {repr(e)}")
        return cls(patterns)
