import typing

from BaseClasses import MultiWorld
from Options import OptionError
from worlds.AutoWorld import World

from .Names import LocationName
from .Options import GateBossPlando


speed_characters_1 = "sonic vs shadow 1"
speed_characters_2 = "sonic vs shadow 2"
mech_characters_1 = "tails vs eggman 1"
mech_characters_2 = "tails vs eggman 2"
hunt_characters_1 = "knuckles vs rouge 1"
big_foot = "big foot"
hot_shot = "hot shot"
flying_dog = "flying dog"
egg_golem_sonic = "egg golem (sonic)"
egg_golem_eggman = "egg golem (eggman)"
king_boom_boo = "king boom boo"

gate_bosses_no_requirements_table = {
    speed_characters_1: 0,
    speed_characters_2: 1,
    mech_characters_1: 2,
    mech_characters_2: 3,
    hunt_characters_1: 4,
    big_foot: 5,
    hot_shot: 6,
    flying_dog: 7,
    egg_golem_sonic: 8,
    egg_golem_eggman: 9,
}

gate_bosses_with_requirements_table = {
    king_boom_boo: 10,
}

extra_boss_rush_bosses_table = {
    speed_characters_1: 11,
    speed_characters_2: 12,
    mech_characters_1: 13,
    mech_characters_2: 14,
    hunt_characters_1: 15,
}

all_gate_bosses_table = {
    **gate_bosses_no_requirements_table,
    **gate_bosses_with_requirements_table,
}


boss_id_to_name = {
    0: "Sonic vs Shadow 1",
    1: "Sonic vs Shadow 2",
    2: "Tails vs Eggman 1",
    3: "Tails vs Eggman 2",
    4: "Knuckles vs Rouge 1",
    5: "F-6t BIG FOOT",
    6: "B-3x HOT SHOT",
    7: "R-1/A FLYING DOG",
    8: "Egg Golem (Sonic)",
    9: "Egg Golem (Eggman)",
    10: "King Boom Boo",
    11: "Sonic vs Shadow 1",
    12: "Sonic vs Shadow 2",
    13: "Tails vs Eggman 1",
    14: "Tails vs Eggman 2",
    15: "Knuckles vs Rouge 1",
}

def get_boss_name(boss: int):
    return boss_id_to_name[boss]


def boss_has_requirement(boss: int):
    return boss >= len(gate_bosses_no_requirements_table)


def get_gate_bosses(world: World):
    selected_bosses: typing.List[int] = []
    boss_gates: typing.List[int] = []
    available_bosses: typing.List[str] = list(gate_bosses_no_requirements_table.keys())
    world.random.shuffle(available_bosses)

    gate_boss_plando: typing.Union[int, str] = world.options.gate_boss_plando.value
    plando_bosses = ["None", "None", "None", "None", "None"]
    if isinstance(gate_boss_plando, str):
        # boss plando
        options = gate_boss_plando.split(";")
        gate_boss_plando = GateBossPlando.options[options.pop()]
        for option in options:
            if "-" in option:
                loc, boss = option.split("-")
                boss_num = LocationName.boss_gate_names[loc]

                if boss_num >= world.options.number_of_level_gates.value:
                    # Don't reject bosses plando'd into gate bosses that won't exist
                    pass

                if boss in plando_bosses:
                    raise OptionError(f"Invalid input for option `plando_bosses`: "
                                      f"No Duplicate Bosses permitted ({boss}) - for "
                                      f"{world.player_name}")

                plando_bosses[boss_num] = boss

                if boss in available_bosses:
                    available_bosses.remove(boss)

    for x in range(world.options.number_of_level_gates):
        if (10 not in selected_bosses) and (king_boom_boo not in available_bosses) and ((x + 1) / world.options.number_of_level_gates) > 0.5:
            available_bosses.extend(gate_bosses_with_requirements_table.keys())
            world.random.shuffle(available_bosses)

        chosen_boss = available_bosses[0]
        if plando_bosses[x] != "None":
            if plando_bosses[x] not in available_bosses:
                available_bosses.append(plando_bosses[x])
            chosen_boss = plando_bosses[x]

        selected_bosses.append(all_gate_bosses_table[chosen_boss])
        boss_gates.append(x + 1)
        available_bosses.remove(chosen_boss)

    bosses: typing.Dict[int, int] = dict(zip(boss_gates, selected_bosses))

    return bosses


def get_boss_rush_bosses(world: World):

    if world.options.boss_rush_shuffle == 0:
        boss_list_o = list(range(0, 16))
        boss_list_s = [5, 2, 0, 10, 8, 4, 3, 1, 6, 13, 7, 11, 9, 15, 14, 12]

        return dict(zip(boss_list_o, boss_list_s))
    elif world.options.boss_rush_shuffle == 1:
        boss_list_o = list(range(0, 16))
        boss_list_s = boss_list_o.copy()
        world.random.shuffle(boss_list_s)

        return dict(zip(boss_list_o, boss_list_s))
    elif world.options.boss_rush_shuffle == 2:
        boss_list_o = list(range(0, 16))
        boss_list_s = [world.random.choice(boss_list_o) for i in range(0, 16)]
        if 10 not in boss_list_s:
            boss_list_s[world.random.randint(0, 15)] = 10

        return dict(zip(boss_list_o, boss_list_s))
    elif world.options.boss_rush_shuffle == 3:
        boss_list_o = list(range(0, 16))
        boss_list_s = [world.random.choice(boss_list_o)] * len(boss_list_o)
        if 10 not in boss_list_s:
            boss_list_s[world.random.randint(0, 15)] = 10

        return dict(zip(boss_list_o, boss_list_s))
    else:
        return dict()
