"""
    Ensures a target level can be reached with available resources
    """
from worlds.generic.Rules import CollectionRule, add_rule
from .Names import RegionNames, ItemNames


def get_fishing_skill_rule(level, player, options) -> CollectionRule:
    if options.max_fishing_level < level:
        return lambda state: False

    if options.brutal_grinds or level < 5:
        return lambda state: state.can_reach_region(RegionNames.Shrimp, player)
    if level < 20:
        return lambda state: state.can_reach_region(RegionNames.Shrimp, player) and \
                             state.can_reach_region(RegionNames.Port_Sarim, player)
    else:
        return lambda state: state.can_reach_region(RegionNames.Shrimp, player) and \
                             state.can_reach_region(RegionNames.Port_Sarim, player) and \
                             state.can_reach_region(RegionNames.Fly_Fish, player)


def get_mining_skill_rule(level, player, options) -> CollectionRule:
    if options.max_mining_level < level:
        return lambda state: False

    if options.brutal_grinds or level < 15:
        return lambda state: state.can_reach_region(RegionNames.Bronze_Ores, player) or \
                             state.can_reach_region(RegionNames.Clay_Rock, player)
    else:
        # Iron is the best way to train all the way to 99, so having access to iron is all you need to check for
        return lambda state: (state.can_reach_region(RegionNames.Bronze_Ores, player) or
                              state.can_reach_region(RegionNames.Clay_Rock, player)) and \
                             state.can_reach_region(RegionNames.Iron_Rock, player)


def get_woodcutting_skill_rule(level, player, options) -> CollectionRule:
    if options.max_woodcutting_level < level:
        return lambda state: False

    if options.brutal_grinds or level < 15:
        # I've checked. There is not a single chunk in the f2p that does not have at least one normal tree.
        # Even the desert.
        return lambda state: True
    if level < 30:
        return lambda state: state.can_reach_region(RegionNames.Oak_Tree, player)
    else:
        return lambda state: state.can_reach_region(RegionNames.Oak_Tree, player) and \
                             state.can_reach_region(RegionNames.Willow_Tree, player)


def get_smithing_skill_rule(level, player, options) -> CollectionRule:
    if options.max_smithing_level < level:
        return lambda state: False

    if options.brutal_grinds:
        return lambda state: state.can_reach_region(RegionNames.Bronze_Ores, player) and \
                             state.can_reach_region(RegionNames.Furnace, player)
    if level < 15:
        # Lumbridge has a special bronze-only anvil. This is the only anvil of its type so it's not included
        # in the "Anvil" resource region. We still need to check for it though.
        return lambda state: state.can_reach_region(RegionNames.Bronze_Ores, player) and \
                             state.can_reach_region(RegionNames.Furnace, player) and \
                             (state.can_reach_region(RegionNames.Anvil, player) or
                              state.can_reach_region(RegionNames.Lumbridge, player))
    if level < 30:
        # For levels between 15 and 30, the lumbridge anvil won't cut it. Only a real one will do
        return lambda state: state.can_reach_region(RegionNames.Bronze_Ores, player) and \
                             state.can_reach_region(RegionNames.Iron_Rock, player) and \
                             state.can_reach_region(RegionNames.Furnace, player) and \
                             state.can_reach_region(RegionNames.Anvil, player)
    else:
        return lambda state: state.can_reach_region(RegionNames.Bronze_Ores, player) and \
                             state.can_reach_region(RegionNames.Iron_Rock, player) and \
                             state.can_reach_region(RegionNames.Coal_Rock, player) and \
                             state.can_reach_region(RegionNames.Furnace, player) and \
                             state.can_reach_region(RegionNames.Anvil, player)


