from dataclasses import dataclass, fields
from datetime import datetime
from typing import Any, ClassVar, cast, Dict, Iterator, List, Tuple, Protocol

from Options import AssembleOptions, Choice, DeathLink, DefaultOnToggle, Option, PerGameCommonOptions, \
    ProgressionBalancing, Range, Toggle


# typing boilerplate
class FlagsProtocol(Protocol):
    value: int
    default: ClassVar[int]
    flags: List[str]


class FlagProtocol(Protocol):
    value: int
    default: ClassVar[int]
    flag: str


# meta options
class EvermizerFlags:
    flags: List[str]

    def to_flag(self: FlagsProtocol) -> str:
        return self.flags[self.value]


class EvermizerFlag:
    flag: str

    def to_flag(self: FlagProtocol) -> str:
        return self.flag if self.value != self.default else ''


class OffOnFullChoice(Choice):
    option_off = 0
    option_on = 1
    option_full = 2
    alias_chaos = 2


class OffOnLogicChoice(Choice):
    option_off = 0
    option_on = 1
    option_logic = 2


# actual options
class Difficulty(EvermizerFlags, Choice):
    """Changes relative spell cost and stuff"""
    display_name = "Difficulty"
    option_easy = 0
    option_normal = 1
    option_hard = 2
    option_mystery = 3  # 'random' is reserved
    alias_chaos = 3
    default = 1
    flags = ['e', 'n', 'h', 'x']


class EnergyCore(EvermizerFlags, Choice):
    """How to obtain the Energy Core"""
    display_name = "Energy Core"
    option_vanilla = 0
    option_shuffle = 1
    option_fragments = 2
    default = 1
    flags = ['z', '', 'Z']


class RequiredFragments(Range):
    """Required fragment count for Energy Core = Fragments"""
    display_name = "Required Fragments"
    range_start = 1
    range_end = 99
    default = 10


class AvailableFragments(Range):
    """Placed fragment count for Energy Core = Fragments"""
    display_name = "Available Fragments"
    range_start = 1
    range_end = 99
    default = 11


class MoneyModifier(Range):
    """Money multiplier in %"""
    display_name = "Money Modifier"
    range_start = 1
    range_end = 2500
    default = 200


class ExpModifier(Range):
    """EXP multiplier for Weapons, Characters and Spells in %"""
    display_name = "Exp Modifier"
    range_start = 1
    range_end = 2500
    default = 200


class SequenceBreaks(EvermizerFlags, OffOnLogicChoice):
    """Disable, enable some sequence breaks or put them in logic"""
    display_name = "Sequence Breaks"
    default = 0
    flags = ['', 'j', 'J']


class OutOfBounds(EvermizerFlags, OffOnLogicChoice):
    """Disable, enable the out-of-bounds glitch or put it in logic"""
    display_name = "Out Of Bounds"
    default = 0
    flags = ['', 'u', 'U']


class FixCheats(EvermizerFlag, DefaultOnToggle):
    """Fix cheats left in by the devs (not desert skip)"""
    display_name = "Fix Cheats"
    flag = '2'


class FixInfiniteAmmo(EvermizerFlag, Toggle):
    """Fix infinite ammo glitch"""
    display_name = "Fix Infinite Ammo"
    flag = '5'


class FixAtlasGlitch(EvermizerFlag, Toggle):
    """Fix atlas underflowing stats"""
    display_name = "Fix Atlas Glitch"
    flag = '6'


class FixWingsGlitch(EvermizerFlag, Toggle):
    """Fix wings making you invincible in some areas"""
    display_name = "Fix Wings Glitch"
    flag = '7'


class ShorterDialogs(EvermizerFlag, DefaultOnToggle):
    """Cuts some dialogs"""
    display_name = "Shorter Dialogs"
    flag = '9'


class ShortBossRush(EvermizerFlag, DefaultOnToggle):
    """Start boss rush at Metal Magmar, cut enemy HP in half"""
    display_name = "Short Boss Rush"
    flag = 'f'


class Ingredienizer(EvermizerFlags, OffOnFullChoice):
    """On Shuffles, Full randomizes spell ingredients"""
    display_name = "Ingredienizer"
    default = 1
    flags = ['i', '', 'I']


class Sniffamizer(EvermizerFlags, Choice):
    """
    Off: all vanilla items in sniff spots
    Shuffle: sniff items shuffled into random sniff spots
    """
    display_name = "Sniffamizer"
    option_off = 0
    option_shuffle = 1
    if datetime.today().year > 2024 or datetime.today().month > 3:
        option_everywhere = 2
        __doc__ = __doc__ + "    Everywhere: add sniff spots to multiworld pool"
    alias_true = 1
    default = 1
    flags = ['s', '', 'S']


