from __future__ import annotations

import logging
from typing import Collection, Callable

from .ability_logic import AbilityLogicMixin
from .action_logic import ActionLogicMixin
from .animal_logic import AnimalLogicMixin
from .arcade_logic import ArcadeLogicMixin
from .artisan_logic import ArtisanLogicMixin
from .base_logic import LogicRegistry
from .book_logic import BookLogicMixin
from .building_logic import BuildingLogicMixin
from .bundle_logic import BundleLogicMixin
from .combat_logic import CombatLogicMixin
from .cooking_logic import CookingLogicMixin
from .crafting_logic import CraftingLogicMixin
from .farming_logic import FarmingLogicMixin
from .fishing_logic import FishingLogicMixin
from .gift_logic import GiftLogicMixin
from .grind_logic import GrindLogicMixin
from .harvesting_logic import HarvestingLogicMixin
from .has_logic import HasLogicMixin
from .logic_event import all_logic_events
from .mine_logic import MineLogicMixin
from .money_logic import MoneyLogicMixin
from .monster_logic import MonsterLogicMixin
from .museum_logic import MuseumLogicMixin
from .pet_logic import PetLogicMixin
from .quality_logic import QualityLogicMixin
from .quest_logic import QuestLogicMixin
from .received_logic import ReceivedLogicMixin
from .region_logic import RegionLogicMixin
from .relationship_logic import RelationshipLogicMixin
from .requirement_logic import RequirementLogicMixin
from .season_logic import SeasonLogicMixin
from .shipping_logic import ShippingLogicMixin
from .skill_logic import SkillLogicMixin
from .source_logic import SourceLogicMixin
from .special_order_logic import SpecialOrderLogicMixin
from .time_logic import TimeLogicMixin
from .tool_logic import ToolLogicMixin
from .traveling_merchant_logic import TravelingMerchantLogicMixin
from .wallet_logic import WalletLogicMixin
from .walnut_logic import WalnutLogicMixin
from ..content.game_content import StardewContent
from ..data.craftable_data import all_crafting_recipes
from ..data.museum_data import all_museum_items
from ..data.recipe_data import all_cooking_recipes
from ..mods.logic.magic_logic import MagicLogicMixin
from ..mods.logic.mod_logic import ModLogicMixin
from ..mods.mod_data import ModNames
from ..options import ExcludeGingerIsland, FestivalLocations, StardewValleyOptions
from ..stardew_rule import False_, True_, StardewRule
from ..strings.animal_names import Animal
from ..strings.animal_product_names import AnimalProduct
from ..strings.ap_names.community_upgrade_names import CommunityUpgrade
from ..strings.artisan_good_names import ArtisanGood
from ..strings.building_names import Building
from ..strings.craftable_names import Consumable, Ring, Fishing, Lighting, WildSeeds
from ..strings.crop_names import Fruit, Vegetable
from ..strings.currency_names import Currency
from ..strings.decoration_names import Decoration
from ..strings.fertilizer_names import Fertilizer, SpeedGro, RetainingSoil
from ..strings.festival_check_names import FestivalCheck
from ..strings.fish_names import Fish, Trash, WaterItem, WaterChest
from ..strings.flower_names import Flower
from ..strings.food_names import Meal, Beverage
from ..strings.forageable_names import Forageable
from ..strings.fruit_tree_names import Sapling
from ..strings.generic_names import Generic
from ..strings.geode_names import Geode
from ..strings.gift_names import Gift
from ..strings.ingredient_names import Ingredient
from ..strings.machine_names import Machine
from ..strings.material_names import Material
from ..strings.metal_names import Ore, MetalBar, Mineral, Fossil, Artifact
from ..strings.monster_drop_names import Loot
from ..strings.monster_names import Monster
from ..strings.region_names import Region, LogicRegion
from ..strings.season_names import Season
from ..strings.seed_names import Seed, TreeSeed
from ..strings.skill_names import Skill
from ..strings.tool_names import Tool, ToolMaterial
from ..strings.villager_names import NPC
from ..strings.wallet_item_names import Wallet

logger = logging.getLogger(__name__)


