import random

class GameObject:
    '''
    This is a base class for game objects which all other item 'types'
    are based on.

    You shouldn't use this base class to generate objects unless it is an interactionless
    item, instead use a class that inherits <class GameObject> 
    '''
    id = ""
    name = ""
    description = ""
    can_pickup = False
    can_place = False
    spawnable = {}  # Place Map class as key and probability as value that an item will spawn in a room
    on_interaction = None  # The function in the class that is reponsible for carrying out an interaction
    equippable = False
    usable = False
    conflict = False  # This state is changed if the id has been updated due to two items having the same id

    def __init__(self, id, name, description, pickup=True, place=True):
        '''
        This is the init method, so the method which is called when a new GameObject is generated
        When creating a new GameObject, pass values through GameObject() so that they become properties
        '''

        self.can_pickup = pickup
        self.can_place = place
        self.description = description
        self.name = name
        self.id = id

    def get_name_capitalised(self):
        '''
        Call this function if you want to get the item name starting with a capital

        Useful for if you're going to put the item at the start of a sentence
        '''

        return self.name[0].upper() + self.name[1:]

    def inspect(self):
        print(self.get_name_capitalised())
        print(self.description + "\n")

    def _interaction(self):
        '''
        If you want an item with a special purpose (other than a generic purpose, like a weapon)
        you should inherit GameObject and implement this method, also setting on_interaction to this method
        '''

        pass

    def dispose(self, rooms, player):
        # Destroys the item from anywhere where it may exist in the game space
        location = self.get_location(rooms, player)

        if location == "INVENTORY":
            place_in_list = player.inventory.index(self)
            del player.inventory[place_in_list]

        elif location == "EQUIPPED":
            for category in player.equipped:
                item = player.equipped[category]
                if self == item:
                    player.equipped[category] = None

        else:
            place_in_list = rooms[location].items.index(self)
            del rooms[location].items[place_in_list]

    def get_location(self, rooms, player):
        '''
        This function will return the location of a specific (not general) item.
        As it only expects an item to exist in one location at a time, it will not
        return multiple locations. You may also pass just inventory in the case of an interaction
        of an item in the inventory

        If you want multiple versions of one item, consider creating many instances of the class
        '''

        if rooms:
            for room in rooms:
                if self in rooms[room].items:
                    return room

        for category in player.equipped:
            item = player.equipped[category]
            if self == item: return "EQUIPPED"
        
        # If the item can't be found in a room, it must be in someone's inventory
        for item in player.inventory:
            if self == item:
                return "INVENTORY"

        # Else, return nothing as this object does not exist in the game space
        return None



class DurableItem(GameObject):
    '''
    DurableItem is an intermediate class for items (such as defense or attack items) that have durability
    '''
    durability = 0

    def __init__(self, id, name, description, dura, pickup=True, place=True):
        super().__init__(id, name, description, pickup, place)

        self.durability = dura

    def checkout_dura(self, player, rooms, durability_loss):
        '''
        For DurableItem, checkout_dura(rooms, player, x: int) will take an integer and remove the amount
        of durability from the item. If the item reaches 0, then the class should be removed from the game space
        '''
        self.durability -= durability_loss

        if self.durability <= 0:
            print(f"{self.name} has broken!")
            self.dispose(rooms, player)
        else:
            print(f'{self.get_name_capitalised()} has lost {durability_loss} durability!')

class Weapon(DurableItem):
    attack = 0
    wobble = 0.1
    equippable = True

    def __init__(self, id, name, description, attack, dura, wobble=0, pickup=True, place=True):
        super().__init__(id, name, description, dura, pickup, place)
        self.attack = attack
        self.wobble = wobble

    def inspect(self):
        print(self.get_name_capitalised())
        print(self.description)
        print("It is a weapon, and has " + str(self.attack) + " attack power.")
        print(f'It has a wobble of +/- {self.wobble * 100}%.')
        print("It has " + str(self.durability) + " durability left.\n")

    def get_attack(self):
        '''
        This function is designed to get the attach amount of usage of this item
        Wobble is a percentage of how much the damage will variate around the base value attack
        For example, a wobble of 0.1 (10%) means the value will variate -10% / +10% the attack value
        '''
        
        lower_value = round(self.attack - (self.attack * self.wobble))
        higher_value = round(self.attack + (self.attack * self.wobble))
        return random.randint(lower_value, higher_value)



