"""
Author: Louis M
Date: Fri, 15 Mar 2024 18:41:40 +0000
Description: Main module for Aquaria game multiworld randomizer
"""

from typing import List, Dict, ClassVar, Any
from worlds.AutoWorld import World, WebWorld
from BaseClasses import Tutorial, MultiWorld, ItemClassification
from .Items import item_table, AquariaItem, ItemType, ItemGroup, ItemNames
from .Locations import location_table, AquariaLocationNames
from .Options import (AquariaOptions, IngredientRandomizer, TurtleRandomizer, EarlyBindSong, EarlyEnergyForm,
                      UnconfineHomeWater, Objective)
from .Regions import AquariaRegions


class AquariaWeb(WebWorld):
    """
    Class used to generate the Aquaria Game Web pages (setup, tutorial, etc.)
    """
    theme = "ocean"

    bug_report_page = "https://github.com/tioui/Aquaria_Randomizer/issues"

    setup = Tutorial(
        "Multiworld Setup Guide",
        "A guide to setting up Aquaria for MultiWorld.",
        "English",
        "setup_en.md",
        "setup/en",
        ["Tioui"]
    )

    setup_fr = Tutorial(
        "Guide de configuration Multimonde",
        "Un guide pour configurer Aquaria MultiWorld",
        "Français",
        "setup_fr.md",
        "setup/fr",
        ["Tioui"]
    )

    tutorials = [setup, setup_fr]
    game_info_languages = ["en", "fr"]


