from collections import defaultdict

from dungeonsheets import features as feats
from dungeonsheets import spells, weapons


class Race:
    name = "Unknown"
    size = "medium"
    speed = 30
    owner = None
    languages = ("Common",)
    proficiencies_text = tuple()
    weapon_proficiences = tuple()
    skill_proficiencies = ()
    skill_choices = ()
    num_skill_choices = 0
    features = tuple()
    features_by_level = defaultdict(list)
    strength_bonus = 0
    dexterity_bonus = 0
    constitution_bonus = 0
    intelligence_bonus = 0
    wisdom_bonus = 0
    charisma_bonus = 0
    hit_point_bonus = 0
    spells_known = ()

    def __init__(self, owner=None):
        self.owner = owner
        cls = type(self)
        # Instantiate the features
        self.features = tuple([f(owner=self.owner) for f in cls.features])
        self.features_by_level = defaultdict(list)
        for i in range(1, 21):
            self.features_by_level[i] = [
                f(owner=self.owner) for f in cls.features_by_level[i]
            ]
        self.spells_known = [S() for S in cls.spells_known]

    @property
    def spells_prepared(self):
        return self.spells_known

    def __str__(self):
        return self.name

    def __repr__(self):
        return '"{:s}"'.format(self.name)


# Dwarves
class _Dwarf(Race):
    name = "Dwarf"
    size = "medium"
    speed = 25
    languages = ("Common", "Dwarvish")
    constitution_bonus = 2
    proficiencies_text = ("battleaxes", "handaxes", "throwing hammers", "warhammers")
    weapon_proficiences = (
        weapons.Battleaxe,
        weapons.Handaxe,
        weapons.ThrowingHammer,
        weapons.Warhammer,
    )
    features = (feats.Darkvision, feats.DwarvenResilience, feats.Stonecunning)


class HillDwarf(_Dwarf):
    name = "Hill Dwarf"
    wisdom_bonus = 1
    hit_point_bonus = 1
    features = _Dwarf.features + (feats.DwarvenToughness,)


class MountainDwarf(_Dwarf):
    name = "Mountain Dwarf"
    strength_bonus = 2


# Elves
class _Elf(Race):
    name = "Elf"
    size = "medium"
    speed = 30
    dexterity_bonus = 2
    skill_proficiencies = ("perception",)
    languages = ("Common", "Elvish")
    features = (feats.Darkvision, feats.FeyAncestry, feats.Trance)


class HighElf(_Elf):
    name = "High Elf"
    weapon_proficiencies = (
        weapons.Longsword,
        weapons.Shortsword,
        weapons.Shortbow,
        weapons.Longbow,
    )
    proficiencies_text = ("longswords", "shortswords", "shortbows", "longbows")
    intelligence_bonus = 1
    languages = ("Common", "Elvish", "[choose one]")
    features = _Elf.features + (feats.ElfCantrip,)


class WoodElf(_Elf):
    name = "Wood Elf"
    weapon_proficiencies = (
        weapons.Longsword,
        weapons.Shortsword,
        weapons.Shortbow,
        weapons.Longbow,
    )
    proficiencies_text = ("longswords", "shortswords", "shortbows", "longbows")
    wisdom_bonus = 1
    speed = 35
    features = _Elf.features + (feats.MaskOfTheWild,)


class DarkElf(_Elf):
    name = "Dark Elf"
    weapon_proficiencies = (weapons.Rapier, weapons.Shortsword, weapons.HandCrossbow)
    proficiencies_text = ("rapiers", "shortswords", "hand crossbows")
    charisma_bonus = 1
    features = (
        feats.SuperiorDarkvision,
        feats.FeyAncestry,
        feats.Trance,
        feats.SunlightSensitivity,
        feats.DrowMagic,
    )
    spells_known = (spells.DancingLights,)


# Halflings
class _Halfling(Race):
    name = "Halfling"
    size = "small"
    speed = 25
    dexterity_bonus = 2
    languages = ("Common", "Halfling")
    features = (feats.Lucky, feats.Brave, feats.HalflingNimbleness)


