from typing import Dict, TYPE_CHECKING

from BaseClasses import CollectionState
from worlds.generic.Rules import allow_self_locking_items, CollectionRule
from .options import DraculasCondition
from .entrances import get_entrance_info
from .data import iname, rname

if TYPE_CHECKING:
    from . import CV64World


class CV64Rules:
    player: int
    world: "CV64World"
    rules: Dict[str, CollectionRule]
    s1s_per_warp: int
    required_s2s: int
    drac_condition: int

    def __init__(self, world: "CV64World") -> None:
        self.player = world.player
        self.world = world
        self.s1s_per_warp = world.s1s_per_warp
        self.required_s2s = world.required_s2s
        self.drac_condition = world.drac_condition

        self.rules = {
            iname.left_tower_key: lambda state: state.has(iname.left_tower_key, self.player),
            iname.storeroom_key: lambda state: state.has(iname.storeroom_key, self.player),
            iname.archives_key: lambda state: state.has(iname.archives_key, self.player),
            iname.garden_key: lambda state: state.has(iname.garden_key, self.player),
            iname.copper_key: lambda state: state.has(iname.copper_key, self.player),
            iname.chamber_key: lambda state: state.has(iname.chamber_key, self.player),
            "Bomb 1": lambda state: state.has_all({iname.magical_nitro, iname.mandragora}, self.player),
            "Bomb 2": lambda state: state.has(iname.magical_nitro, self.player, 2)
                                    and state.has(iname.mandragora, self.player, 2),
            iname.execution_key: lambda state: state.has(iname.execution_key, self.player),
            iname.science_key1: lambda state: state.has(iname.science_key1, self.player),
            iname.science_key2: lambda state: state.has(iname.science_key2, self.player),
            iname.science_key3: lambda state: state.has(iname.science_key3, self.player),
            iname.clocktower_key1: lambda state: state.has(iname.clocktower_key1, self.player),
            iname.clocktower_key2: lambda state: state.has(iname.clocktower_key2, self.player),
            iname.clocktower_key3: lambda state: state.has(iname.clocktower_key3, self.player),
            "Dracula": self.can_enter_dracs_chamber
        }

    def can_enter_dracs_chamber(self, state: CollectionState) -> bool:
        drac_object_name = None
        if self.drac_condition == DraculasCondition.option_crystal:
            drac_object_name = "Crystal"
        elif self.drac_condition == DraculasCondition.option_bosses:
            drac_object_name = "Trophy"
        elif self.drac_condition == DraculasCondition.option_specials:
            drac_object_name = "Special2"

        if drac_object_name is not None:
            return state.has(drac_object_name, self.player, self.required_s2s)
        return True

    def set_cv64_rules(self) -> None:
        multiworld = self.world.multiworld

        for region in multiworld.get_regions(self.player):
            # Set each entrance's rule if it should have one.
            # Warp entrances have their own special handling.
            for entrance in region.entrances:
                if entrance.parent_region.name == "Menu":
                    if entrance.name.startswith("Warp "):
                        entrance.access_rule = lambda state, warp_num=int(entrance.name[5]): \
                            state.has(iname.special_one, self.player, self.s1s_per_warp * warp_num)
                else:
                    ent_rule = get_entrance_info(entrance.name, "rule")
                    if ent_rule in self.rules:
                        entrance.access_rule = self.rules[ent_rule]

        multiworld.completion_condition[self.player] = lambda state: state.has(iname.victory, self.player)
        if self.world.options.accessibility:  # not locations accessibility
            self.set_self_locking_items()

    def set_self_locking_items(self) -> None:
        multiworld = self.world.multiworld

        # Do the regions that we know for a fact always exist, and we always do no matter what.
        allow_self_locking_items(multiworld.get_region(rname.villa_archives, self.player), iname.archives_key)
        allow_self_locking_items(multiworld.get_region(rname.cc_torture_chamber, self.player), iname.chamber_key)

        # Add this region if the world doesn't have the Villa Storeroom warp entrance.
        if "Villa" not in self.world.active_warp_list[1:]:
            allow_self_locking_items(multiworld.get_region(rname.villa_storeroom, self.player), iname.storeroom_key)

        # Add this region if Hard Logic is on and Multi Hit Breakables are off.
        if self.world.options.hard_logic and not self.world.options.multi_hit_breakables:
            allow_self_locking_items(multiworld.get_region(rname.cw_ltower, self.player), iname.left_tower_key)

        # Add these regions if Tower of Science is in the world.
        if "Tower of Science" in self.world.active_stage_exits:
            allow_self_locking_items(multiworld.get_region(rname.tosci_three_doors, self.player), iname.science_key1)
            allow_self_locking_items(multiworld.get_region(rname.tosci_key3, self.player), iname.science_key3)

        # Add this region if Tower of Execution is in the world and Hard Logic is not on.
        if "Tower of Execution" in self.world.active_stage_exits and self.world.options.hard_logic:
            allow_self_locking_items(multiworld.get_region(rname.toe_ledge, self.player), iname.execution_key)