class SniffIngredients(EvermizerFlag, Choice):
    """Select which items should be used as sniff items"""
    display_name = "Sniff Ingredients"
    option_vanilla_ingredients = 0
    option_random_ingredients = 1
    flag = 'v'


class Callbeadamizer(EvermizerFlags, OffOnFullChoice):
    """On Shuffles call bead characters, Full shuffles individual spells"""
    display_name = "Callbeadamizer"
    default = 1
    flags = ['c', '', 'C']


class Musicmizer(EvermizerFlag, Toggle):
    """Randomize music for some rooms"""
    display_name = "Musicmizer"
    flag = 'm'


class Doggomizer(EvermizerFlags, OffOnFullChoice):
    """On shuffles dog per act, Full randomizes dog per screen, Pupdunk gives you Everpupper everywhere"""
    display_name = "Doggomizer"
    option_pupdunk = 3
    default = 0
    flags = ['', 'd', 'D', 'p']


class TurdoMode(EvermizerFlag, Toggle):
    """Replace offensive spells by Turd Balls with varying strength and make weapons weak"""
    display_name = "Turdo Mode"
    flag = 't'


class TrapCount(Range):
    """Replace some filler items with traps"""
    display_name = "Trap Count"
    range_start = 0
    range_end = 100
    default = 0


# more meta options
class ItemChanceMeta(AssembleOptions):
    def __new__(mcs, name: str, bases: Tuple[type], attrs: Dict[Any, Any]) -> "ItemChanceMeta":
        if 'item_name' in attrs:
            attrs["display_name"] = f"{attrs['item_name']} Chance"
        attrs["range_start"] = 0
        attrs["range_end"] = 100
        cls = super(ItemChanceMeta, mcs).__new__(mcs, name, bases, attrs)   # type: ignore[no-untyped-call]
        return cast(ItemChanceMeta, cls)


class TrapChance(Range, metaclass=ItemChanceMeta):
    item_name: str
    default = 20


# more actual options
class TrapChanceQuake(TrapChance):
    """Sets the chance/ratio of quake traps"""
    item_name = "Quake Trap"


class TrapChancePoison(TrapChance):
    """Sets the chance/ratio of poison effect traps"""
    item_name = "Poison Trap"


class TrapChanceConfound(TrapChance):
    """Sets the chance/ratio of confound effect traps"""
    item_name = "Confound Trap"


class TrapChanceHUD(TrapChance):
    """Sets the chance/ratio of HUD visibility traps"""
    item_name = "HUD Trap"


class TrapChanceOHKO(TrapChance):
    """Sets the chance/ratio of OHKO (1HP left) traps"""
    item_name = "OHKO Trap"


class SoEProgressionBalancing(ProgressionBalancing):
    default = 30
    __doc__ = ProgressionBalancing.__doc__.replace(f"default {ProgressionBalancing.default}", f"default {default}") \
        if ProgressionBalancing.__doc__ else None
    special_range_names = {**ProgressionBalancing.special_range_names, "normal": default}


# noinspection SpellCheckingInspection
@dataclass
class SoEOptions(PerGameCommonOptions):
    difficulty:            Difficulty
    energy_core:           EnergyCore
    required_fragments:    RequiredFragments
    available_fragments:   AvailableFragments
    money_modifier:        MoneyModifier
    exp_modifier:          ExpModifier
    sequence_breaks:       SequenceBreaks
    out_of_bounds:         OutOfBounds
    fix_cheats:            FixCheats
    fix_infinite_ammo:     FixInfiniteAmmo
    fix_atlas_glitch:      FixAtlasGlitch
    fix_wings_glitch:      FixWingsGlitch
    shorter_dialogs:       ShorterDialogs
    short_boss_rush:       ShortBossRush
    ingredienizer:         Ingredienizer
    sniffamizer:           Sniffamizer
    sniff_ingredients:     SniffIngredients
    callbeadamizer:        Callbeadamizer
    musicmizer:            Musicmizer
    doggomizer:            Doggomizer
    turdo_mode:            TurdoMode
    death_link:            DeathLink
    trap_count:            TrapCount
    trap_chance_quake:     TrapChanceQuake
    trap_chance_poison:    TrapChancePoison
    trap_chance_confound:  TrapChanceConfound
    trap_chance_hud:       TrapChanceHUD
    trap_chance_ohko:      TrapChanceOHKO
    progression_balancing: SoEProgressionBalancing

    @property
    def trap_chances(self) -> Iterator[TrapChance]:
        for field in fields(self):
            option = getattr(self, field.name)
            if isinstance(option, TrapChance):
                yield option

    @property
    def flags(self) -> str:
        flags = ''
        for field in fields(self):
            option = getattr(self, field.name)
            if isinstance(option, (EvermizerFlag, EvermizerFlags)):
                assert isinstance(option, Option)
                # noinspection PyUnresolvedReferences
                flags += option.to_flag()
        return flags