class AquariaWorld(World):
    """
    Aquaria is a side-scrolling action-adventure game. It follows Naija, an
    aquatic humanoid woman, as she explores the underwater world of Aquaria.
    Along her journey, she learns about the history of the world she inhabits
    as well as her own past. The gameplay focuses on a combination of swimming,
    singing, and combat, through which Naija can interact with the world. Her
    songs can move items, affect plants and animals, and change her physical
    appearance into other forms that have different abilities, like firing
    projectiles at hostile creatures, or passing through barriers inaccessible
    to her in her natural form.
    From: https://en.wikipedia.org/wiki/Aquaria_(video_game)
    """

    game: str = "Aquaria"
    "The name of the game"

    topology_present = True
    "show path to required location checks in spoiler"

    web: WebWorld = AquariaWeb()
    "The web page generation informations"

    item_name_to_id: ClassVar[Dict[str, int]] = \
        {name: data.id for name, data in item_table.items()}
    "The name and associated ID of each item of the world"

    item_name_groups = {
        "Damage": {ItemNames.ENERGY_FORM, ItemNames.NATURE_FORM, ItemNames.BEAST_FORM,
                   ItemNames.LI_AND_LI_SONG, ItemNames.BABY_NAUTILUS, ItemNames.BABY_PIRANHA,
                   ItemNames.BABY_BLASTER},
        "Light": {ItemNames.SUN_FORM, ItemNames.BABY_DUMBO}
    }
    """Grouping item make it easier to find them"""

    location_name_to_id = location_table
    "The name and associated ID of each location of the world"

    base_id = 698000
    "The starting ID of the items and locations of the world"

    ingredients_substitution: List[int]
    "Used to randomize ingredient drop"

    options_dataclass = AquariaOptions
    "Used to manage world options"

    options: AquariaOptions
    "Every options of the world"

    regions: AquariaRegions | None
    "Used to manage Regions"

    exclude: List[str]

    def __init__(self, multiworld: MultiWorld, player: int):
        """Initialisation of the Aquaria World"""
        super(AquariaWorld, self).__init__(multiworld, player)
        self.regions = None
        self.ingredients_substitution = []
        self.exclude = []

    def generate_early(self) -> None:
        """
        Run before any general steps of the MultiWorld other than options. Useful for getting and adjusting option
        results and determining layouts for entrance rando etc. start inventory gets pushed after this step.
        """
        self.regions = AquariaRegions(self.multiworld, self.player)

    def create_regions(self) -> None:
        """
        Create every Region in `regions`
        """
        self.regions.add_regions_to_world()
        self.regions.connect_regions()
        self.regions.add_event_locations()

    def create_item(self, name: str) -> AquariaItem:
        """
        Create an AquariaItem using 'name' as item name.
        """
        result: AquariaItem
        data = item_table[name]
        classification: ItemClassification = ItemClassification.useful
        if data.type == ItemType.JUNK:
            classification = ItemClassification.filler
        elif data.type == ItemType.PROGRESSION:
            classification = ItemClassification.progression
        result = AquariaItem(name, classification, data.id, self.player)

        return result

    def __pre_fill_item(self, item_name: str, location_name: str, precollected,
                        itemClassification: ItemClassification = ItemClassification.useful) -> None:
        """Pre-assign an item to a location"""
        if item_name not in precollected:
            self.exclude.append(item_name)
            data = item_table[item_name]
            item = AquariaItem(item_name, itemClassification, data.id, self.player)
            self.multiworld.get_location(location_name, self.player).place_locked_item(item)

    def get_filler_item_name(self):
        """Getting a random ingredient item as filler"""
        ingredients = []
        for name, data in item_table.items():
            if data.group == ItemGroup.INGREDIENT:
                ingredients.append(name)
        filler_item_name = self.random.choice(ingredients)
        return filler_item_name

    def create_items(self) -> None:
        """Create every item in the world"""
        precollected = [item.name for item in self.multiworld.precollected_items[self.player]]
        if self.options.turtle_randomizer.value != TurtleRandomizer.option_none:
            if self.options.turtle_randomizer.value == TurtleRandomizer.option_all_except_final:
                self.__pre_fill_item(ItemNames.TRANSTURTLE_BODY, AquariaLocationNames.FINAL_BOSS_AREA_TRANSTURTLE,
                                     precollected)
        else:
            self.__pre_fill_item(ItemNames.TRANSTURTLE_VEIL_TOP_LEFT,
                                 AquariaLocationNames.THE_VEIL_TOP_LEFT_AREA_TRANSTURTLE, precollected)
            self.__pre_fill_item(ItemNames.TRANSTURTLE_VEIL_TOP_RIGHT,
                                 AquariaLocationNames.THE_VEIL_TOP_RIGHT_AREA_TRANSTURTLE, precollected)
            self.__pre_fill_item(ItemNames.TRANSTURTLE_OPEN_WATERS,
                                 AquariaLocationNames.OPEN_WATERS_TOP_RIGHT_AREA_TRANSTURTLE,
                                 precollected)
            self.__pre_fill_item(ItemNames.TRANSTURTLE_KELP_FOREST,
                                 AquariaLocationNames.KELP_FOREST_BOTTOM_LEFT_AREA_TRANSTURTLE,
                                 precollected)
            self.__pre_fill_item(ItemNames.TRANSTURTLE_HOME_WATERS, AquariaLocationNames.HOME_WATERS_TRANSTURTLE,
                                 precollected)
            self.__pre_fill_item(ItemNames.TRANSTURTLE_ABYSS, AquariaLocationNames.ABYSS_RIGHT_AREA_TRANSTURTLE,
                                 precollected)
            self.__pre_fill_item(ItemNames.TRANSTURTLE_BODY, AquariaLocationNames.FINAL_BOSS_AREA_TRANSTURTLE,
                                 precollected)
            # The last two are inverted because in the original game, they are special turtle that communicate directly
            self.__pre_fill_item(ItemNames.TRANSTURTLE_SIMON_SAYS, AquariaLocationNames.ARNASSI_RUINS_TRANSTURTLE,
                                 precollected, ItemClassification.progression)
            self.__pre_fill_item(ItemNames.TRANSTURTLE_ARNASSI_RUINS, AquariaLocationNames.SIMON_SAYS_AREA_TRANSTURTLE,
                                 precollected)
        for name, data in item_table.items():
            if name not in self.exclude:
                for i in range(data.count):
                    item = self.create_item(name)
                    self.multiworld.itempool.append(item)

    def set_rules(self) -> None:
        """
        Launched when the Multiworld generator is ready to generate rules
        """
        if self.options.early_energy_form == EarlyEnergyForm.option_early:
            self.multiworld.early_items[self.player][ItemNames.ENERGY_FORM] = 1
        elif self.options.early_energy_form == EarlyEnergyForm.option_early_and_local:
            self.multiworld.local_early_items[self.player][ItemNames.ENERGY_FORM] = 1
        if self.options.early_bind_song == EarlyBindSong.option_early:
            self.multiworld.early_items[self.player][ItemNames.BIND_SONG] = 1
        elif self.options.early_bind_song == EarlyBindSong.option_early_and_local:
            self.multiworld.local_early_items[self.player][ItemNames.BIND_SONG] = 1
        self.regions.adjusting_rules(self.options)
        self.multiworld.completion_condition[self.player] = lambda \
                state: state.has(ItemNames.VICTORY, self.player)

    def generate_basic(self) -> None:
        """
        Player-specific randomization that does not affect logic.
        Used to fill then `ingredients_substitution` list
        """
        simple_ingredients_substitution = [i for i in range(27)]
        if self.options.ingredient_randomizer.value > IngredientRandomizer.option_off:
            if self.options.ingredient_randomizer.value == IngredientRandomizer.option_common_ingredients:
                simple_ingredients_substitution.pop(-1)
                simple_ingredients_substitution.pop(-1)
                simple_ingredients_substitution.pop(-1)
            self.random.shuffle(simple_ingredients_substitution)
            if self.options.ingredient_randomizer.value == IngredientRandomizer.option_common_ingredients:
                simple_ingredients_substitution.extend([24, 25, 26])
        dishes_substitution = [i for i in range(27, 76)]
        if self.options.dish_randomizer:
            self.random.shuffle(dishes_substitution)
        self.ingredients_substitution.clear()
        self.ingredients_substitution.extend(simple_ingredients_substitution)
        self.ingredients_substitution.extend(dishes_substitution)

    def fill_slot_data(self) -> Dict[str, Any]:
        return {"ingredientReplacement": self.ingredients_substitution,
                "aquarian_translate": bool(self.options.aquarian_translation.value),
                "blind_goal": bool(self.options.blind_goal.value),
                "secret_needed":
                    self.options.objective.value == Objective.option_obtain_secrets_and_kill_the_creator,
                "minibosses_to_kill": self.options.mini_bosses_to_beat.value,
                "bigbosses_to_kill": self.options.big_bosses_to_beat.value,
                "skip_first_vision": bool(self.options.skip_first_vision.value),
                "unconfine_home_water_energy_door":
                    self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_energy_door
                    or self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_both,
                "unconfine_home_water_transturtle":
                    self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_transturtle
                    or self.options.unconfine_home_water.value == UnconfineHomeWater.option_via_both,
                "bind_song_needed_to_get_under_rock_bulb": bool(self.options.bind_song_needed_to_get_under_rock_bulb),
                "no_progression_hard_or_hidden_locations": bool(self.options.no_progression_hard_or_hidden_locations),
                "light_needed_to_get_to_dark_places": bool(self.options.light_needed_to_get_to_dark_places),
                "turtle_randomizer": self.options.turtle_randomizer.value
                }