def get_crafting_skill_rule(level, player, options):
    if options.max_crafting_level < level:
        return lambda state: False

    # Crafting is really complex. Need a lot of sub-rules to make this even remotely readable
    def can_spin(state):
        return state.can_reach_region(RegionNames.Sheep, player) and \
            state.can_reach_region(RegionNames.Spinning_Wheel, player)

    def can_pot(state):
        return state.can_reach_region(RegionNames.Clay_Rock, player) and \
            state.can_reach_region(RegionNames.Barbarian_Village, player)

    def can_tan(state):
        return state.can_reach_region(RegionNames.Milk, player) and \
            state.can_reach_region(RegionNames.Al_Kharid, player)

    def mould_access(state):
        return state.can_reach_region(RegionNames.Al_Kharid, player) or \
            state.can_reach_region(RegionNames.Rimmington, player)

    def can_silver(state):
        return state.can_reach_region(RegionNames.Silver_Rock, player) and \
            state.can_reach_region(RegionNames.Furnace, player) and mould_access(state)

    def can_gold(state):
        return state.can_reach_region(RegionNames.Gold_Rock, player) and \
            state.can_reach_region(RegionNames.Furnace, player) and mould_access(state)

    if options.brutal_grinds or level < 5:
        return lambda state: can_spin(state) or can_pot(state) or can_tan(state)

    can_smelt_gold = get_smithing_skill_rule(40, player, options)
    can_smelt_silver = get_smithing_skill_rule(20, player, options)
    if level < 16:
        return lambda state: can_pot(state) or can_tan(state) or (can_gold(state) and can_smelt_gold(state))
    else:
        return lambda state: can_tan(state) or (can_silver(state) and can_smelt_silver(state)) or \
                             (can_gold(state) and can_smelt_gold(state))


def get_cooking_skill_rule(level, player, options) -> CollectionRule:
    if options.max_cooking_level < level:
        return lambda state: False

    if options.brutal_grinds or level < 15:
        return lambda state: state.can_reach_region(RegionNames.Milk, player) or \
                             state.can_reach_region(RegionNames.Egg, player) or \
                             state.can_reach_region(RegionNames.Shrimp, player) or \
                             (state.can_reach_region(RegionNames.Wheat, player) and
                              state.can_reach_region(RegionNames.Windmill, player))
    else:
        can_catch_fly_fish = get_fishing_skill_rule(20, player, options)

        return lambda state: (
                                 (state.can_reach_region(RegionNames.Fly_Fish, player) and can_catch_fly_fish(state)) or
                                 (state.can_reach_region(RegionNames.Port_Sarim, player))
                             ) and (
                              state.can_reach_region(RegionNames.Milk, player) or
                              state.can_reach_region(RegionNames.Egg, player) or
                              state.can_reach_region(RegionNames.Shrimp, player) or
                              (state.can_reach_region(RegionNames.Wheat, player) and
                               state.can_reach_region(RegionNames.Windmill, player))
                             )


def get_runecraft_skill_rule(level, player, options) -> CollectionRule:
    if options.max_runecraft_level < level:
        return lambda state: False
    if not options.brutal_grinds:
        # Ensure access to the relevant altars
        if level >= 5:
            return lambda state: state.has(ItemNames.QP_Rune_Mysteries, player) and \
                                 state.can_reach_region(RegionNames.Falador_Farm, player) and \
                                 state.can_reach_region(RegionNames.Lumbridge_Swamp, player)
        if level >= 9:
            return lambda state: state.has(ItemNames.QP_Rune_Mysteries, player) and \
                                 state.can_reach_region(RegionNames.Falador_Farm, player) and \
                                 state.can_reach_region(RegionNames.Lumbridge_Swamp, player) and \
                                 state.can_reach_region(RegionNames.East_Of_Varrock, player)
        if level >= 14:
            return lambda state: state.has(ItemNames.QP_Rune_Mysteries, player) and \
                                 state.can_reach_region(RegionNames.Falador_Farm, player) and \
                                 state.can_reach_region(RegionNames.Lumbridge_Swamp, player) and \
                                 state.can_reach_region(RegionNames.East_Of_Varrock, player) and \
                                 state.can_reach_region(RegionNames.Al_Kharid, player)

    return lambda state: state.has(ItemNames.QP_Rune_Mysteries, player) and \
                         state.can_reach_region(RegionNames.Falador_Farm, player)


def get_magic_skill_rule(level, player, options) -> CollectionRule:
    if options.max_magic_level < level:
        return lambda state: False

    return lambda state: state.can_reach_region(RegionNames.Mind_Runes, player)


def get_firemaking_skill_rule(level, player, options) -> CollectionRule:
    if options.max_firemaking_level < level:
        return lambda state: False
    if not options.brutal_grinds:
        if level >= 30:
            can_chop_willows = get_woodcutting_skill_rule(30, player, options)
            return lambda state: state.can_reach_region(RegionNames.Willow_Tree, player) and can_chop_willows(state)
        if level >= 15:
            can_chop_oaks = get_woodcutting_skill_rule(15, player, options)
            return lambda state: state.can_reach_region(RegionNames.Oak_Tree, player) and can_chop_oaks(state)
    # If brutal grinds are on, or if the level is less than 15, you can train it.
    return lambda state: True