class LightfootHalfling(_Halfling):
    name = "Lightfoot Halfling"
    charisma_bonus = 1
    features = _Halfling.features + (feats.NaturallyStealthy,)


class StoutHalfling(_Halfling):
    name = "Stout Halfling"
    constitution_bonus = 1
    features = _Halfling.features + (feats.StoutResilience,)


# Humans
class Human(Race):
    name = "Human"
    size = "medium"
    speed = 30
    strength_bonus = 1
    dexterity_bonus = 1
    constitution_bonus = 1
    intelligence_bonus = 1
    wisdom_bonus = 1
    charisma_bonus = 1
    languages = ("Common", "[choose one]")


class Rashemi(Human):
    name = "Rashemi"


# Dragonborn
class Dragonborn(Race):
    name = "Dragonborn"
    size = "medium"
    speed = 30
    strength_bonus = 2
    charisma_bonus = 1
    languages = ("Common", "Draconic")
    features = (feats.DraconicAncestry, feats.BreathWeapon, feats.DraconicResistance)


# Gnomes
class _Gnome(Race):
    name = "Gnome"
    size = "small"
    speed = 25
    intelligence_bonus = 2
    languages = ("Common", "Gnomish")
    features = (feats.Darkvision, feats.GnomeCunning)


class ForestGnome(_Gnome):
    name = "Forest Gnome"
    dexterity_bonus = 1
    features = _Gnome.features + (feats.NaturalIllusionist, feats.SpeakWithSmallBeasts)
    spells_known = (spells.MinorIllusion,)


class RockGnome(_Gnome):
    name = "Rock Gnome"
    constitution_bonus = 1
    features = _Gnome.features + (feats.ArtificersLore, feats.Tinker)


class DeepGnome(_Gnome):
    name = "Deep Gnome"
    dexterity_bonus = 1
    languages = ("Common", "Gnomish", "Undercommon")
    features = (feats.SuperiorDarkvision, feats.GnomeCunning, feats.StoneCamouflage)


# Half-elves
class HalfElf(Race):
    name = "Half-Elf"
    size = "medium"
    speed = 30
    charisma_bonus = 2
    skill_choices = (
        "acrobatics",
        "animal handling",
        "arcana",
        "athletics",
        "deception",
        "history",
        "insight",
        "intimidation",
        "investigation",
        "medicine",
        "nature",
        "perception",
        "performance",
        "persuasion",
        "religion",
        "sleight of hand",
        "stealth",
        "survival",
    )
    num_skill_choices = 2
    languages = ("Common", "Elvish", "[choose one]")
    features = (feats.Darkvision, feats.FeyAncestry)


# Half-Orcs
class HalfOrc(Race):
    name = "Half-Orc"
    size = "medium"
    speed = 30
    strength_bonus = 2
    constitution_bonus = 1
    skill_proficiencies = ("intimidation",)
    languages = ("Common", "Orc")
    features = (feats.Darkvision, feats.RelentlessEndurance, feats.SavageAttacks)


# Tielflings
class Tiefling(Race):
    name = "Tiefling"
    size = "medium"
    speed = 30
    intelligence_bonus = 1
    charisma_bonus = 2
    languages = ("Common", "Infernal")
    features = (feats.Darkvision, feats.HellishResistance, feats.InfernalLegacy)


# Aassimar
class _Aasimar(Race):
    name = "Aasimar"
    size = "medium"
    speed = 30
    charisma_bonus = 2
    languages = ("Common", "Celestial")
    features = (
        feats.Darkvision,
        feats.CelestialResistance,
        feats.HealingHands,
        feats.LightBearer,
    )
    spells_known = (spells.Light,)


# Protector Aasimar
class ProtectorAasimar(_Aasimar):
    name = "Protector Aasimar"
    wisdom_bonus = 1
    features_by_level = defaultdict(list)
    features_by_level[3] += [feats.RadiantSoul]


# Fallen Aasimar
class ScourgeAasimar(_Aasimar):
    name = "Scourge Aasimar"
    constitution_bonus = 1
    features_by_level = defaultdict(list)
    features_by_level[3] += [feats.RadiantConsumption]


