import random
from dataclasses import dataclass

from Options import DeathLink, Choice, Toggle, OptionDict, Range, PlandoBosses, DefaultOnToggle, \
    PerGameCommonOptions, PlandoConnections
from .Names import LocationName


class KDL3PlandoConnections(PlandoConnections):
    entrances = exits = {f"{i} {j}" for i in LocationName.level_names for j in range(1, 7)}


class Goal(Choice):
    """
    Zero: collect the Heart Stars, and defeat Zero in the Hyper Zone.
    Boss Butch: collect the Heart Stars, and then complete the boss rematches in the Boss Butch mode.
    MG5: collect the Heart Stars, and then complete a perfect run through the minigame gauntlet within the Super MG5
    Jumping: collect the Heart Stars, and then reach a designated score within the Jumping sub-game
    """
    display_name = "Goal"
    option_zero = 0
    option_boss_butch = 1
    option_MG5 = 2
    option_jumping = 3
    default = 0

    @classmethod
    def get_option_name(cls, value: int) -> str:
        if value == 2:
            return cls.name_lookup[value].upper()
        return super().get_option_name(value)

class GoalSpeed(Choice):
    """
    Normal: the goal is unlocked after purifying the five bosses
    Fast: the goal is unlocked after acquiring the target number of Heart Stars
    """
    display_name = "Goal Speed"
    option_normal = 0
    option_fast = 1


class TotalHeartStars(Range):
    """
    Maximum number of heart stars to include in the pool of items.
    """
    display_name = "Max Heart Stars"
    range_start = 5  # set to 5 so strict bosses does not degrade
    range_end = 50  # 30 default locations + 30 stage clears + 5 bosses - 14 progression items = 51, so round down
    default = 30


class HeartStarsRequired(Range):
    """
    Percentage of heart stars required to purify the five bosses and reach Zero.
    Each boss will require a differing amount of heart stars to purify.
    """
    display_name = "Required Heart Stars"
    range_start = 1
    range_end = 100
    default = 50


class LevelShuffle(Choice):
    """
    None: No stage shuffling.
    Same World: shuffles stages around their world.
    Pattern: shuffles stages according to the stage pattern (stage 3 will always be a minigame stage, etc.)
    Shuffled: shuffles stages across all worlds.
    """
    display_name = "Stage Shuffle"
    option_none = 0
    option_same_world = 1
    option_pattern = 2
    option_shuffled = 3
    default = 0


class BossShuffle(PlandoBosses):
    """
    None: Bosses will remain in their vanilla locations
    Shuffled: Bosses will be shuffled amongst each other
    Full: Bosses will be randomized
    Singularity: All (non-Zero) bosses will be replaced with a single boss
    Supports plando placement.
    """
    bosses = frozenset(LocationName.boss_names.keys())

    locations = frozenset(LocationName.level_names.keys())

    duplicate_bosses = True

    @classmethod
    def can_place_boss(cls, boss: str, location: str) -> bool:
        # Kirby has no logic about requiring bosses in specific locations (since we load in their stage)
        return True

    display_name = "Boss Shuffle"
    option_none = 0
    option_shuffled = 1
    option_full = 2
    option_singularity = 3


class BossShuffleAllowBB(Choice):
    """
    Allow Boss Butch variants of bosses in Boss Shuffle.
    Enabled: any boss placed will have a 50% chance of being the Boss Butch variant, including bosses not present
    Enforced: all bosses will be their Boss Butch variant.
    Boss Butch boss changes are only visual.
    """
    display_name = "Allow Boss Butch Bosses"
    option_disabled = 0
    option_enabled = 1
    option_enforced = 2
    default = 0


class AnimalRandomization(Choice):
    """
    Disabled: all animal positions will be vanilla.
    Shuffled: all animal positions will be shuffled amongst each other.
    Full: random animals will be placed across the levels. At least one of each animal is guaranteed.
    """
    display_name = "Animal Randomization"
    option_disabled = 0
    option_shuffled = 1
    option_full = 2
    default = 0