def get_skill_rule(skill, level, player, options) -> CollectionRule:
    if skill.lower() == "fishing":
        return get_fishing_skill_rule(level, player, options)
    if skill.lower() == "mining":
        return get_mining_skill_rule(level, player, options)
    if skill.lower() == "woodcutting":
        return get_woodcutting_skill_rule(level, player, options)
    if skill.lower() == "smithing":
        return get_smithing_skill_rule(level, player, options)
    if skill.lower() == "crafting":
        return get_crafting_skill_rule(level, player, options)
    if skill.lower() == "cooking":
        return get_cooking_skill_rule(level, player, options)
    if skill.lower() == "runecraft":
        return get_runecraft_skill_rule(level, player, options)
    if skill.lower() == "magic":
        return get_magic_skill_rule(level, player, options)
    if skill.lower() == "firemaking":
        return get_firemaking_skill_rule(level, player, options)

    return lambda state: True


def generate_special_rules_for(entrance, region_row, outbound_region_name, player, options):
    if outbound_region_name == RegionNames.Cooks_Guild:
        add_rule(entrance, get_cooking_skill_rule(32, player, options))
    elif outbound_region_name == RegionNames.Crafting_Guild:
        add_rule(entrance, get_crafting_skill_rule(40, player, options))
    elif outbound_region_name == RegionNames.Corsair_Cove:
        # Need to be able to start Corsair Curse in addition to having the item
        add_rule(entrance, lambda state: state.can_reach(RegionNames.Falador_Farm, "Region", player))
    elif outbound_region_name == "Camdozaal*":
        add_rule(entrance, lambda state: state.has(ItemNames.QP_Below_Ice_Mountain, player))
    elif region_row.name == "Dwarven Mountain Pass" and outbound_region_name == "Anvil*":
        add_rule(entrance, lambda state: state.has(ItemNames.QP_Dorics_Quest, player))

    # Special logic for canoes
    canoe_regions = [RegionNames.Lumbridge, RegionNames.South_Of_Varrock, RegionNames.Barbarian_Village,
                     RegionNames.Edgeville, RegionNames.Wilderness]
    if region_row.name in canoe_regions:
        # Skill rules for greater distances
        woodcutting_rule_d1 = get_woodcutting_skill_rule(12, player, options)
        woodcutting_rule_d2 = get_woodcutting_skill_rule(27, player, options)
        woodcutting_rule_d3 = get_woodcutting_skill_rule(42, player, options)
        woodcutting_rule_all = get_woodcutting_skill_rule(57, player, options)

        if region_row.name == RegionNames.Lumbridge:
            # Canoe Tree access for the Location
            if outbound_region_name == RegionNames.Canoe_Tree:
                add_rule(entrance,
                    lambda state: (state.can_reach_region(RegionNames.South_Of_Varrock, player)
                                   and woodcutting_rule_d1(state)) or
                                  (state.can_reach_region(RegionNames.Barbarian_Village, player)
                                   and woodcutting_rule_d2(state)) or
                                  (state.can_reach_region(RegionNames.Edgeville, player)
                                   and woodcutting_rule_d3(state)) or
                                  (state.can_reach_region(RegionNames.Wilderness, player)
                                   and woodcutting_rule_all(state)))

            # Access to other chunks based on woodcutting settings
            elif outbound_region_name == RegionNames.South_Of_Varrock:
                add_rule(entrance, woodcutting_rule_d1)
            elif outbound_region_name == RegionNames.Barbarian_Village:
                add_rule(entrance, woodcutting_rule_d2)
            elif outbound_region_name == RegionNames.Edgeville:
                add_rule(entrance, woodcutting_rule_d3)
            elif outbound_region_name == RegionNames.Wilderness:
                add_rule(entrance, woodcutting_rule_all)

        elif region_row.name == RegionNames.South_Of_Varrock:
            if outbound_region_name == RegionNames.Canoe_Tree:
                add_rule(entrance,
                    lambda state: (state.can_reach_region(RegionNames.Lumbridge, player)
                                   and woodcutting_rule_d1(state)) or
                                  (state.can_reach_region(RegionNames.Barbarian_Village, player)
                                   and woodcutting_rule_d1(state)) or
                                  (state.can_reach_region(RegionNames.Edgeville, player)
                                   and woodcutting_rule_d2(state)) or
                                  (state.can_reach_region(RegionNames.Wilderness, player)
                                   and woodcutting_rule_d3(state)))

            # Access to other chunks based on woodcutting settings
            elif outbound_region_name == RegionNames.Lumbridge:
                add_rule(entrance, woodcutting_rule_d1)
            elif outbound_region_name == RegionNames.Barbarian_Village:
                add_rule(entrance, woodcutting_rule_d1)
            elif outbound_region_name == RegionNames.Edgeville:
                add_rule(entrance, woodcutting_rule_d3)
            elif outbound_region_name == RegionNames.Wilderness:
                add_rule(entrance, woodcutting_rule_all)
        elif region_row.name == RegionNames.Barbarian_Village:
            if outbound_region_name == RegionNames.Canoe_Tree:
                add_rule(entrance,
                    lambda state: (state.can_reach_region(RegionNames.Lumbridge, player)
                                   and woodcutting_rule_d2(state)) or (state.can_reach_region(RegionNames.South_Of_Varrock, player)
                                   and woodcutting_rule_d1(state)) or (state.can_reach_region(RegionNames.Edgeville, player)
                                   and woodcutting_rule_d1(state)) or (state.can_reach_region(RegionNames.Wilderness, player)
                                   and woodcutting_rule_d2(state)))

            # Access to other chunks based on woodcutting settings
            elif outbound_region_name == RegionNames.Lumbridge:
                add_rule(entrance, woodcutting_rule_d2)
            elif outbound_region_name == RegionNames.South_Of_Varrock:
                add_rule(entrance, woodcutting_rule_d1)
            # Edgeville does not need to be checked, because it's already adjacent
            elif outbound_region_name == RegionNames.Wilderness:
                add_rule(entrance, woodcutting_rule_d3)
        elif region_row.name == RegionNames.Edgeville:
            if outbound_region_name == RegionNames.Canoe_Tree:
                add_rule(entrance,
                    lambda state: (state.can_reach_region(RegionNames.Lumbridge, player)
                                   and woodcutting_rule_d3(state)) or
                                  (state.can_reach_region(RegionNames.South_Of_Varrock, player)
                                   and woodcutting_rule_d2(state)) or
                                  (state.can_reach_region(RegionNames.Barbarian_Village, player)
                                   and woodcutting_rule_d1(state)) or
                                  (state.can_reach_region(RegionNames.Wilderness, player)
                                   and woodcutting_rule_d1(state)))

            # Access to other chunks based on woodcutting settings
            elif outbound_region_name == RegionNames.Lumbridge:
                add_rule(entrance, woodcutting_rule_d3)
            elif outbound_region_name == RegionNames.South_Of_Varrock:
                add_rule(entrance, woodcutting_rule_d2)
            # Barbarian Village does not need to be checked, because it's already adjacent
            # Wilderness does not need to be checked, because it's already adjacent
        elif region_row.name == RegionNames.Wilderness:
            if outbound_region_name == RegionNames.Canoe_Tree:
                add_rule(entrance,
                    lambda state: (state.can_reach_region(RegionNames.Lumbridge, player)
                                   and woodcutting_rule_all(state)) or
                                  (state.can_reach_region(RegionNames.South_Of_Varrock, player)
                                   and woodcutting_rule_d3(state)) or
                                  (state.can_reach_region(RegionNames.Barbarian_Village, player)
                                   and woodcutting_rule_d2(state)) or
                                  (state.can_reach_region(RegionNames.Edgeville, player)
                                   and woodcutting_rule_d1(state)))

            # Access to other chunks based on woodcutting settings
            elif outbound_region_name == RegionNames.Lumbridge:
                add_rule(entrance, woodcutting_rule_all)
            elif outbound_region_name == RegionNames.South_Of_Varrock:
                add_rule(entrance, woodcutting_rule_d3)
            elif outbound_region_name == RegionNames.Barbarian_Village:
                add_rule(entrance, woodcutting_rule_d2)
            # Edgeville does not need to be checked, because it's already adjacent