# Fallen Aasimar
class FallenAasimar(_Aasimar):
    name = "Fallen Aasimar"
    strength_bonus = 1
    features_by_level = defaultdict(list)
    features_by_level[3] += [feats.NecroticShroud]


# Firbolg
class Firbolg(Race):
    name = "Firbolg"
    size = "medium"
    speed = 30
    wisdom_bonus = 2
    strength_bonus = 1
    features = (
        feats.FirbolgMagic,
        feats.HiddenStep,
        feats.PowerfulBuild,
        feats.SpeechOfBeastAndLeaf,
    )
    languages = ("Common", "Elvish", "Giant")


# Goliath
class Goliath(Race):
    name = "Goliath"
    size = "Medium"
    speed = 30
    skill_proficiencies = ("athletics",)
    languages = ("Common", "Giant")
    features = (feats.StonesEndurance, feats.PowerfulBuild, feats.MountainBorn)


# Lizardfolk
class Lizardfolk(Race):
    name = "Lizardfolk"
    size = "medium"
    speed = """30 (30 swim)"""
    constitution_bonus = 2
    wisdom_bonus = 1
    languages = ("Common", "Draconic")
    weapon_proficiencies = (weapons.Bite,)
    proficiencies_text = ("bite",)
    features = (
        feats.CunningArtisan,
        feats.HoldBreath,
        feats.NaturalArmor,
        feats.HungryJaws,
    )
    skill_choices = ("animal handling", "nature", "perception", "stealth", "survival")

    def __init__(self, owner=None):
        super().__init__(owner=owner)
        self.owner.wield_weapon("bite")


# Kenku
class Kenku(Race):
    name = "Kenku"
    size = "medium"
    speed = 30
    dexterity_bonus = 2
    wisdom_bonus = 1
    languages = ("Common", "Auran")
    skill_choices = ("acrobatics", "deception", "stealth", "sleight of hand")
    num_skill_choices = 2
    features = (
        feats.ExpertForgery,
        feats.Mimicry,
    )


# Tabaxi
class Tabaxi(Race):
    name = "Tabaxi"
    size = "medium"
    dexterity_bonus = 2
    charisma_bonus = 1
    speed = "30 (20 climb)"
    languages = ("Common", "[Choose One]")
    weapon_proficiencies = (weapons.Claws,)
    proficiences_text = ("Claws",)
    skill_proficiencies = ("perception", "stealth")
    features = (
        feats.Darkvision,
        feats.FelineAgility,
    )

    def __init__(self, owner=None):
        super().__init__(owner=owner)
        self.owner.wield_weapon("claws")


# Triton
class Triton(Race):
    name = "Triton"
    size = "medium"
    strength_bonus = 1
    constitution_bonus = 1
    charisma_bonus = 1
    speed = "30 (30 swim)"
    features = (
        feats.Amphibious,
        feats.ControlAirAndWater,
        feats.EmissaryOfTheSea,
        feats.GuardiansOfTheDepths,
    )
    languages = ("Common", "Primordial")
    spells_known = (spells.FogCloud,)


# Aarakocra
class Aarakocra(Race):
    name = "Aarakocra"
    size = "medium"
    speed = "25 (50 fly)"
    dexterity_bonus = 2
    wisdom_bonus = 1
    languages = ("Common", "Aarakocra", "Auran")
    weapon_proficiencies = (weapons.Talons,)
    proficiences_text = ("Talons",)

    def __init__(self, owner=None):
        super().__init__(owner=owner)
        self.owner.wield_weapon("talons")


# Genasi
class _Genasi(Race):
    name = "Genasi"
    constitution_bonus = 2
    size = "medium"
    speed = 30
    languages = ("Common", "Primoridal")


class AirGenasi(_Genasi):
    name = "Air Genasi"
    dexterity_bonus = 1
    features = (feats.UnendingBreath, feats.MingleWithTheWind)


class EarthGenasi(_Genasi):
    name = "Earth Genasi"
    strength_bonus = 1
    features = (feats.EarthWalk, feats.MergeWithStone)


class FireGenasi(_Genasi):
    name = "Fire Genasi"
    intelligence_bonus = 1
    features = (feats.Darkvision, feats.FireResistance, feats.ReachToTheBlaze)