class StardewLogic(ReceivedLogicMixin, HasLogicMixin, RegionLogicMixin, TravelingMerchantLogicMixin, TimeLogicMixin,
                   SeasonLogicMixin, MoneyLogicMixin, ActionLogicMixin, ArcadeLogicMixin, ArtisanLogicMixin, GiftLogicMixin,
                   BuildingLogicMixin, ShippingLogicMixin, RelationshipLogicMixin, MuseumLogicMixin, WalletLogicMixin, AnimalLogicMixin,
                   CombatLogicMixin, MagicLogicMixin, MonsterLogicMixin, ToolLogicMixin, PetLogicMixin, QualityLogicMixin,
                   SkillLogicMixin, FarmingLogicMixin, BundleLogicMixin, FishingLogicMixin, MineLogicMixin, CookingLogicMixin, AbilityLogicMixin,
                   SpecialOrderLogicMixin, QuestLogicMixin, CraftingLogicMixin, ModLogicMixin, HarvestingLogicMixin, SourceLogicMixin,
                   RequirementLogicMixin, BookLogicMixin, GrindLogicMixin, WalnutLogicMixin):
    player: int
    options: StardewValleyOptions
    content: StardewContent
    regions: Collection[str]

    def __init__(self, player: int, options: StardewValleyOptions, content: StardewContent, regions: Collection[str]):
        self.registry = LogicRegistry()
        super().__init__(player, self.registry, options, content, regions, self)

        self.registry.fish_rules.update({fish.name: self.fishing.can_catch_fish(fish) for fish in content.fishes.values()})
        self.registry.museum_rules.update({donation.item_name: self.museum.can_find_museum_item(donation) for donation in all_museum_items})

        for recipe in all_cooking_recipes:
            if recipe.mod_name and recipe.mod_name not in self.options.mods:
                continue
            can_cook_rule = self.cooking.can_cook(recipe)
            if recipe.meal in self.registry.cooking_rules:
                can_cook_rule = can_cook_rule | self.registry.cooking_rules[recipe.meal]
            self.registry.cooking_rules[recipe.meal] = can_cook_rule

        for recipe in all_crafting_recipes:
            if recipe.mod_name and recipe.mod_name not in self.options.mods:
                continue
            can_craft_rule = self.crafting.can_craft(recipe)
            if recipe.item in self.registry.crafting_rules:
                can_craft_rule = can_craft_rule | self.registry.crafting_rules[recipe.item]
            self.registry.crafting_rules[recipe.item] = can_craft_rule

        self.registry.crop_rules.update({
            Fruit.ancient_fruit: (self.received("Ancient Seeds") | self.received("Ancient Seeds Recipe")) &
                                 self.region.can_reach(Region.greenhouse) & self.has(Machine.seed_maker),
        })

        # @formatter:off
        self.registry.item_rules.update({
            "Energy Tonic": self.money.can_spend_at(Region.hospital, 1000),
            WaterChest.fishing_chest: self.fishing.can_fish_chests(),
            WaterChest.golden_fishing_chest: self.fishing.can_fish_chests() & self.skill.has_mastery(Skill.fishing),
            WaterChest.treasure: self.fishing.can_fish_chests(),
            Ring.hot_java_ring: self.region.can_reach(Region.volcano_floor_10),
            "Galaxy Soul": self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 40),
            "JotPK Big Buff": self.arcade.has_jotpk_power_level(7),
            "JotPK Max Buff": self.arcade.has_jotpk_power_level(9),
            "JotPK Medium Buff": self.arcade.has_jotpk_power_level(4),
            "JotPK Small Buff": self.arcade.has_jotpk_power_level(2),
            "Junimo Kart Big Buff": self.arcade.has_junimo_kart_power_level(6),
            "Junimo Kart Max Buff": self.arcade.has_junimo_kart_power_level(8),
            "Junimo Kart Medium Buff": self.arcade.has_junimo_kart_power_level(4),
            "Junimo Kart Small Buff": self.arcade.has_junimo_kart_power_level(2),
            "Magic Rock Candy": self.region.can_reach(Region.desert) & self.has("Prismatic Shard"),
            "Muscle Remedy": self.money.can_spend_at(Region.hospital, 1000),
            # self.has(Ingredient.vinegar)),
            # self.received("Deluxe Fertilizer Recipe") & self.has(MetalBar.iridium) & self.has(SVItem.sap),
            # | (self.ability.can_cook() & self.relationship.has_hearts(NPC.emily, 3) & self.has(Forageable.leek) & self.has(Forageable.dandelion) &
            # | (self.ability.can_cook() & self.relationship.has_hearts(NPC.jodi, 7) & self.has(AnimalProduct.cow_milk) & self.has(Ingredient.sugar)),
            Animal.chicken: self.animal.can_buy_animal(Animal.chicken),
            Animal.cow: self.animal.can_buy_animal(Animal.cow),
            Animal.dinosaur: self.building.has_building(Building.big_coop) & self.has(AnimalProduct.dinosaur_egg),
            Animal.duck: self.animal.can_buy_animal(Animal.duck),
            Animal.goat: self.animal.can_buy_animal(Animal.goat),
            Animal.ostrich: self.building.has_building(Building.barn) & self.has(AnimalProduct.ostrich_egg) & self.has(Machine.ostrich_incubator),
            Animal.pig: self.animal.can_buy_animal(Animal.pig),
            Animal.rabbit: self.animal.can_buy_animal(Animal.rabbit),
            Animal.sheep: self.animal.can_buy_animal(Animal.sheep),
            AnimalProduct.any_egg: self.has_any(AnimalProduct.chicken_egg, AnimalProduct.duck_egg),
            AnimalProduct.brown_egg: self.animal.has_animal(Animal.chicken),
            AnimalProduct.chicken_egg: self.has_any(AnimalProduct.egg, AnimalProduct.brown_egg, AnimalProduct.large_egg, AnimalProduct.large_brown_egg),
            AnimalProduct.cow_milk: self.has_any(AnimalProduct.milk, AnimalProduct.large_milk),
            AnimalProduct.duck_egg: self.animal.has_animal(Animal.duck),
            AnimalProduct.duck_feather: self.animal.has_happy_animal(Animal.duck),
            AnimalProduct.egg: self.animal.has_animal(Animal.chicken),
            AnimalProduct.goat_milk: self.has(Animal.goat),
            AnimalProduct.golden_egg: self.received(AnimalProduct.golden_egg) & (self.money.can_spend_at(Region.ranch, 100000) | self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 100)),
            AnimalProduct.large_brown_egg: self.animal.has_happy_animal(Animal.chicken),
            AnimalProduct.large_egg: self.animal.has_happy_animal(Animal.chicken),
            AnimalProduct.large_goat_milk: self.animal.has_happy_animal(Animal.goat),
            AnimalProduct.large_milk: self.animal.has_happy_animal(Animal.cow),
            AnimalProduct.milk: self.animal.has_animal(Animal.cow),
            AnimalProduct.ostrich_egg: self.tool.can_forage(Generic.any, Region.island_north, True) & self.has(Forageable.journal_scrap) & self.region.can_reach(Region.volcano_floor_5),
            AnimalProduct.rabbit_foot: self.animal.has_happy_animal(Animal.rabbit),
            AnimalProduct.roe: self.skill.can_fish() & self.building.has_building(Building.fish_pond),
            AnimalProduct.squid_ink: self.mine.can_mine_in_the_mines_floor_81_120() | (self.building.has_building(Building.fish_pond) & self.has(Fish.squid)),
            AnimalProduct.sturgeon_roe: self.has(Fish.sturgeon) & self.building.has_building(Building.fish_pond),
            AnimalProduct.truffle: self.animal.has_animal(Animal.pig) & self.season.has_any_not_winter(),
            AnimalProduct.void_egg: self.money.can_spend_at(Region.sewer, 5000) | (self.building.has_building(Building.fish_pond) & self.has(Fish.void_salmon)),
            AnimalProduct.wool: self.animal.has_animal(Animal.rabbit) | self.animal.has_animal(Animal.sheep),
            AnimalProduct.slime_egg_green: self.has(Machine.slime_egg_press) & self.has(Loot.slime),
            AnimalProduct.slime_egg_blue: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(3),
            AnimalProduct.slime_egg_red: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(6),
            AnimalProduct.slime_egg_purple: self.has(Machine.slime_egg_press) & self.has(Loot.slime) & self.time.has_lived_months(9),
            AnimalProduct.slime_egg_tiger: self.has(Fish.lionfish) & self.building.has_building(Building.fish_pond),
            ArtisanGood.aged_roe: self.artisan.can_preserves_jar(AnimalProduct.roe),
            ArtisanGood.battery_pack: (self.has(Machine.lightning_rod) & self.season.has_any_not_winter()) | self.has(Machine.solar_panel),
            ArtisanGood.caviar: self.artisan.can_preserves_jar(AnimalProduct.sturgeon_roe),
            ArtisanGood.cheese: (self.has(AnimalProduct.cow_milk) & self.has(Machine.cheese_press)) | (self.region.can_reach(Region.desert) & self.has(Mineral.emerald)),
            ArtisanGood.cloth: (self.has(AnimalProduct.wool) & self.has(Machine.loom)) | (self.region.can_reach(Region.desert) & self.has(Mineral.aquamarine)),
            ArtisanGood.dinosaur_mayonnaise: self.artisan.can_mayonnaise(AnimalProduct.dinosaur_egg),
            ArtisanGood.duck_mayonnaise: self.artisan.can_mayonnaise(AnimalProduct.duck_egg),
            ArtisanGood.goat_cheese: self.has(AnimalProduct.goat_milk) & self.has(Machine.cheese_press),
            ArtisanGood.honey: self.money.can_spend_at(Region.oasis, 200) | (self.has(Machine.bee_house) & self.season.has_any_not_winter()),
            ArtisanGood.maple_syrup: self.has(Machine.tapper),
            ArtisanGood.mayonnaise: self.artisan.can_mayonnaise(AnimalProduct.chicken_egg),
            ArtisanGood.mystic_syrup: self.has(Machine.tapper) & self.has(TreeSeed.mystic),
            ArtisanGood.oak_resin: self.has(Machine.tapper),
            ArtisanGood.pine_tar: self.has(Machine.tapper),
            ArtisanGood.smoked_fish: self.artisan.has_smoked_fish(),
            ArtisanGood.targeted_bait: self.artisan.has_targeted_bait(),
            ArtisanGood.stardrop_tea: self.has(WaterChest.golden_fishing_chest),
            ArtisanGood.truffle_oil: self.has(AnimalProduct.truffle) & self.has(Machine.oil_maker),
            ArtisanGood.void_mayonnaise: (self.skill.can_fish(Region.witch_swamp)) | (self.artisan.can_mayonnaise(AnimalProduct.void_egg)),
            Beverage.pina_colada: self.money.can_spend_at(Region.island_resort, 600),
            Beverage.triple_shot_espresso: self.has("Hot Java Ring"),
            Consumable.butterfly_powder: self.money.can_spend_at(Region.sewer, 20000),
            Consumable.far_away_stone: self.region.can_reach(Region.mines_floor_100) & self.has(Artifact.ancient_doll),
            Consumable.fireworks_red: self.region.can_reach(Region.casino),
            Consumable.fireworks_purple: self.region.can_reach(Region.casino),
            Consumable.fireworks_green: self.region.can_reach(Region.casino),
            Consumable.golden_animal_cracker: self.skill.has_mastery(Skill.farming),
            Consumable.mystery_box: self.received(CommunityUpgrade.mr_qi_plane_ride),
            Consumable.gold_mystery_box: self.received(CommunityUpgrade.mr_qi_plane_ride) & self.skill.has_mastery(Skill.foraging),
            Currency.calico_egg: self.region.can_reach(LogicRegion.desert_festival),
            Currency.golden_tag: self.region.can_reach(LogicRegion.trout_derby),
            Currency.prize_ticket: self.time.has_lived_months(2),  # Time to do a few help wanted quests
            Decoration.rotten_plant: self.has(Lighting.jack_o_lantern) & self.season.has(Season.winter),
            Fertilizer.basic: self.money.can_spend_at(Region.pierre_store, 100),
            Fertilizer.quality: self.time.has_year_two & self.money.can_spend_at(Region.pierre_store, 150),
            Fertilizer.tree: self.skill.has_level(Skill.foraging, 7) & self.has(Material.fiber) & self.has(Material.stone),
            Fish.any: self.logic.or_(*(self.fishing.can_catch_fish(fish) for fish in content.fishes.values())),
            Fish.crab: self.skill.can_crab_pot_at(Region.beach),
            Fish.crayfish: self.skill.can_crab_pot_at(Region.town),
            Fish.lobster: self.skill.can_crab_pot_at(Region.beach),
            Fish.mussel: self.tool.can_forage(Generic.any, Region.beach) or self.has(Fish.mussel_node),
            Fish.mussel_node: self.region.can_reach(Region.island_west),
            Fish.oyster: self.tool.can_forage(Generic.any, Region.beach),
            Fish.periwinkle: self.skill.can_crab_pot_at(Region.town),
            Fish.shrimp: self.skill.can_crab_pot_at(Region.beach),
            Fish.snail: self.skill.can_crab_pot_at(Region.town),
            Fishing.curiosity_lure: self.monster.can_kill(self.monster.all_monsters_by_name[Monster.mummy]),
            Fishing.lead_bobber: self.skill.has_level(Skill.fishing, 6) & self.money.can_spend_at(Region.fish_shop, 200),
            Forageable.hay: self.building.has_building(Building.silo) & self.tool.has_tool(Tool.scythe), #
            Forageable.journal_scrap: self.region.can_reach_all((Region.island_west, Region.island_north, Region.island_south, Region.volcano_floor_10)) & (self.ability.can_chop_trees() | self.mine.can_mine_in_the_mines_floor_1_40()),#
            Forageable.secret_note: self.quest.has_magnifying_glass() & (self.ability.can_chop_trees() | self.mine.can_mine_in_the_mines_floor_1_40()), #
            Fossil.bone_fragment: (self.region.can_reach(Region.dig_site) & self.tool.has_tool(Tool.pickaxe)) | self.monster.can_kill(Monster.skeleton),
            Fossil.fossilized_leg: self.region.can_reach(Region.dig_site) & self.tool.has_tool(Tool.pickaxe),
            Fossil.fossilized_ribs: self.region.can_reach(Region.island_south) & self.tool.has_tool(Tool.hoe),
            Fossil.fossilized_skull: self.action.can_open_geode(Geode.golden_coconut),
            Fossil.fossilized_spine: self.skill.can_fish(Region.dig_site),
            Fossil.fossilized_tail: self.action.can_pan_at(Region.dig_site, ToolMaterial.copper),
            Fossil.mummified_bat: self.region.can_reach(Region.volcano_floor_10),
            Fossil.mummified_frog: self.region.can_reach(Region.island_east) & self.tool.has_tool(Tool.scythe),
            Fossil.snake_skull: self.region.can_reach(Region.dig_site) & self.tool.has_tool(Tool.hoe),
            Fossil.snake_vertebrae: self.region.can_reach(Region.island_west) & self.tool.has_tool(Tool.hoe),
            Geode.artifact_trove: self.has(Geode.omni) & self.region.can_reach(Region.desert),
            Geode.frozen: self.mine.can_mine_in_the_mines_floor_41_80(),
            Geode.geode: self.mine.can_mine_in_the_mines_floor_1_40(),
            Geode.golden_coconut: self.region.can_reach(Region.island_north),
            Geode.magma: self.mine.can_mine_in_the_mines_floor_81_120() | (self.has(Fish.lava_eel) & self.building.has_building(Building.fish_pond)),
            Geode.omni: self.mine.can_mine_in_the_mines_floor_41_80() | self.region.can_reach(Region.desert) | self.tool.has_tool(Tool.pan, ToolMaterial.iron) | self.received(Wallet.rusty_key) | (self.has(Fish.octopus) & self.building.has_building(Building.fish_pond)) | self.region.can_reach(Region.volcano_floor_10),
            Gift.bouquet: self.relationship.has_hearts_with_any_bachelor(8) & self.money.can_spend_at(Region.pierre_store, 100),
            Gift.golden_pumpkin: self.season.has(Season.fall) | self.action.can_open_geode(Geode.artifact_trove),
            Gift.mermaid_pendant: self.region.can_reach(Region.tide_pools) & self.relationship.has_hearts_with_any_bachelor(10) & self.building.has_house(1) & self.has(Consumable.rain_totem),
            Gift.movie_ticket: self.money.can_spend_at(Region.movie_ticket_stand, 1000),
            Gift.pearl: (self.has(Fish.blobfish) & self.building.has_building(Building.fish_pond)) | self.action.can_open_geode(Geode.artifact_trove),
            Gift.tea_set: self.season.has(Season.winter) & self.time.has_lived_max_months,
            Gift.void_ghost_pendant: self.money.can_trade_at(Region.desert, Loot.void_essence, 200) & self.relationship.has_hearts(NPC.krobus, 10),
            Gift.wilted_bouquet: self.has(Machine.furnace) & self.has(Gift.bouquet) & self.has(Material.coal),
            Ingredient.oil: self.money.can_spend_at(Region.pierre_store, 200) | (self.has(Machine.oil_maker) & (self.has(Vegetable.corn) | self.has(Flower.sunflower) | self.has(Seed.sunflower))),
            Ingredient.qi_seasoning: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 10),
            Ingredient.rice: self.money.can_spend_at(Region.pierre_store, 200) | (self.building.has_building(Building.mill) & self.has(Vegetable.unmilled_rice)),
            Ingredient.sugar: self.money.can_spend_at(Region.pierre_store, 100) | (self.building.has_building(Building.mill) & self.has(Vegetable.beet)),
            Ingredient.vinegar: self.money.can_spend_at(Region.pierre_store, 200) | self.artisan.can_keg(Ingredient.rice),
            Ingredient.wheat_flour: self.money.can_spend_at(Region.pierre_store, 100) | (self.building.has_building(Building.mill) & self.has(Vegetable.wheat)),
            Loot.bat_wing: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern(),
            Loot.bug_meat: self.mine.can_mine_in_the_mines_floor_1_40(),
            Loot.slime: self.mine.can_mine_in_the_mines_floor_1_40(),
            Loot.solar_essence: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern(),
            Loot.void_essence: self.mine.can_mine_in_the_mines_floor_81_120() | self.mine.can_mine_in_the_skull_cavern(),
            Machine.coffee_maker: self.received(Machine.coffee_maker),
            Machine.crab_pot: self.skill.has_level(Skill.fishing, 3) & self.money.can_spend_at(Region.fish_shop, 1500),
            Machine.enricher: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 20),
            Machine.pressure_nozzle: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 20),
            Material.cinder_shard: self.region.can_reach(Region.volcano_floor_5),
            Material.clay: self.region.can_reach_any((Region.farm, Region.beach, Region.quarry)) & self.tool.has_tool(Tool.hoe),
            Material.coal: self.mine.can_mine_in_the_mines_floor_41_80() | self.tool.has_tool(Tool.pan),
            Material.fiber: True_(),
            Material.hardwood: self.tool.has_tool(Tool.axe, ToolMaterial.copper) & (self.region.can_reach(Region.secret_woods) | self.region.can_reach(Region.island_west)),
            Material.moss: True_(),
            Material.sap: self.ability.can_chop_trees(),
            Material.stone: self.tool.has_tool(Tool.pickaxe),
            Material.wood: self.tool.has_tool(Tool.axe),
            Meal.ice_cream: (self.season.has(Season.summer) & self.money.can_spend_at(Region.town, 250)) | self.money.can_spend_at(Region.oasis, 240),
            Meal.strange_bun: self.relationship.has_hearts(NPC.shane, 7) & self.has(Ingredient.wheat_flour) & self.has(Fish.periwinkle) & self.has(ArtisanGood.void_mayonnaise),
            MetalBar.copper: self.can_smelt(Ore.copper),
            MetalBar.gold: self.can_smelt(Ore.gold),
            MetalBar.iridium: self.can_smelt(Ore.iridium),
            MetalBar.iron: self.can_smelt(Ore.iron),
            MetalBar.quartz: self.can_smelt(Mineral.quartz) | self.can_smelt("Fire Quartz") | (self.has(Machine.recycling_machine) & (self.has(Trash.broken_cd) | self.has(Trash.broken_glasses))),
            MetalBar.radioactive: self.can_smelt(Ore.radioactive),
            Ore.copper: self.mine.can_mine_in_the_mines_floor_1_40() | self.mine.can_mine_in_the_skull_cavern() | self.tool.has_tool(Tool.pan, ToolMaterial.copper),
            Ore.gold: self.mine.can_mine_in_the_mines_floor_81_120() | self.mine.can_mine_in_the_skull_cavern() | self.tool.has_tool(Tool.pan, ToolMaterial.iron),
            Ore.iridium: self.mine.can_mine_in_the_skull_cavern() | self.can_fish_pond(Fish.super_cucumber) | self.tool.has_tool(Tool.pan, ToolMaterial.gold),
            Ore.iron: self.mine.can_mine_in_the_mines_floor_41_80() | self.mine.can_mine_in_the_skull_cavern() | self.tool.has_tool(Tool.pan, ToolMaterial.copper),
            Ore.radioactive: self.ability.can_mine_perfectly() & self.region.can_reach(Region.qi_walnut_room),
            RetainingSoil.basic: self.money.can_spend_at(Region.pierre_store, 100),
            RetainingSoil.quality: self.time.has_year_two & self.money.can_spend_at(Region.pierre_store, 150),
            Sapling.tea: self.relationship.has_hearts(NPC.caroline, 2) & self.has(Material.fiber) & self.has(Material.wood),
            SpeedGro.basic: self.money.can_spend_at(Region.pierre_store, 100),
            SpeedGro.deluxe: self.time.has_year_two & self.money.can_spend_at(Region.pierre_store, 150),
            Trash.broken_cd: self.skill.can_crab_pot,
            Trash.broken_glasses: self.skill.can_crab_pot,
            Trash.driftwood: self.skill.can_crab_pot,
            Trash.joja_cola: self.money.can_spend_at(Region.saloon, 75),
            Trash.soggy_newspaper: self.skill.can_crab_pot,
            Trash.trash: self.skill.can_crab_pot,
            TreeSeed.acorn: self.skill.has_level(Skill.foraging, 1) & self.ability.can_chop_trees(),
            TreeSeed.mahogany: self.region.can_reach(Region.secret_woods) & self.tool.has_tool(Tool.axe, ToolMaterial.iron) & self.skill.has_level(Skill.foraging, 1),
            TreeSeed.maple: self.skill.has_level(Skill.foraging, 1) & self.ability.can_chop_trees(),
            TreeSeed.mushroom: self.money.can_trade_at(Region.qi_walnut_room, Currency.qi_gem, 5),
            TreeSeed.pine: self.skill.has_level(Skill.foraging, 1) & self.ability.can_chop_trees(),
            TreeSeed.mossy: self.ability.can_chop_trees() & self.season.has(Season.summer),
            Fish.clam: self.tool.can_forage(Generic.any, Region.beach),
            Fish.cockle: self.tool.can_forage(Generic.any, Region.beach),
            WaterItem.green_algae: self.fishing.can_fish_in_freshwater(),
            WaterItem.cave_jelly: self.fishing.can_fish_at(Region.mines_floor_100) & self.tool.has_fishing_rod(2),
            WaterItem.river_jelly: self.fishing.can_fish_at(Region.town) & self.tool.has_fishing_rod(2),
            WaterItem.sea_jelly: self.fishing.can_fish_at(Region.beach) & self.tool.has_fishing_rod(2),
            WaterItem.seaweed: self.skill.can_fish(Region.tide_pools),
            WaterItem.white_algae: self.skill.can_fish(Region.mines_floor_20),
            WildSeeds.grass_starter: self.money.can_spend_at(Region.pierre_store, 100),
        })
        # @formatter:on

        content_rules = {
            item_name: self.source.has_access_to_item(game_item)
            for item_name, game_item in self.content.game_items.items()
        }

        for item in set(content_rules.keys()).intersection(self.registry.item_rules.keys()):
            logger.warning(f"Rule for {item} already exists in the registry, overwriting it.")

        self.registry.item_rules.update(content_rules)
        self.registry.item_rules.update(self.registry.fish_rules)
        self.registry.item_rules.update(self.registry.museum_rules)
        self.registry.item_rules.update(self.registry.crop_rules)
        self.artisan.initialize_rules()
        self.registry.item_rules.update(self.registry.artisan_good_rules)

        self.registry.item_rules.update(self.mod.item.get_modded_item_rules())
        self.mod.item.modify_vanilla_item_rules_with_mod_additions(self.registry.item_rules)  # New regions and content means new ways to obtain old items

        # For some recipes, the cooked item can be obtained directly, so we either cook it or get it
        for recipe in self.registry.cooking_rules:
            cooking_rule = self.registry.cooking_rules[recipe]
            obtention_rule = self.registry.item_rules[recipe] if recipe in self.registry.item_rules else False_()
            self.registry.item_rules[recipe] = obtention_rule | cooking_rule

        # For some recipes, the crafted item can be obtained directly, so we either craft it or get it
        for recipe in self.registry.crafting_rules:
            crafting_rule = self.registry.crafting_rules[recipe]
            obtention_rule = self.registry.item_rules[recipe] if recipe in self.registry.item_rules else False_()
            self.registry.item_rules[recipe] = obtention_rule | crafting_rule

        self.building.initialize_rules()
        self.building.update_rules(self.mod.building.get_modded_building_rules())

        self.quest.initialize_rules()
        self.quest.update_rules(self.mod.quest.get_modded_quest_rules())

        self.registry.festival_rules.update({
            FestivalCheck.egg_hunt: self.can_win_egg_hunt(),
            FestivalCheck.strawberry_seeds: self.money.can_spend(1000),
            FestivalCheck.dance: self.relationship.has_hearts_with_any_bachelor(4),
            FestivalCheck.tub_o_flowers: self.money.can_spend(2000),
            FestivalCheck.rarecrow_5: self.money.can_spend(2500),
            FestivalCheck.luau_soup: self.can_succeed_luau_soup(),
            FestivalCheck.moonlight_jellies: True_(),
            FestivalCheck.moonlight_jellies_banner: self.money.can_spend(800),
            FestivalCheck.starport_decal: self.money.can_spend(1000),
            FestivalCheck.smashing_stone: True_(),
            FestivalCheck.grange_display: self.can_succeed_grange_display(),
            FestivalCheck.rarecrow_1: True_(),  # only cost star tokens
            FestivalCheck.fair_stardrop: True_(),  # only cost star tokens
            FestivalCheck.spirit_eve_maze: True_(),
            FestivalCheck.jack_o_lantern: self.money.can_spend(2000),
            FestivalCheck.rarecrow_2: self.money.can_spend(5000),
            FestivalCheck.fishing_competition: self.can_win_fishing_competition(),
            FestivalCheck.rarecrow_4: self.money.can_spend(5000),
            FestivalCheck.mermaid_pearl: self.has(Forageable.secret_note),
            FestivalCheck.cone_hat: self.money.can_spend(2500),
            FestivalCheck.iridium_fireplace: self.money.can_spend(15000),
            FestivalCheck.rarecrow_7: self.money.can_spend(5000) & self.museum.can_donate_museum_artifacts(20),
            FestivalCheck.rarecrow_8: self.money.can_spend(5000) & self.museum.can_donate_museum_items(40),
            FestivalCheck.lupini_red_eagle: self.money.can_spend(1200),
            FestivalCheck.lupini_portrait_mermaid: self.money.can_spend(1200),
            FestivalCheck.lupini_solar_kingdom: self.money.can_spend(1200),
            FestivalCheck.lupini_clouds: self.time.has_year_two & self.money.can_spend(1200),
            FestivalCheck.lupini_1000_years: self.time.has_year_two & self.money.can_spend(1200),
            FestivalCheck.lupini_three_trees: self.time.has_year_two & self.money.can_spend(1200),
            FestivalCheck.lupini_the_serpent: self.time.has_year_three & self.money.can_spend(1200),
            FestivalCheck.lupini_tropical_fish: self.time.has_year_three & self.money.can_spend(1200),
            FestivalCheck.lupini_land_of_clay: self.time.has_year_three & self.money.can_spend(1200),
            FestivalCheck.secret_santa: self.gifts.has_any_universal_love,
            FestivalCheck.legend_of_the_winter_star: True_(),
            FestivalCheck.rarecrow_3: True_(),
            FestivalCheck.all_rarecrows: self.region.can_reach(Region.farm) & self.has_all_rarecrows(),
            FestivalCheck.calico_race: True_(),
            FestivalCheck.mummy_mask: True_(),
            FestivalCheck.calico_statue: True_(),
            FestivalCheck.emily_outfit_service: True_(),
            FestivalCheck.earthy_mousse: True_(),
            FestivalCheck.sweet_bean_cake: True_(),
            FestivalCheck.skull_cave_casserole: True_(),
            FestivalCheck.spicy_tacos: True_(),
            FestivalCheck.mountain_chili: True_(),
            FestivalCheck.crystal_cake: True_(),
            FestivalCheck.cave_kebab: True_(),
            FestivalCheck.hot_log: True_(),
            FestivalCheck.sour_salad: True_(),
            FestivalCheck.superfood_cake: True_(),
            FestivalCheck.warrior_smoothie: True_(),
            FestivalCheck.rumpled_fruit_skin: True_(),
            FestivalCheck.calico_pizza: True_(),
            FestivalCheck.stuffed_mushrooms: True_(),
            FestivalCheck.elf_quesadilla: True_(),
            FestivalCheck.nachos_of_the_desert: True_(),
            FestivalCheck.cloppino: True_(),
            FestivalCheck.rainforest_shrimp: True_(),
            FestivalCheck.shrimp_donut: True_(),
            FestivalCheck.smell_of_the_sea: True_(),
            FestivalCheck.desert_gumbo: True_(),
            FestivalCheck.free_cactis: True_(),
            FestivalCheck.monster_hunt: self.monster.can_kill(Monster.serpent),
            FestivalCheck.deep_dive: self.region.can_reach(Region.skull_cavern_50),
            FestivalCheck.treasure_hunt: self.region.can_reach(Region.skull_cavern_25),
            FestivalCheck.touch_calico_statue: self.region.can_reach(Region.skull_cavern_25),
            FestivalCheck.real_calico_egg_hunter: self.region.can_reach(Region.skull_cavern_100),
            FestivalCheck.willy_challenge: self.fishing.can_catch_fish(content.fishes[Fish.scorpion_carp]),
            FestivalCheck.desert_scholar: True_(),
            FestivalCheck.squidfest_day_1_copper: self.fishing.can_catch_fish(content.fishes[Fish.squid]),
            FestivalCheck.squidfest_day_1_iron: self.fishing.can_catch_fish(content.fishes[Fish.squid]) & self.has(Fishing.bait),
            FestivalCheck.squidfest_day_1_gold: self.fishing.can_catch_fish(content.fishes[Fish.squid]) & self.has(Fishing.deluxe_bait),
            FestivalCheck.squidfest_day_1_iridium: self.fishing.can_catch_fish(content.fishes[Fish.squid]) &
                                                   self.fishing.has_specific_bait(content.fishes[Fish.squid]),
            FestivalCheck.squidfest_day_2_copper: self.fishing.can_catch_fish(content.fishes[Fish.squid]),
            FestivalCheck.squidfest_day_2_iron: self.fishing.can_catch_fish(content.fishes[Fish.squid]) & self.has(Fishing.bait),
            FestivalCheck.squidfest_day_2_gold: self.fishing.can_catch_fish(content.fishes[Fish.squid]) & self.has(Fishing.deluxe_bait),
            FestivalCheck.squidfest_day_2_iridium: self.fishing.can_catch_fish(content.fishes[Fish.squid]) &
                                                   self.fishing.has_specific_bait(content.fishes[Fish.squid]),
        })
        for i in range(1, 11):
            self.registry.festival_rules[f"{FestivalCheck.trout_derby_reward_pattern}{i}"] = self.fishing.can_catch_fish(content.fishes[Fish.rainbow_trout])

        self.special_order.initialize_rules()
        self.special_order.update_rules(self.mod.special_order.get_modded_special_orders_rules())

    def setup_events(self, register_event: Callable[[str, str, StardewRule], None]) -> None:
        for logic_event in all_logic_events:
            rule = self.registry.item_rules[logic_event.item]
            register_event(logic_event.name, logic_event.region, rule)
            self.registry.item_rules[logic_event.item] = self.received(logic_event.name)

    def can_smelt(self, item: str) -> StardewRule:
        return self.has(Machine.furnace) & self.has(item)

    def can_finish_grandpa_evaluation(self) -> StardewRule:
        # https://stardewvalleywiki.com/Grandpa
        rules_worth_a_point = [
            self.money.can_have_earned_total(50000),  # 50 000g
            self.money.can_have_earned_total(100000),  # 100 000g
            self.money.can_have_earned_total(200000),  # 200 000g
            self.money.can_have_earned_total(300000),  # 300 000g
            self.money.can_have_earned_total(500000),  # 500 000g
            self.money.can_have_earned_total(1000000),  # 1 000 000g first point
            self.money.can_have_earned_total(1000000),  # 1 000 000g second point
            self.skill.has_total_level(30),  # Total Skills: 30
            self.skill.has_total_level(50),  # Total Skills: 50
            self.museum.can_complete_museum(),  # Completing the museum for a point
            # Catching every fish not expected
            # Shipping every item not expected
            self.relationship.can_get_married() & self.building.has_house(2),
            self.relationship.has_hearts_with_n(5, 8),  # 5 Friends
            self.relationship.has_hearts_with_n(10, 8),  # 10 friends
            self.pet.has_pet_hearts(5),  # Max Pet
            self.bundle.can_complete_community_center,  # Community Center Completion
            self.bundle.can_complete_community_center,  # CC Ceremony first point
            self.bundle.can_complete_community_center,  # CC Ceremony second point
            self.received(Wallet.skull_key),  # Skull Key obtained
            self.wallet.has_rusty_key(),  # Rusty key obtained
        ]
        return self.count(12, *rules_worth_a_point)

    def can_win_egg_hunt(self) -> StardewRule:
        return True_()

    def can_succeed_luau_soup(self) -> StardewRule:
        if self.options.festival_locations != FestivalLocations.option_hard:
            return True_()
        eligible_fish = (Fish.blobfish, Fish.crimsonfish, Fish.ice_pip, Fish.lava_eel, Fish.legend, Fish.angler, Fish.catfish, Fish.glacierfish,
                         Fish.mutant_carp, Fish.spookfish, Fish.stingray, Fish.sturgeon, Fish.super_cucumber)
        fish_rule = self.has_any(*(f for f in eligible_fish if f in self.content.fishes))  # To filter stingray
        eligible_kegables = (Fruit.ancient_fruit, Fruit.apple, Fruit.banana, Forageable.coconut, Forageable.crystal_fruit, Fruit.mango, Fruit.melon,
                             Fruit.orange, Fruit.peach, Fruit.pineapple, Fruit.pomegranate, Fruit.rhubarb, Fruit.starfruit, Fruit.strawberry,
                             Forageable.cactus_fruit, Fruit.cherry, Fruit.cranberries, Fruit.grape, Forageable.spice_berry, Forageable.wild_plum,
                             Vegetable.hops, Vegetable.wheat)
        keg_rules = [self.artisan.can_keg(kegable) for kegable in eligible_kegables if kegable in self.content.game_items]
        aged_rule = self.has(Machine.cask) & self.logic.or_(*keg_rules)
        # There are a few other valid items, but I don't feel like coding them all
        return fish_rule | aged_rule

    def can_succeed_grange_display(self) -> StardewRule:
        if self.options.festival_locations != FestivalLocations.option_hard:
            return True_()

        animal_rule = self.animal.has_animal(Generic.any)
        artisan_rule = self.artisan.can_keg(Generic.any) | self.artisan.can_preserves_jar(Generic.any)
        cooking_rule = self.money.can_spend_at(Region.saloon, 220)  # Salads at the bar are good enough
        fish_rule = self.skill.can_fish(difficulty=50)
        forage_rule = self.region.can_reach_any((Region.forest, Region.backwoods))  # Hazelnut always available since the grange display is in fall
        mineral_rule = self.action.can_open_geode(Generic.any)  # More than half the minerals are good enough
        good_fruits = (fruit
                       for fruit in
                       (Fruit.apple, Fruit.banana, Forageable.coconut, Forageable.crystal_fruit, Fruit.mango, Fruit.orange, Fruit.peach, Fruit.pomegranate,
                        Fruit.strawberry, Fruit.melon, Fruit.rhubarb, Fruit.pineapple, Fruit.ancient_fruit, Fruit.starfruit)
                       if fruit in self.content.game_items)
        fruit_rule = self.has_any(*good_fruits)
        good_vegetables = (vegeteable
                           for vegeteable in
                           (Vegetable.amaranth, Vegetable.artichoke, Vegetable.beet, Vegetable.cauliflower, Forageable.fiddlehead_fern, Vegetable.kale,
                            Vegetable.radish, Vegetable.taro_root, Vegetable.yam, Vegetable.red_cabbage, Vegetable.pumpkin)
                           if vegeteable in self.content.game_items)
        vegetable_rule = self.has_any(*good_vegetables)

        return animal_rule & artisan_rule & cooking_rule & fish_rule & \
            forage_rule & fruit_rule & mineral_rule & vegetable_rule

    def can_win_fishing_competition(self) -> StardewRule:
        return self.skill.can_fish(difficulty=60)

    def has_island_trader(self) -> StardewRule:
        if self.options.exclude_ginger_island == ExcludeGingerIsland.option_true:
            return False_()
        return self.region.can_reach(Region.island_trader)

    def has_all_stardrops(self) -> StardewRule:
        other_rules = []
        number_of_stardrops_to_receive = 0
        number_of_stardrops_to_receive += 1  # The Mines level 100
        number_of_stardrops_to_receive += 1  # Old Master Cannoli
        number_of_stardrops_to_receive += 1  # Museum Stardrop
        number_of_stardrops_to_receive += 1  # Krobus Stardrop

        # Master Angler Stardrop
        if self.content.features.fishsanity.is_enabled:
            number_of_stardrops_to_receive += 1
        else:
            other_rules.append(self.fishing.can_catch_every_fish())

        if self.options.festival_locations == FestivalLocations.option_disabled:  # Fair Stardrop
            other_rules.append(self.season.has(Season.fall))
        else:
            number_of_stardrops_to_receive += 1

        # Spouse Stardrop
        if self.content.features.friendsanity.is_enabled:
            number_of_stardrops_to_receive += 1
        else:
            other_rules.append(self.relationship.has_hearts_with_any_bachelor(13))

        if ModNames.deepwoods in self.options.mods:  # Petting the Unicorn
            number_of_stardrops_to_receive += 1

        if not other_rules:
            return self.received("Stardrop", number_of_stardrops_to_receive)

        return self.received("Stardrop", number_of_stardrops_to_receive) & self.logic.and_(*other_rules)

    def has_all_rarecrows(self) -> StardewRule:
        rules = []
        for rarecrow_number in range(1, 9):
            rules.append(self.received(f"Rarecrow #{rarecrow_number}"))
        return self.logic.and_(*rules)

    def has_abandoned_jojamart(self) -> StardewRule:
        return self.received(CommunityUpgrade.movie_theater, 1)

    def has_movie_theater(self) -> StardewRule:
        return self.received(CommunityUpgrade.movie_theater, 2)

    def can_use_obelisk(self, obelisk: str) -> StardewRule:
        return self.region.can_reach(Region.farm) & self.received(obelisk)

    def can_fish_pond(self, fish: str) -> StardewRule:
        return self.building.has_building(Building.fish_pond) & self.has(fish)
