import time
from random import randint
from functions import startGame, typingPrint
from enum import Enum, auto

# pyinstaller --onefile --paths=C:\Users\kasai\PycharmProjects\pythonProject\Kanjin\KanjinEngine.py


class Player:
    """Character Creation"""

    # Default Character
    def __init__(self, name, gender, age, race, job):
        # Personality
        self.name = name
        self.gender = gender
        self.age = age
        self.race = race
        self.job = job
        # Health
        self.hitDie = randint(1, 6)
        self.currentHP = 100
        self.maxHP = 100
        # Levels
        self.level = 1
        self.exp = 0
        self.maxEXP = 100
        # Magic
        self.spells_known = []
        self.spells_ready = []
        # Equipment
        self.weapon = rock
        self.armor = tornRags
        # Defence
        self.ac = 0
        self.elemental_resistance = {DamageType.FIRE: False, DamageType.WATER: False, DamageType.LIGHTNING: False}
        self.physical_resistance = {DamageType.SLASHING: False, DamageType.CRUSHING: False, DamageType.PIERCING: False}
        # Ability Scores
        self.stats = {"Strength": 0,
                      "Dexterity": 0,
                      "Constitution": 0,
                      "Intelligence": 0,
                      "Wisdom": 0,
                      "Charisma": 0}
        # Ability Modifiers
        self.mods = {"Strength": 0,
                     "Dexterity": 0,
                     "Constitution": 0,
                     "Intelligence": 0,
                     "Wisdom": 0,
                     "Charisma": 0}
        # Held items
        self.inventory = {rock: {"Count": 1,
                                 "object": Weapon,
                                 "equipped": True
                                 },
                          tornRags: {"Count": 1,
                                     "object": Armor,
                                     "equipped": True
                                     },
                          GP1: {"Count": 10,
                                "object": Money,
                                "equipped": False
                                },
                          }

    def getModifier(self):
        self.mods["Strength"] = -5 + self.stats["Strength"] // 2
        self.mods["Dexterity"] = -5 + self.stats["Dexterity"] // 2
        self.mods["Constitution"] = -5 + self.stats["Constitution"] // 2
        self.mods["Intelligence"] = -5 + self.stats["Intelligence"] // 2
        self.mods["Wisdom"] = -5 + self.stats["Wisdom"] // 2
        self.mods["Charisma"] = -5 + self.stats["Charisma"] // 2

    def set_stats_race(self):
        if self.race.lower() == "human":
            print("Human")
            self.stats["Strength"] += 1,
            self.stats["Dexterity"] += 1,
            self.stats["Constitution"] += 1,
            self.stats["Intelligence"] += 1,
            self.stats["Wisdom"] += 1,
            self.stats["Charisma"] += 1

        elif self.race.lower() == "elf":
            print("Elf")
            self.stats["Dexterity"] += 2
            self.stats["Intelligence"] += 1

        elif self.race.lower() == "dwarf":
            print("Dwarf")
            self.stats["Constitution"] += 2
            self.stats["Wisdom"] += 1

    def set_stats_job(self):
        if self.job.lower() == "warrior":
            print("Warrior")
            self.hitDie = randint(1, 12)
            self.maxHP = (12 + self.mods["Constitution"])
            self.currentHP = (12 + self.mods["Constitution"])

        elif self.job.lower() == "ranger":
            print("Ranger")
            self.hitDie = randint(1, 10)
            self.maxHP = (10 + self.mods["Constitution"])
            self.currentHP = (10 + self.mods["Constitution"])

        elif self.job.lower() == "mage":
            print("Mage")
            self.hitDie = randint(1, 8)
            self.maxHP = (8 + self.mods["Constitution"])
            self.currentHP = (8 + self.mods["Constitution"])

    def current_stats(self):
        """Prints a display of the user's current statistics."""
        typingPrint("Your current stats are:", time)
        typingPrint(f'Hit Points: {self.currentHP}/{self.maxHP}', time)
        typingPrint(f'Strength: {self.stats["Strength"]}', time)
        typingPrint(f'Dexterity: {self.stats["Dexterity"]}', time)
        typingPrint(f'Constitution: {self.stats["Constitution"]}', time)
        typingPrint(f'Intelligence: {self.stats["Intelligence"]}', time)
        typingPrint(f'Wisdom: {self.stats["Wisdom"]}', time)
        typingPrint(f'Charisma: {self.stats["Charisma"]}', time)

    def current_equip(self):
        """Prints a list of items currently equipped."""
        typingPrint(f"You have equipped:\n {self.armor.name}\n"
                    f"    {self.armor.description}"
                    f"\n{self.weapon.name}\n"
                    f"    {self.weapon.description}\n", time)

    def new_char(self):
        """Outputs final stats, armour, and inventory"""
        self.allocation()
        self.set_stats_race()
        self.set_stats_job()
        self.getModifier()
        self.current_stats()
        print(' ')
        time.sleep(1)
        self.current_equip()
        print(' ')
        # current_inventory()
        print(' ')

    def health_check(self):
        """Print out current/max HP"""
        print(f'You have {self.currentHP}/{self.maxHP} HP.')

    def take_damage(self, DMGtype, size):
        """Define the damage type and dice size"""
        damage = randint(1, size)
        if DMGtype in self.elemental_resistance or DMGtype in self.physical_resistance:
            damage //= 2
        self.currentHP -= damage
        return damage

    def allocation(self):
        rolls = []
        stats = []
        attributes = ["Strength", "Dexterity", "Constitution", "Intelligence", "Wisdom", "Charisma"]
        while len(stats) != 6:
            # roll 4d6
            for roll in range(4):
                r = randint(1, 6)
                rolls.append(r)
            # Drop the lowest number and then add to single value
            del rolls[rolls.index(min(rolls))]
            val = sum(rolls)
            stats.append(str(val))
            rolls = []

        typingPrint("Please assign a stat to a selected attribute by entering the number then the attribute.\n"
                    "For example, '10 strength' will assign the stat 10 to your strength, if you have a 10 showing.\n",
                    time)
        typingPrint("Please select a stat and an attribute.\n\n", time)
        typingPrint("Your rolled stats are:\n" + ', '.join(stats) + "\n\n", time)
        typingPrint("Your attributes are:\n" + ', '.join(attributes) + "\n\n", time)

        # Allocate
        while len(stats) != 0:
            input_text = input(">> ").title()
            words = input_text.split()
            if words[0] in stats and words[1] in attributes:
                self.stats[words[1]] += int(words[0])
                stats.remove(words[0])
                attributes.remove(words[1])
                if len(stats) == 0:
                    print("")
                    break
                typingPrint("The remaining options are:\n", time)
                typingPrint(', '.join(stats), time)
                typingPrint(', '.join(attributes), time)
            else:
                typingPrint("Error. Input was not recognised. Please try again with 'Number' + 'Attribute'.", time)
                continue