class WaterGenasi(_Genasi):
    name = "Water Genasi"
    wisdom_bonus = 1
    speed = "30 (30 swim)"
    features = (feats.AcidResistance, feats.Amphibious, feats.CallToTheWave)


# Eberron Races
class Kalashtar(Race):
    name = "Kalashtar"
    wisdom_bonus = 2
    charisma_bonus = 1
    size = "medium"
    speed = 30
    languages = (
        "Common",
        "Quori",
    )  # Not sure how to have a "+1 language of your choice" - naviabbot
    features = (
        feats.DualMind,
        feats.MentalDiscipline,
        feats.MindLink,
        feats.SeveredFromDreams,
    )


class BugBear(Race):
    name = "BugBear"
    strength_bonus = 2
    dexterity_bonus = 1
    size = "medium"
    speed = 30
    features = (
        feats.Darkvision,
        feats.LongLimbed,
        feats.PowerfulBuild,
        feats.SupriseAttack,
    )
    skill_proficiencies = ("stealth",)
    languages = ("Common", "Goblin")


class Goblin(Race):
    name = "Goblin"
    dexterity_bonus = 2
    constitution_bonus = 1
    size = "small"
    speed = 30
    features = (feats.Darkvision, feats.FuryOfTheSmall, feats.NimbleEscape)
    languages = ("Common", "Goblin")


class HobGoblin(Race):
    name = "HobGoblin"
    constitution_bonus = 2
    intelligence_bonus = 1
    size = "medium"
    speed = 30
    features = (feats.Darkvision, feats.SavingFace)
    proficiencies_text = ("light armor", "[Chose two martial melee weapons]")
    languages = ("Common", "Goblin")


class Kobold(Race):
    name = "Kobold"
    dexterity_bonus = 2
    strength_bonus = -2
    size = "small"
    speed = 30
    features = (
        feats.Darkvision,
        feats.PackTactics,
        feats.GrovelCowerAndBeg,
        feats.SunlightSensitivity,
    )
    languages = ("Common", "Draconic")


class Orc(Race):
    name = "Orc"
    strength_bonus = 2
    constitution_bonus = 1
    intelligence_bonus = -2
    size = "medium"
    speed = 30
    skill_proficiencies = ("intimidation",)
    features = (feats.Darkvision, feats.Aggressive, feats.PowerfulBuild)
    languages = ("Common", "Orc")


class PureBlood(Race):
    name = "Yuan-Ti Pureblood"
    charisma_bonus = 2
    intelligence_bonus = 1
    size = "medium"
    speed = 30
    features = (
        feats.Darkvision,
        feats.InnateSpellcasting,
        feats.MagicResistance,
        feats.PoisonImmunity,
    )
    spells_known = (spells.PoisonSpray,)
    languages = ("Common", "Abyssal", "Draconic")


PHB_races = [
    HillDwarf,
    MountainDwarf,
    HighElf,
    WoodElf,
    DarkElf,
    LightfootHalfling,
    StoutHalfling,
    Rashemi,
    Dragonborn,
    ForestGnome,
    RockGnome,
    HalfElf,
    HalfOrc,
    Tiefling,
    Human,
]

VOLO_races = [
    ProtectorAasimar,
    ScourgeAasimar,
    FallenAasimar,
    Firbolg,
    Goliath,
    Lizardfolk,
    Kenku,
    Tabaxi,
    Triton,
]

EE_races = [Aarakocra, DeepGnome, AirGenasi, FireGenasi, EarthGenasi, WaterGenasi]

MONSTER_races = [BugBear, Goblin, HobGoblin, Kobold, Orc, PureBlood]

RFTLW_races = [Kalashtar]

# Guildmaster's Guide to Ravnica
GGTR_races = [Goblin]

available_races = (
    PHB_races + VOLO_races + EE_races + MONSTER_races + RFTLW_races + GGTR_races
)

__all__ = tuple([r.name for r in available_races]) + (
    "available_races",
    "PHB_races",
    "VOLO_races",
    "EE_races",
    "MONSTER_races",
    "RFTLW_races",
    "GGTR_races",
)
