import csv
import enum
import math
from typing import Protocol, Union, Dict, List, Set
from BaseClasses import Item, ItemClassification
from . import Options, data
from dataclasses import dataclass, field
from random import Random


class DLCQuestItem(Item):
    game: str = "DLCQuest"


offset = 120_000


class Group(enum.Enum):
    DLC = enum.auto()
    DLCQuest = enum.auto()
    Freemium = enum.auto()
    Item = enum.auto()
    Coin = enum.auto()
    Trap = enum.auto()


@dataclass(frozen=True)
class ItemData:
    code_without_offset: offset
    name: str
    classification: ItemClassification
    groups: Set[Group] = field(default_factory=frozenset)

    def __post_init__(self):
        if not isinstance(self.groups, frozenset):
            super().__setattr__("groups", frozenset(self.groups))

    @property
    def code(self):
        return offset + self.code_without_offset if self.code_without_offset is not None else None

    def has_any_group(self, *group: Group) -> bool:
        groups = set(group)
        return bool(groups.intersection(self.groups))


def load_item_csv():
    try:
        from importlib.resources import files
    except ImportError:
        from importlib_resources import files  # noqa

    items = []
    with files(data).joinpath("items.csv").open() as file:
        item_reader = csv.DictReader(file)
        for item in item_reader:
            id = int(item["id"]) if item["id"] else None
            classification = ItemClassification[item["classification"]]
            groups = {Group[group] for group in item["groups"].split(",") if group}
            items.append(ItemData(id, item["name"], classification, groups))
    return items


all_items: List[ItemData] = load_item_csv()
item_table: Dict[str, ItemData] = {}
items_by_group: Dict[Group, List[ItemData]] = {}


def initialize_item_table():
    item_table.update({item.name: item for item in all_items})


def initialize_groups():
    for item in all_items:
        for group in item.groups:
            item_group = items_by_group.get(group, list())
            item_group.append(item)
            items_by_group[group] = item_group


initialize_item_table()
initialize_groups()


def create_trap_items(world, World_Options: Options.DLCQuestOptions, trap_needed: int, random: Random) -> List[Item]:
    traps = []
    for i in range(trap_needed):
        trap = random.choice(items_by_group[Group.Trap])
        traps.append(world.create_item(trap))

    return traps


def create_items(world, World_Options: Options.DLCQuestOptions, locations_count: int, random: Random):
    created_items = []
    if World_Options[Options.Campaign] == Options.Campaign.option_basic or World_Options[
        Options.Campaign] == Options.Campaign.option_both:
        for item in items_by_group[Group.DLCQuest]:
            if item.has_any_group(Group.DLC):
                created_items.append(world.create_item(item))
            if item.has_any_group(Group.Item) and World_Options[
                Options.ItemShuffle] == Options.ItemShuffle.option_shuffled:
                created_items.append(world.create_item(item))
        if World_Options[Options.CoinSanity] == Options.CoinSanity.option_coin:
            coin_bundle_needed = math.floor(825 / World_Options[Options.CoinSanityRange])
            for item in items_by_group[Group.DLCQuest]:
                if item.has_any_group(Group.Coin):
                    for i in range(coin_bundle_needed):
                        created_items.append(world.create_item(item))
                    if 825 % World_Options[Options.CoinSanityRange] != 0:
                        created_items.append(world.create_item(item))

    if World_Options[Options.Campaign] == Options.Campaign.option_live_freemium_or_die or World_Options[
        Options.Campaign] == Options.Campaign.option_both:
        for item in items_by_group[Group.Freemium]:
            if item.has_any_group(Group.DLC):
                created_items.append(world.create_item(item))
            if item.has_any_group(Group.Item) and World_Options[
                Options.ItemShuffle] == Options.ItemShuffle.option_shuffled:
                created_items.append(world.create_item(item))
        if World_Options[Options.CoinSanity] == Options.CoinSanity.option_coin:
            coin_bundle_needed = math.floor(889 / World_Options[Options.CoinSanityRange])
            for item in items_by_group[Group.Freemium]:
                if item.has_any_group(Group.Coin):
                    for i in range(coin_bundle_needed):
                        created_items.append(world.create_item(item))
                    if 889 % World_Options[Options.CoinSanityRange] != 0:
                        created_items.append(world.create_item(item))

    trap_items = create_trap_items(world, World_Options, locations_count - len(created_items), random)
    created_items += trap_items

    return created_items