class Scene(object):

    def enter(self):
        print(self.name)  # works
        print(self.descrip)  # works
    # this applies to all classes under Scene, but Zed does it differently


class Engine(object):

    def __init__(self, scene_map):
        self.name = "name"
        self.descrip = "descrip"
        self.scene_map = scene_map
        # gets the game's map from instance "mymap" at bottom of this file

    def play(self):
        current_scene = self.scene_map.opening_scene()
        last_scene = self.scene_map.next_scene('Finished')
        # see the Map object: runs function named opening_scene()
        # this runs only once 
        # this STARTS THE GAME

        # this (below) is supposed to be an infinite loop (but mine is not)
        while not last_scene:
            next_scene_name = current_scene.enter()
            current_scene = self.scene_map.next_scene(next_scene_name)
            print("\n--------")
            current_scene.enter()

            #  note: will throw error if no new scene passed in by next line:
            next_scene_name = current_scene.action()
            #  get the name of the next scene from the action() function that
            #     runs in the current scene - what it returns

            current_scene = self.scene_map.next_scene(next_scene_name)
            #  here we use that val returned by current scene to go to
            #    the next scene, running function in Map


class DamageType(Enum):
    SLASHING = auto()
    CRUSHING = auto()
    PIERCING = auto()
    FIRE = auto()
    WATER = auto()
    LIGHTNING = auto()