class CopyAbilityRandomization(Choice):
    """
    Disabled: enemies give regular copy abilities and health.
    Enabled: all enemies will have the copy ability received from them randomized.
    Enabled Plus Minus: enemies (except minibosses) can additionally give you anywhere from +2 health to -1 health when eaten.
    """
    display_name = "Copy Ability Randomization"
    option_disabled = 0
    option_enabled = 1
    option_enabled_plus_minus = 2


class StrictBosses(DefaultOnToggle):
    """
    If enabled, one will not be able to move onto the next world until the previous world's boss has been purified.
    """
    display_name = "Strict Bosses"


class OpenWorld(DefaultOnToggle):
    """
    If enabled, all 6 stages will be unlocked upon entering a world for the first time. A certain amount of stages
    will need to be completed in order to unlock the bosses
    """
    display_name = "Open World"


class OpenWorldBossRequirement(Range):
    """
    The amount of stages completed needed to unlock the boss of a world when Open World is turned on.
    """
    display_name = "Open World Boss Requirement"
    range_start = 1
    range_end = 6
    default = 3


class BossRequirementRandom(Toggle):
    """
    If enabled, boss purification will require a random amount of Heart Stars. Depending on options, this may have
    boss purification unlock in a random order.
    """
    display_name = "Randomize Purification Requirement"


class JumpingTarget(Range):
    """
    The required score needed to complete the Jumping minigame.
    """
    display_name = "Jumping Target Score"
    range_start = 1
    range_end = 25
    default = 10


class GameLanguage(Choice):
    """
    The language that the game should display. This does not have to match the given rom.
    """
    display_name = "Game Language"
    option_japanese = 0
    option_english = 1
    default = 1


class FillerPercentage(Range):
    """
    Percentage of non-required Heart Stars to be converted to filler items (1-Ups, Maxim Tomatoes, Invincibility Candy).
    """
    display_name = "Filler Percentage"
    range_start = 0
    range_end = 100
    default = 50


class TrapPercentage(Range):
    """
    Percentage of filler items to be converted to trap items (Gooey Bags, Slowness, Eject Ability).
    """
    display_name = "Trap Percentage"
    range_start = 0
    range_end = 100
    default = 50


class GooeyTrapPercentage(Range):
    """
    Chance that any given trap is a Gooey Bag (spawns Gooey when you receive it).
    """
    display_name = "Gooey Trap Percentage"
    range_start = 0
    range_end = 100
    default = 50


class SlowTrapPercentage(Range):
    """
    Chance that any given trap is Slowness (halves your max speed for 15 seconds when you receive it).
    """
    display_name = "Slowness Trap Percentage"
    range_start = 0
    range_end = 100
    default = 50


class AbilityTrapPercentage(Range):
    """
    Chance that any given trap is an Eject Ability (ejects your ability when you receive it).
    """
    display_name = "Ability Trap Percentage"
    range_start = 0
    range_end = 100
    default = 50


class ConsumableChecks(Toggle):
    """
    When enabled, adds all 1-Ups and Maxim Tomatoes as possible locations.
    """
    display_name = "Consumable-sanity"


class StarChecks(Toggle):
    """
    When enabled, every star in a given stage will become a check.
    Will increase the possible filler pool to include 1/3/5 stars.
    """
    display_name = "Starsanity"


class KirbyFlavorPreset(Choice):
    """
    The color of Kirby, from a list of presets.
    """
    display_name = "Kirby Flavor"
    option_default = 0
    option_bubblegum = 1
    option_cherry = 2
    option_blueberry = 3
    option_lemon = 4
    option_kiwi = 5
    option_grape = 6
    option_chocolate = 7
    option_marshmallow = 8
    option_licorice = 9
    option_watermelon = 10
    option_orange = 11
    option_lime = 12
    option_lavender = 13
    option_custom = 14
    default = 0

    @classmethod
    def from_text(cls, text: str) -> Choice:
        text = text.lower()
        if text == "random":
            choice_list = list(cls.name_lookup)
            choice_list.remove(14)
            return cls(random.choice(choice_list))
        return super().from_text(text)