class Defense(DurableItem):
    defense = 0  # defense is a base value between 0-1 which is a percentage of how much the damage will be reduced
    wobble = 0.0  # wobble is percentage variation (+/-) around the baseline defense percentage
    equippable = True

    def __init__(self, id, name, description, defense, dura, wobble=0.0, pickup=True, place=True):
        super().__init__(id, name, description, dura, pickup, place)

        self.defense = defense
        self.wobble = wobble

    def inspect(self):
        print(self.name)
        print(self.description)
        print("It is a defense item, and has " + str(self.defense) + " defense power.")
        print("It has a wobble of +/- " + str(self.wobble * 100) + "%.")
        print("It has " + str(self.durability) + " durability left.\n")

    def get_defense(self, base_defense, damage_amount):
        '''
        get_defense(x: int) is designed to see how much of the attack will be defended, aligning to the % defense and
        how much wobble will be applied
        '''

        p_defense_lower = round(self.defense - (self.defense * self.wobble))
        p_defense_higher = round(self.defense + (self.defense * self.wobble))
        percentage_defense = random.randint(p_defense_lower, p_defense_higher)

        final_damage = damage_amount - base_defense - (damage_amount * percentage_defense)
        return final_damage

# magic items increase luck
class Magic(GameObject):
    magic = 0
    on_interaction_description = "to use this item"
    equippable = True

    def __init__(self, id, name, description, magic, pickup=True, place=True):
        super().__init__(id, name, description, pickup, place)

        self.magic = magic

    def inspect(self):
        print(self.name)
        print(self.description)
        print("It is a magic item, and has " + str(self.magic) + " magic power.\n")

class HealthItem(GameObject):
    health = 0
    on_interaction_description = "to use this item"
    usable = True

    def __init__(self, id, name, description, health, pickup=True, place=True):
        super().__init__(id, name, description, pickup, place)

        self.health = health

    def inspect(self):
        print(self.name)
        print(self.description)
        print("It is a health item, and has " + str(self.health) + " health value.\n")

    def _interaction(self, player):
        # We don't need to heal if the player's health is more than 100
        if player.hp >= player.base_hp:
            print(f"You can't use {self.name} - your health is already at 100 HP!")

        else:
            player.hp += self.health

            # Don't allow the player's health to get higher than 100
            if player.hp >= player.base_hp:
                player.hp = player.base_hp

            print(f'Success, your health is now {player.hp} HP!')

            self.dispose(None, player)

    on_interaction = _interaction

class Chest(GameObject):
    contains = None   # contains is expected to be a subclass of GameObject, a list or a NoneType
    requires = None   # None means no key or anything is needed, else this should be set to Unlocks class
    opened = False
    on_interaction_description = "to open the {0}"

    def __init__(self, id, name, description, contains, requires=None, pickup=False, place=False):
        super().__init__(id, name, description, pickup, place)

        self.contains = contains
        self.requires = requires
        self.on_interaction_description = self.on_interaction_description.format(self.id)

    def _interaction(self, player):
        # Upon interacting with a chest item, the player should be awarded the items inside
        # One should pass the inventory list to the function and expect to receive the updated inventory back

        if self.opened:
            print(f'This {self.id.upper()} has already been opened')
            return
        else:
            # Check if the player has the item required to unlock the chest, if needed
            if self.requires:
                if not self.requires in player.inventory:
                    print(f"This {self.id.upper()} is locked. You need something to open it...")
                    return

                else:
                    # We need to remove the item if it's an item that destroys itself after use
                    if self.requires.perish_on_open:
                        self.requires.dispose(None, player)

            contains_type = type(self.contains)

            if contains_type == type(None):
                print(f"The {self.id.upper()} was weirdly empty...")

            elif issubclass(contains_type, GameObject): 
                print(f'You found {self.contains.name} in the {self.id.upper()}')

                player.inventory.append(self.contains)
                
            elif contains_type == list:
                # Creates a line that lists out all the items found in the chest. Adds them to inv
                items_string = "You found "
                player.inventory += self.contains

                for item in self.contains:
                    items_string += f'{item.name}, '

                print(items_string[:-2] + f' in the {self.id.upper()}')

            self.opened = True
            return

    on_interaction = _interaction

class Unlocks(GameObject):
    '''
    Unlocks is a special class which is used to unlock locked items, like chests
    '''
    perish_on_open = False   # Should this item get destroyed when it unlocks its target?

    def __init__(self, id, name, description, perish_on_open=False, pickup=True, place=True):
        super().__init__(id, name, description, pickup, place)

        self.perish_on_open = perish_on_open

class GiantSword(Weapon):
    def __init__(self, id):
        super().__init__(
            id,
            name="giant sword",
            description="The swords barely fits in your hand. Surely a relic of a grand warrior",
            attack=40,
            dura=100,
            wobble=0.12,
        )

class WornDagger(Weapon):
    def __init__(self, id):
        super().__init__(
            id,
            name="worn dagger",
            description="The blade of this thing has seen better days",
            attack=20,
            dura=25,
            wobble=0.3,
        )