class DamageMod(Enum):
    STRENGTH = auto()
    DEXTERITY = auto()
    CONSTITUTION = auto()
    INTELLIGENCE = auto()
    WISDOM = auto()
    CHARISMA = auto()


class Item:
    """The base class for all items"""

    def __init__(self, name, description, value, magical):
        self.name = name
        self.description = description
        self.value = value
        self.magical = magical

    def __repr__(self):
        return f"{self.name}\n=====\n{self.description}\nValue: {self.value}\n"


class Money(Item):
    """The currency item used in the world of Kanjin"""

    def __init__(self, name, amt, magical):
        self.name = name
        self.amt = amt
        self.magical = magical
        super().__init__(name=self.name,
                         description=f"A small round coin made of {self.name.lower()} "
                                     f"with the imperial city logo stamped on the face.",
                         value=self.amt,
                         magical=self.magical)


class Weapon(Item):
    """The base class for all weapons"""

    def __init__(self, name, description, value, damage1H, damage2H, dmgType, dmgMod, vers, thrown, magical):
        self.damage1H = damage1H
        self.damage2H = damage2H
        self.dmgType = dmgType
        self.dmgMod = dmgMod
        self.vers = vers
        self.thrown = thrown
        super().__init__(name, description, value, magical)

    def __repr__(self):
        return f"{self.name}\n=====\n{self.description}\nValue: {self.value}" \
               f"\nDamage: One Handed - {self.damage1H}" \
               f"\nDamage: Two Handed - {self.damage2H}\n" \
               f"Damage Type: {self.dmgType}\nMagical: {self.magical}.\n"


class Armor(Item):
    """The base class for all armor"""

    def __init__(self, name, description, value, grade, ac, disadvantage, dmgRes, magical):
        self.grade = grade
        self.ac = ac
        self.stealthDis = disadvantage
        self.dmgRes = dmgRes
        super().__init__(name, description, value, magical)

    def __repr__(self):
        if self.stealthDis:
            return f"{self.name}\n=====\n{self.description}\nValue: {self.value}\n" \
                   f"AC: {self.ac}\nMagical: {self.magical}"
        elif not self.stealthDis:
            return f"{self.name}\n=====\n{self.description}\nValue: {self.value}\n" \
                   f"AC: {self.ac}\nDisadvantage on Stealth checks: {self.stealthDis}\nMagical: {self.magical}"


# Different types of coins
GP1 = Money("Gold", 1, False)
SP1 = Money("Silver", 1, False)
CP1 = Money("Copper", 1, False)

rock = Weapon(
    name="Rock",
    description="A fist-sized rock, suitable for bludgeoning.",
    value="No value",
    damage1H=randint(1, 6),
    damage2H=0,
    dmgType=DamageType.CRUSHING,
    dmgMod=DamageMod.STRENGTH,
    vers=False,
    thrown=True,
    magical=False
)
tornRags = Armor(
    name="Torn Rags",
    description="A ripped and worn-out outfit.",
    value=0,
    grade="Light",
    ac=0,
    disadvantage=True,
    dmgRes="None",
    magical=False
)

# user = Player("Jamie", "Male", 29, "Elf", "Mage")
user = Player(*startGame())


class Begin(Scene):
    name = "Kanjin - An RPG Text Adventure"
    desc = "Welcome to Kanjin.\nThis game is based off of the D&D SRD ruleset, and will see you, our adventurer, " \
           "exploring, adventuring, and plundering.\n\nTo play Kanjin is very simple.\nRead the instructions, " \
           "understand the scene, and take control.\nAll directional queues will be given and clear but you " \
           "can also use specific commands at almost anytime when you give your input.\n" \
           "After character creation, type 'Help' to view a list of commands and explanations."

    def enter(self):
        # user.new_char()
        pass


