import typing
from itertools import chain
from typing import Callable, Set

from . import pyevermizer
from .options import EnergyCore, OutOfBounds, SequenceBreaks, SoEOptions

if typing.TYPE_CHECKING:
    from BaseClasses import CollectionState

# TODO: Options may preset certain progress steps (i.e. P_ROCK_SKIP), set in generate_early?

# TODO: resolve/flatten/expand rules to get rid of recursion below where possible
# Logic.rules are all rules including locations, excluding those with no progress (i.e. locations that only drop items)
rules = pyevermizer.get_logic()
# Logic.items are all items and extra items excluding non-progression items and duplicates
# NOTE: we are skipping sniff items here because none of them is supposed to provide progression
item_names: Set[str] = set()
items = [item for item in filter(lambda item: item.progression,  # type: ignore[arg-type]
                                 chain(pyevermizer.get_items(), pyevermizer.get_extra_items()))
         if item.name not in item_names and not item_names.add(item.name)]  # type: ignore[func-returns-value]


class SoEPlayerLogic:
    __slots__ = "player", "out_of_bounds", "sequence_breaks", "has"
    player: int
    out_of_bounds: bool
    sequence_breaks: bool

    has: Callable[..., bool]
    """
    Returns True if count of one of evermizer's progress steps is reached based on collected items. i.e. 2 * P_DE
    """

    def __init__(self, player: int, options: "SoEOptions"):
        self.player = player
        self.out_of_bounds = options.out_of_bounds == OutOfBounds.option_logic
        self.sequence_breaks = options.sequence_breaks == SequenceBreaks.option_logic

        if options.energy_core == EnergyCore.option_fragments:
            # override logic for energy core fragments
            required_fragments = options.required_fragments.value

            def fragmented_has(state: "CollectionState", progress: int, count: int = 1) -> bool:
                if progress == pyevermizer.P_ENERGY_CORE:
                    progress = pyevermizer.P_CORE_FRAGMENT
                    count = required_fragments
                return self._has(state, progress, count)

            self.has = fragmented_has
        else:
            # default (energy core) logic
            self.has = self._has

    def _count(self, state: "CollectionState", progress: int, max_count: int = 0) -> int:
        """
        Returns reached count of one of evermizer's progress steps based on collected items.
        i.e. returns 0-3 for P_DE based on items providing CHECK_BOSS,DIAMOND_EYE_DROP
        """
        n = 0
        for item in items:
            for pvd in item.provides:
                if pvd[1] == progress:
                    if state.has(item.name, self.player):
                        n += state.count(item.name, self.player) * pvd[0]
                        if n >= max_count > 0:
                            return n
        for rule in rules:
            for pvd in rule.provides:
                if pvd[1] == progress and pvd[0] > 0:
                    has = True
                    for req in rule.requires:
                        if not self.has(state, req[1], req[0]):
                            has = False
                            break
                    if has:
                        n += pvd[0]
                        if n >= max_count > 0:
                            return n
        return n

    def _has(self, state: "CollectionState", progress: int, count: int = 1) -> bool:
        """Default implementation of has"""
        if self.out_of_bounds is True and progress == pyevermizer.P_ALLOW_OOB:
            return True
        if self.sequence_breaks is True and progress == pyevermizer.P_ALLOW_SEQUENCE_BREAKS:
            return True
        return self._count(state, progress, count) >= count