class KirbyFlavor(OptionDict):
    """
    A custom color for Kirby. To use a custom color, set the preset to Custom and then define a dict of keys from "1" to
    "15", with their values being an HTML hex color.
    """
    default = {
      "1": "B01810",
      "2": "F0E0E8",
      "3": "C8A0A8",
      "4": "A87070",
      "5": "E02018",
      "6": "F0A0B8",
      "7": "D07880",
      "8": "A85048",
      "9": "E8D0D0",
      "10": "E85048",
      "11": "D0C0C0",
      "12": "B08888",
      "13": "E87880",
      "14": "F8F8F8",
      "15": "B03830",
    }


class GooeyFlavorPreset(Choice):
    """
    The color of Gooey, from a list of presets.
    """
    display_name = "Gooey Flavor"
    option_default = 0
    option_bubblegum = 1
    option_cherry = 2
    option_blueberry = 3
    option_lemon = 4
    option_kiwi = 5
    option_grape = 6
    option_chocolate = 7
    option_marshmallow = 8
    option_licorice = 9
    option_watermelon = 10
    option_orange = 11
    option_lime = 12
    option_lavender = 13
    option_custom = 14
    default = 0

    @classmethod
    def from_text(cls, text: str) -> Choice:
        text = text.lower()
        if text == "random":
            choice_list = list(cls.name_lookup)
            choice_list.remove(14)
            return cls(random.choice(choice_list))
        return super().from_text(text)


class GooeyFlavor(OptionDict):
    """
    A custom color for Gooey. To use a custom color, set the preset to Custom and then define a dict of keys from "1" to
    "15", with their values being an HTML hex color.
    """
    default = {
        "1": "000808",
        "2": "102838",
        "3": "183048",
        "4": "183878",
        "5": "1838A0",
        "6": "B01810",
        "7": "E85048",
        "8": "D0C0C0",
        "9": "F8F8F8",
    }


class MusicShuffle(Choice):
    """
    None: default music will play
    Shuffled: music will be shuffled amongst each other
    Full: random music will play in each room
    Note that certain songs will not be chosen in shuffled or full
    """
    display_name = "Music Randomization"
    option_none = 0
    option_shuffled = 1
    option_full = 2
    default = 0


class VirtualConsoleChanges(Choice):
    """
    Adds the ability to enable 2 of the Virtual Console changes.
    Flash Reduction: reduces the flashing during the Zero battle.
    Color Changes: changes the color of the background within the Zero Boss Butch rematch.
    """
    display_name = "Virtual Console Changes"
    option_none = 0
    option_flash_reduction = 1
    option_color_changes = 2
    option_both = 3
    default = 1


class Gifting(Toggle):
    """
    When enabled, the goal game item will be sent to other compatible games as a gift,
    and you can receive gifts from other players. This can be enabled during gameplay
    using the client.
    """
    display_name = "Gifting"


@dataclass
class KDL3Options(PerGameCommonOptions):
    plando_connections: KDL3PlandoConnections
    death_link: DeathLink
    game_language: GameLanguage
    goal: Goal
    goal_speed: GoalSpeed
    total_heart_stars: TotalHeartStars
    heart_stars_required: HeartStarsRequired
    filler_percentage: FillerPercentage
    trap_percentage: TrapPercentage
    gooey_trap_weight: GooeyTrapPercentage
    slow_trap_weight: SlowTrapPercentage
    ability_trap_weight: AbilityTrapPercentage
    jumping_target: JumpingTarget
    stage_shuffle: LevelShuffle
    boss_shuffle: BossShuffle
    allow_bb: BossShuffleAllowBB
    animal_randomization: AnimalRandomization
    copy_ability_randomization: CopyAbilityRandomization
    strict_bosses: StrictBosses
    open_world: OpenWorld
    ow_boss_requirement: OpenWorldBossRequirement
    boss_requirement_random: BossRequirementRandom
    consumables: ConsumableChecks
    starsanity: StarChecks
    gifting: Gifting
    kirby_flavor_preset: KirbyFlavorPreset
    kirby_flavor: KirbyFlavor
    gooey_flavor_preset: GooeyFlavorPreset
    gooey_flavor: GooeyFlavor
    music_shuffle: MusicShuffle
    virtual_console: VirtualConsoleChanges