class CaveEntrance(Scene):
    name = "Cave Entrance"
    descTrue = "You are standing at the entryway to a cave."
    descFalse = "You return to the cave entrance."
    has_visited = False

    def enter(self):
        typingPrint(f'\n===========\n'
                    f'{self.name}', time)
        if not self.has_visited:
            typingPrint(self.descTrue, time)
        else:
            typingPrint(self.descTrue, time)

        action = input(">> ")

        if action == "shoot!":
            return "death"
        elif action == "dodge":
            return "pod"
        else:
            print("error")
            return "central corridor"

    # def action(self):
    #     pass


class A1(Scene):

    def __init__(self):
        self.name = "Laser Weapon Armory"
        self.descrip = "Shelves and cases line the walls of this room. Weapons of every description " \
                       "fill the shelves and cases. There is a digital keypad set into the wall."

    def enter(self):
        print(f'\n===========\n'
              f'{self.name}'
              f'\n===========\n'
              f'{self.descrip}')

    def action(self):
        pass


# class Death(Scene):
#
#     def __init__(self):
#         self.name = "You have died!"
#         self.descrip = "Your spirit leaves swiftly as your body collapses."
#
#     quips = [
#         "You died. You kinda suck at this.",
#         "Your Mom would be proud...if she were smarter.",
#         "Such a loser.",
#         "I have a small puppy that's better at this.",
#         "You're worse than your Dad's jokes."
#     ]
#
#     def enter(self):
#         print(Death.quips[randint(0, len(self.quips) - 1)])
#         exit(1)
#
#     def action(self):
#         pass
#
#
# class TheBridge(Scene):
#
#     def __init__(self):
#         self.name = "The Bridge"
#         self.descrip = "Clearly this is a central command station of the spaceship. A wide view screen shows the" \
#                        " stars against a black curtain of empty space."
#
#     def enter(self):
#         print(f'\n===========\n'
#               f'{self.name}'
#               f'\n===========\n'
#               f'{self.descrip}')
#
#     def action(self):
#         pass


class Tutorial(Scene):
    name = "Tutorial Level"
    descrip = ("There are certain commands that will be available almost anytime you are able to type,\n"
               "such as viewing your inventory, checking your equipped items, and also changing them.\n"
               "You can also view your stats including your current and max hp.\n")

    def enter(self):
        print(f'\n===========\n'
              f'{self.name}'
              f'\n===========\n'
              f'{self.descrip}')
        action = input(">> ")

        if action == "shoot!":
            print("result")
            return "death"
        elif action == "dodge":
            print("result")
            return "pod"
        else:
            print("error")
            return "central corridor"

    def action(self):
        pass


# Map tells us where we are and where we can go
# it does not make us move - Engine does that 
class Map(object):
    scenes = {
        'Cave entrance': CaveEntrance(),
        'A1': A1(),
        # 'death': Death(),
        # 'bridge': TheBridge(),
        'Tutorial': Tutorial(),
        "begin": Begin()
    }

    def __init__(self, start_scene_key):
        self.start_scene_key = start_scene_key
        # above we make a local var named start_scene_key
        # start_scene_key remains unchanged throughout the game 

    def next_scene(self, scene_name):
        val = Map.scenes.get(scene_name)
        # above is how we get value out of the dictionary named scenes 
        return val
        # this function can be called repeatedly in the game, 
        # unlike opening_scene, which is called only ONCE 

    def opening_scene(self):
        return self.next_scene(self.start_scene_key)
        # this function exists only for starting, using the first 
        # string we passed in ('corridor')
        # it combines the previous 2 functions and is called only once 
        # (called in Engine) 


# print("\nType one of the following words: pod, corridor, bridge, armory, death.")
seed = "begin"  # or input("> ")
# this is for testing only - get the "seed" for the map

mymap = Map(seed)  # instantiate a new Map object w/ one arg
mygame = Engine(mymap)  # instantiate a new Engine object w/ one arg
mygame.play()  # call function from that Engine instance