class PlasticSpoon(Weapon):
    def __init__(self, id):
        super().__init__(
            id,
            name="plastic spoon",
            description="I don't think it'll have much use defending against monsters...",
            attack=2,
            dura=10,
            wobble=0.8,
        )

class GolfClub(Weapon):
    def __init__(self, id):
        super().__init__(
            id,
            name="golf club",
            description="Turns out the people who lived here were avid golf players.",
            attack=10,
            dura=50,
            wobble=0.2,
        )

class Handgun(Weapon):
    def __init__(self, id):
        super().__init__(
            id,
            name="old handgun",
            description="Does this thing even work?",
            attack=30,
            dura=10,
            wobble=0.9,
        )

class Whip(Weapon):
    def __init__(self, id):
        super().__init__(
            id,
            name="creaky whip",
            description="I don't want to know what this was used for... horses?",
            attack=5,
            dura=20,
            wobble=0.1,
        )

class Crocs(Defense):
    def __init__(self, id):
        super().__init__(
            id,
            name="pair of crocs",
            description="I doubt these will actually soften a blow. Yet their ugly style may distract the monsters",
            defense=0.05,
            dura=20,
            wobble=0.15,
        )

class SturdyShield(Defense):
    def __init__(self, id):
        super().__init__(
            id,
            name="sturdy shield",
            description="Seems like it can take a blow",
            defense=0.5,
            dura=100,
            wobble=0.05,
        )

class Apron(Defense):
    def __init__(self, id):
        super().__init__(
            id,
            name="well used apron",
            description="Covered in cooking stains. Clearly not cleaned all too well.",
            defense=0.1,
            dura=30,
            wobble=0.2,
        )

class SuitofArmour(Defense):
    def __init__(self, id):
        super().__init__(
            id,
            name="pristine suit of armour",
            description="Whoever owned this before took great care of it. It is still shiny!",
            defense=0.6,
            dura=150,
            wobble=0.05,
        )

class Cookies(HealthItem):
    def __init__(self, id):
        super().__init__(
            id,
            name="pack of cookies",
            description="Who cares how old these are? All I can think of is the taste of chocolate chip cookies!",
            health=40,
        )

class FirstAidKit(HealthItem):
    def __init__(self, id):
        super().__init__(
            id,
            name='first aid kit',
            description="This can heal any ailment at a whim!",
            health=100,
        )

class Apple(HealthItem):
    def __init__(self, id):
        super().__init__(
            id, 
            name="apple",
            description="An apple a day keeps the doctor away.",
            health=20,
        )

class StrangePotion(Magic):
    def __init__(self, id):
        super().__init__(
            id,
            name="strange potion",
            description="A vile of unknown contents. Doesn't smell of anything.",
            magic=20,
        )

class Wine(Magic):
    def __init__(self, id):
        super().__init__(
            id,
            name="bottle of wine",
            description="Bourgogne Pinot Noir 2003. Exquisite.",
            magic=10,
        )

class Ambrosia(HealthItem):
    def __init__(self, id):
        super().__init__(
            id,
            name="plate of ambrosia",
            description="Dine like a God",
            health=1000,
        )

class AppleCrumble(HealthItem):
    def __init__(self, id):
        super().__init__(
            id,
            name="tray of apple crumble",
            description="Sounds like a right treat",
            health=50,
        )

class SeaFood(HealthItem):
    def __init__(self, id):
        super().__init__(
            id,
            name="plate of seafood",
            description="I hope I don\'t have an allergic reaction...",
            health=25,
        )

class Chocolate(HealthItem):
    def __init__(self, id):
        super().__init__(
            id,
            name="bar of chocolate",
            description="The back reads: \"Expires January 1989\"",
            health=10,
        )

class WoodernSword(Weapon):
    def __init__(self, id):
        super().__init__(
            id,
            name="woodern sword",
            description="Huh? This is a children's sword...",
            attack=15,
            dura=30,
            wobble=0.1
        )

class BattleAxe(Weapon):
    def __init__(self, id):
        super().__init__(
            id,
            name="battle axe",
            description="This could be lethal",
            attack=50,
            dura=105,
            wobble=0.05
        )

spawnable_items = {
    "weapons": [ GiantSword, WornDagger, PlasticSpoon, GolfClub, Handgun, Whip, WoodernSword, BattleAxe ],
    "defense": [ Crocs, SturdyShield, Apron, SuitofArmour ],
    "health": [ Cookies, FirstAidKit, Apple, Ambrosia, AppleCrumble, SeaFood, Chocolate ],
    "magic": [ StrangePotion, Wine ]
}