"""Base classes for the various D&D 5e content types."""


import warnings
from abc import ABC
from pathlib import Path

from dungeonsheets import exceptions
from dungeonsheets.stats import Ability, ArmorClass, Initiative, Speed, Skill
from dungeonsheets.content_registry import find_content


def read(fname):
    return open((Path(__file__).parent / fname).resolve()).read()


__version__ = read("../VERSION").strip()


class Content(ABC):
    """A base class for all D&D 5e content types.

    Every piece of content (e.g. class feature, spell, monster) should
    have this base class in its inheritance tree.

    """
    dungeonsheets_version = __version__
    name = "Generic content"

    @staticmethod
    def _resolve_mechanic(mechanic, SuperClass, warning_message=None):
        """Take a raw entry in a character sheet and turn it into a usable object.

        Eg: spells can be defined in many ways. This function accepts all
        of those options and returns an actual *Spell* class that can be
        used by a character::

            >>> _resolve_mechanic("mage_hand", SuperClass=spells.Spell)

            >>> _resolve_mechanic("mage_hand", SuperClass=None)

            >>> from dungeonsheets import spells
            >>> class MySpell(spells.Spell): pass
            >>> _resolve_mechanic(MySpell, SuperClass=spells.Spell)

            >>> _resolve_mechanic("hocus pocus", SuperClass=spells.Spell)

        The acceptable entries for *mechanic*, in priority order, are:
          1. A subclass of *SuperClass*
          2. A string with the name of defined content
          3. The name of an unknown spell (creates generic object using *factory*)

        *SuperClass* can be ``None`` to match any class, however this will
        raise an exception if more than one content type has this
        name. For example, "shield" can refer to both the armor or the
        spell, so ``_resolve_mechanic("shield")`` will raise an exception.

        Parameters
        ==========
        mechanic : str, type
          The thing to be resolved, either a string with the name of the
          mechanic, or a subclass of *ParentClass* describing the
          mechanic.
        SuperClass : type
          Class to determine whether *mechanic* should just be allowed
          through as is.
        error_message : str, optional
          A string whose ``str.format()`` method (receiving one positional
          argument *mechanic*) will be used for displaying a warning when an
          unknown mechanic is resolved. If omitted, no warning will be
          displayed.

        Returns
        =======
        Mechanic
          A class representing the resolved game mechanic. This will
          likely be a subclass of *SuperClass* if the other parameters are
          well behaved, but this is not enforced.

        """
        is_already_resolved = isinstance(mechanic, type) and issubclass(
            mechanic, SuperClass
        )
        if is_already_resolved:
            Mechanic = mechanic
        elif SuperClass is not None and isinstance(mechanic, SuperClass):
            # Has been instantiated for some reason
            Mechanic = type(mechanic)
        else:
            try:
                # Retrieve pre-defined mechanic
                valid_classes = [SuperClass] if SuperClass is not None else []
                Mechanic = find_content(mechanic, valid_classes=valid_classes)
            except ValueError:
                # No pre-defined mechanic available
                if warning_message is not None:
                    # Emit the warning
                    msg = warning_message.format(mechanic)
                    warnings.warn(msg)
                else:
                    # Create a generic message so we can make a docstring later.
                    msg = f'Mechanic "{mechanic}" not defined. Please add it.'
                # Create generic mechanic from the factory
                try:
                    class_name = "".join([s.title() for s in mechanic.split("_")])
                except AttributeError:
                    raise exceptions.InvalidContentType(
                        f"``{mechanic}`` must either be string-like, "
                        f"or inherit from {SuperClass}. "
                    )
                mechanic_name = mechanic.replace("_", " ").title()
                attrs = {"name": mechanic_name, "__doc__": msg, "source": "Unknown"}
                Mechanic = type(class_name, (SuperClass,), attrs)
        return Mechanic


class Creature(Content):
    """A thing with stats. Use Monster or Character, not this class
    directly!

    """
    # General attributes
    alignment = "Neutral"
    _race = None
    name = "Generic creature"
    # Hit points
    hp_max = None
    # Base stats (ability scores)
    strength = Ability()
    dexterity = Ability()
    constitution = Ability()
    intelligence = Ability()
    wisdom = Ability()
    charisma = Ability()
    # Numerical things
    armor_class = ArmorClass()
    initiative = Initiative()
    speed = Speed()
    # Proficiencies and Languages
    _saving_throw_proficiencies = tuple()  # use to overwrite class proficiencies
    other_weapon_proficiencies = tuple()  # add to class/race proficiencies
    skill_proficiencies = list()
    skill_expertise = list()
    languages = ""
    senses = ""
    # Skills
    acrobatics = Skill(ability="dexterity")
    animal_handling = Skill(ability="wisdom")
    arcana = Skill(ability="intelligence")
    athletics = Skill(ability="strength")
    deception = Skill(ability="charisma")
    history = Skill(ability="intelligence")
    insight = Skill(ability="wisdom")
    intimidation = Skill(ability="charisma")
    investigation = Skill(ability="intelligence")
    medicine = Skill(ability="wisdom")
    nature = Skill(ability="intelligence")
    perception = Skill(ability="wisdom")
    performance = Skill(ability="charisma")
    persuasion = Skill(ability="charisma")
    religion = Skill(ability="intelligence")
    sleight_of_hand = Skill(ability="dexterity")
    stealth = Skill(ability="dexterity")
    survival = Skill(ability="wisdom")
    # Inventory
    cp = 0
    sp = 0
    ep = 0
    gp = 0
    pp = 0
    equipment = ""
    _weapons = list()
    magic_items = list()
    armor = None
    shield = None
    # Magic
    spellcasting_ability = None
    _spells = list()
    _spells_prepared = list()
    infusions = list()
    # Features IN MAJOR DEVELOPMENT
    custom_features = list()
    feature_choices = list()

    def __init__(self):
        pass

    @property
    def weapons(self):
        return self._weapons.copy()

    @property
    def passive_wisdom(self):
        return self.perception.modifier + 10

    @property
    def passive_perception(self):
        """Just a wrapper around passive wisdom."""
        return self.passive_wisdom

    @property
    def passive_insight(self):
        return self.insight.modifier + 10

    @property
    def passive_investigation(self):
        return self.investigation.modifier + 10

    @property
    def abilities(self):
        return [self.strength, self.dexterity, self.constitution,
                self.intelligence, self.wisdom, self.charisma]

    @property
    def skills(self):
        return [self.acrobatics, self.animal_handling, self.arcana,
                self.athletics, self.deception, self.history,
                self.insight, self.intimidation, self.investigation,
                self.medicine, self.nature, self.perception,
                self.performance, self.persuasion, self.religion,
                self.sleight_of_hand, self.stealth, self.survival]
    
    @property
    def is_spellcaster(self):
        raise NotImplementedError
