import random
import os

def asOrderedList(d):
    ordered = []
    for key in d:
        ordered.append([key, d[key]])
        ordered.sort()
    return ordered

def clear():
    os.system('cls' if os.name == 'nt' else 'clear')

class Player:
    def __init__(self, w):
        self.world = w
        self.name = input("What is your creature's name? ")
        print("Is your creature a carnivore or an herbivore?")
        self.diet = input("Herbivores need only find fruit to survive, while carnivores must kill their prey to have meat. ").lower()
        while self.diet != 'carnivore' and self.diet!= 'c' and self.diet != 'herbivore' and self.diet != 'h':
            self.diet = input('Invalid response. Choose "carnivore" or "herbivore." ')
        if self.diet == 'h':
            self.diet = 'herbivore'
        elif self.diet == 'c':
            self.diet = 'carnivore'

        w.add_player(self)

        self.location = random.choice(self.world.squares)
        while self.location.terrain == 'lake':
            self.location = random.choice(self.world.squares)
        self.home = self.location # The player's home base will be their starting location.

        self.alive = True

        self.hunger = 100 # If self.hunger reaches 0, the player's health will decrease at each update.
        self.maxHealth, self.health = 50, 50
        self.maxStrength, self.strength = 10, 10
        self.maxSociability, self.sociability = 10, 10
        self.maxSpeed, self.speed = 10, 10

        self.healthLoss = 2
        self.hungerLoss = 5
        self.speedPenalty = 0
        self.socPenalty = 0

        self.intelligence = 0
        self.experience = 0

        self.abilities = []
        self.inventory = {}
        self.inventorySize = 0
        self.inventoryCap = 10
        self.invweight = 0
        self.maxinvweight = 20

        self.availabledirs = []
        self.dirstring = ''

        self.defeated = 0
        self.friends = []
        self.ally = None

        self.m = 0
        self.going = ''

        self.conch = True
        self.conchUses = 0

    def update(self):
        if self.conchUses >= 2:
            self.conch = False
            self.conchUses = 0
            del self.inventory['conch shell']
            input('Unfortunately, you dropped your conch shell while using it. It is destroyed.')
        if self.going != '':
            print('You go ' + self.going + '.')
            self.going = ''
        self.dirstring = ''
        for elem in self.availabledirs:
            if self.dirstring == '':
                self.dirstring = elem
            else:
                self.dirstring += ', ' + elem
        if self.ally != None:
            if self.ally.hostility < 0:
                self.ally.hostility = 0

        # We reset the penalties in order to implement the terrain and weather effects
        self.healthLoss = 2
        self.hungerLoss = 5
        self.speedPenalty = 0
        self.socPenalty = 0
        if self.hunger > 100:
            self.hunger = 100

        # Terrain effects
        if self.location.terrain == "desert":
            self.hungerLoss += 5
        elif self.location.terrain == "hills":
            self.speedPenalty += self.maxSpeed // 4
        elif self.location.terrain == "tundra":
            self.healthLoss += 3

        # Weather effects
        if self.world.weather == "rainy":
            self.speedPenalty += self.maxSpeed // 4
        elif self.world.weather == "hailing":
            self.healthLoss += 3
        elif self.world.weather == "snowy":
            self.socPenalty += self.maxSociability // 4
        elif self.world.weather == "drought":
            self.hungerLoss += 5

        # You gain health at home
        if self.location == self.home:
            healthGained = self.maxHealth // 2
            self.health += healthGained
            if self.health > self.maxHealth:
                self.health = self.maxHealth
                healthGained -= self.health - self.maxHealth
            print('You gain ' + str(healthGained) + ' health at your home base!')
        else: # Your stats (may) go down elsewhere
            self.health -= self.healthLoss
            self.sociability -= self.socPenalty
            if self.sociability < 0: # No negative stats
                self.sociability = 0
            self.speed -= self.speedPenalty
            if self.speed < 0:
                self.speed = 0
            print()
            print('You lose ' + str(self.healthLoss) + ' health from the terrain and weather.')
            print('Your sociability decreases by ' + str(self.socPenalty) + ' points.')
            print('Your speed decreases by ' + str(self.speedPenalty) + ' points.')
        if self.health <= 0:
            self.die()

        if self.hunger > 0:
            if 'Improved metabolism' in self.abilities: # The "Improved metabolism" ability makes you become hungrier less quickly
                self.hunger -= 5
            else:
                self.hunger -= self.hungerLoss
            if self.hunger < 0:
                self.hunger = 0
        elif self.hunger == 0: # If they player is starving...
            print()
            r = random.randint(0,3) # then they will randomly take damage to health, strength, sociability, or speed
            if r == 0:
                hungerPenalty = self.health // 10
                self.health -= hungerPenalty
                print("You're starving! You lose " + str(hungerPenalty) + " health!")
            elif r == 1:
                hungerPenalty = self.strength // 10
                self.strength -= hungerPenalty
                print("You're starving! You lose " + str(hungerPenalty) + " strength!")
            elif r == 2:
                hungerPenalty = self.sociability // 10
                self.sociability -= hungerPenalty
                print("You're starving! You lose " + str(hungerPenalty) + " sociability!")
            elif r == 3:
                hungerPenalty = self.speed // 10
                self.speed -= hungerPenalty
                print("You're starving! You lose " + str(hungerPenalty) + " speed!")
        if self.hunger < 0: # You can't have negative hunger!
            self.hunger = 0

        self.availabledirs = []
        for exit in self.location.exits:
            if exit != None:
                self.availabledirs.append(exit)

        if 'meat' in self.inventory:
            self.m += 1
            if self.m == 6: # If you go long enough with meat in your inventory, then it will rot all your food
                self.invweight -= self.inventory['meat'] * self.world.itemWeights['meat']
                self.inventorySize -= self.inventory['meat']
                del self.inventory['meat']
                if 'fruit' in self.inventory:
                    self.invweight -= self.inventory['fruit'] * self.world.itemWeights['fruit']
                    self.inventorySize -= self.inventory['fruit']
                    del self.inventory['fruit']

                self.m = 0
                print('Oh no! You carried meat in your bag for too long. All of your food has gone rotten.')
        else:
            self.m = 0

    def showInventory(self):
        #clear()
        print('Your inventory contains the following items:')
        orderedInventory = asOrderedList(self.inventory)
        for kvp in orderedInventory:
            weight = self.world.itemWeights[kvp[0]] * kvp[1]
            print('\t' + kvp[0] + ' x' + str(kvp[1]) + ', ' + str(weight) + ' weight')

    def showAbilities(self):
        print('You have the following abilities:')
        for ab in self.abilities:
            print('\t' + ab)

    def evolve(self):
        clear()
        print('Health increase: 5 exp')
        print('Stomach size increase: 5 exp')
        print('Strength increase: 5 exp')
        print('Sociability increase: 5 exp')
        print('Speed increase: 5 exp')
        print('Intelligence increase – unlock new upgrades: 5 exp')
        print('Pouches – can carry more items: 5 exp')
        print('Stronger back – can carry heaver items: 5 exp')
        if self.diet != 'omnivore':
            print('Omnivorous diet — eat any food you find: 10 exp')
        if 'Metabolism increase' not in self.abilities:
            print('Metabolism increase – hunger increases more slowly: 10 exp')
        if 'Fat reserves' not in self.abilities:
            print('Fat reserves – reduced penalty when starving: 10 exp')
        if 'Semiaquatic' not in self.abilities:
            print('Semiaquatic – access watery terrain: 10 exp')
        if 'use items' not in self.abilities:
            print('Use items: 10 exp')
        if self.intelligence >= 8 and 'Item use' not in self.abilities:
            print('Item use: 10 exp')
        if self.intelligence >= 13 and 'Item use' in self.abilities and 'Flexible responding' not in self.abilities:
            print('Flexible responding – more options when you engage with other creatures: 20 exp') # Idk, maybe players will be able to change whether they want to socialize or attack. Also, I just thought that if the player attacks a creature, then the creature's hostility should go up
        if self.intelligence >= 20 and 'Flexible responding' in self.abilities:
            print('Fire: 30 exp')

        print()
        print('Go back.')
        print()
        print('You have ' + str(self.experience) + ' experience points.')
        print()
        transactionCompleted = False
        while not transactionCompleted:
            choice = input('What would you like to improve? ')
            if choice.lower() == 'health increase':
                if self.experience >= 5:
                    self.maxHealth += 8
                    self.health = self.maxHealth
                    self.experience -= 5
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'use items':
                if self.experience >= 10:
                    self.abilities.append('use items')
                    self.abilities.append('Item use')
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'stomach size increase':
                if self.experience >= 5:
                    self.maxHunger += 5
                    self.hunger = self.maxHunger
                    self.experience -= 5
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'strength increase':
                if self.experience >= 5:
                    self.maxStrength += 3
                    self.strength = self.maxStrength
                    self.experience -= 5
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'sociability increase':
                if self.experience >= 5:
                    self.maxSociability += 3
                    self.sociability = self.maxSociability
                    self.experience -= 5
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'speed increase':
                if self.experience >= 5:
                    self.maxSpeed += 3
                    self.speed = self.maxSpeed
                    self.experience -= 5
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'intelligence increase':
                if self.experience >= 5:
                    self.intelligence += 4
                    self.experience -= 5
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'pouches':
                if self.experience >= 5:
                    self.inventoryCap += 3
                    self.experience -= 5
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'stronger back':
                if self.experience >= 5:
                    self.maxinvweight += 3
                    self.experience -= 5
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'omnivore':
                if self.experience >= 10:
                    self.diet = 'omnivore'
                    self.abilities.append('omnivore')
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'metabolism increase':
                if self.experience >= 15:
                    self.abilities.append('improved metabolism')
                    self.experience -= 15
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'fat reserves':
                if self.experience >= 15:
                    self.abilities.append('fat reserves')
                    self.experience -= 15
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'semiaquatic':
                if self.experience >= 15:
                    self.abilities.append('semiaquatic')
                    self.experience -= 15
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'item use':
                if self.experience >= 15:
                    self.abilities.append('item use')
                    self.experience -= 15
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'flexible responding':
                if self.experience >= 25:
                    self.abilities.append('flexible responding')
                    self.experience -= 30
                    transactionCompleted = True
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'fire':
                if self.experience >= 35:
                    self.abilities.append('fire')
                    victory()
                else:
                    print('Not enough experience. Try again.')
            elif choice.lower() == 'go back':
                transactionCompleted = True

    def fillStats(self, n):
        healthGained = self.maxHealth // n
        self.health += healthGained
        if self.health > self.maxHealth:
            self.health = self.maxHealth
            print('Your health is now at max!')
        else:
            print('You gain ' + str(healthGained) + ' health.')

        strengthGained = self.maxStrength // n
        self.strength += strengthGained
        if self.strength > self.maxStrength:
            self.strength = self.maxStrength
            print('Your strength is now at max!')
        else:
            print('You gain ' + str(strengthGained) + ' strength.')

        sociabilityGained = self.maxSociability // n
        self.sociability += sociabilityGained
        if self.sociability > self.maxSociability:
            self.sociability = self.maxSociability
            print('Your sociability is now at max!')
        else:
            print('You gain ' + str(sociabilityGained) + ' sociability.')

        speedGained = self.maxSpeed // n
        self.speed += speedGained
        if self.speed > self.maxSpeed:
            self.speed = self.maxSpeed
            print('Your speed is now at max!')
        else:
            print('You gain ' + str(sociabilityGained) + ' speed.')

    def die(self):
        self.alive = False

    def eat(self, food):
        while food != 'fruit' and food != 'meat':
            food = input("Sorry, I didn't catch that. Do you want to eat fruit or meat? ")
        print()
        if food not in self.location.items and food not in self.inventory:
            print("There's no " + food + " for you to eat here!")
            return
        if self.location.terrain == 'forest':
            if 'big stick' not in self.inventory:
                print("You'll need a stick or something to get the food out of the trees.")
                return
            elif 'big stick' in self.inventory and 'Item use' not in self.abilities:
                print('You need to unlock the "item use" ability before that stick will help you!')
                return
        if food == 'fruit':
            if self.diet == 'herbivore' or self.diet == 'omnivore':
                if 'fruit' in self.location.items:
                    self.location.items['fruit'] -= 1
                    if self.location.items['fruit'] <= 0:
                        del self.location.items['fruit']
                elif 'fruit' in self.inventory:
                    self.inventory['fruit'] -= 1
                    self.inventorySize -= 1
                    self.invweight -= self.world.itemWeights['fruit']
                    if self.inventory['fruit'] <= 0:
                        del self.inventory['fruit']
                print('You eat the fruit.')
                print()
                self.fillStats(2)
                self.hunger += 20
                return True
            else:
                print("You can't eat that! Bleh!")
                return
        elif food == 'meat':
            if self.diet == 'carnivore' or self.diet == 'omnivore':
                if 'meat' in self.location.items:
                    self.location.items['meat'] -= 1
                    if self.location.items['meat'] <= 0:
                        del self.location.items['meat']
                elif 'meat' in self.inventory:
                    self.inventory['meat'] -= 1
                    self.inventorySize -= 1
                    self.invweight -= self.world.itemWeights['meat']
                    if self.inventory['meat'] <= 0:
                        del self.inventory['meat']
                print('You eat the meat.')
                self.fillStats(1)
                self.hunger += 30
                return True
            else:
                print("You can't eat that! Bleh!")
                return

    def pickup(self, item):
        if self.location.terrain == 'forest':
            if 'big stick' not in self.inventory:
                if item == 'big stick':
                    print("Good thing that's a big stick...you're just able to pull it out of a tree without needing another big stick!")
                else:
                    print("You'll need a stick or something to get the item out of the trees.")
                    return
            elif 'big stick' in self.inventory and 'Item use' not in self.abilities and 'use items' not in self.abilities:
                print('You need to unlock the "item use" ability before that stick will help you!')
                return
        if item in self.location.items:
            if self.invweight + self.world.itemWeights[item] > self.maxinvweight:
                s = self.invweight + self.world.itemWeights[item] - self.maxinvweight
                print("This item is too heavy for you to pick up! Leave it behind or use the 'drop' command to free up " + str(s) + " kg in your inventory. ")
            elif self.inventorySize < self.inventoryCap:
                if item in self.location.items:
                    if item in self.inventory:
                        self.inventory[item] += 1
                        self.invweight += self.world.itemWeights[item]
                    else:
                        self.inventory[item] = 1
                        self.invweight += self.world.itemWeights[item]
                    self.inventorySize += 1
                    self.location.items[item] -= 1
                    if self.location.items[item] <= 0:
                        del self.location.items[item]
                    print('You pick up the ' + item + '.')
            else:
                print('Your inventory is already full!')
        else:
            print('There is no such item here.')

    def drop(self,item):
        if item in self.inventory:
            if item in self.location.items:
                self.location.items[item] += 1
            else:
                self.location.items[item] = 1
            self.inventory[item] -= 1
            self.inventorySize -= 1
            self.invweight -= self.world.itemWeights[item]
            if self.inventory[item] <= 0:
                del self.inventory[item]
            print('You drop the ' + item + '.')
        else:
            print('There is no such item in your inventory.')

    def inspect(self, item):
        if item == 'creature' or item in self.world.creatureNames:
            if self.location.creature == None:
                print('There is no creature here.')
            else:
                print("The creature is a " + self.location.creature.name + '!')
                print("It has " + str(self.location.creature.health) + " health, " + str(self.location.creature.speed) + " speed, " + str(self.location.creature.strength) + " strength, and " + str(self.location.creature.hostility) + " hostility.")
            if self.ally != None:
                print("Your ally is a " + self.ally.name + '!')
                print("It has " + str(self.ally.health) + " health, " + str(self.ally.speed) + " speed, " + str(self.ally.strength) + " strength, and " + str(self.ally.hostility) + " hostility.")
            #$$$
        elif item in self.location.items or item in self.inventory:
            if item == 'sticky sap':
                print("Sticky sap from a tree. Use it during an encounter to decrease the other creature's speed.")
            elif item == 'poison berries':
                print("Poisonous berries. Use them during an encounter to decrease the other creature's health and strength.")
            elif item == 'big leaf':
                print('A large, surprisingly sturdy leaf. It could protect you from the weather.')
            elif item == 'healing salve':
                print('A healing salve from a plant. Use it to restore your stats.')
            elif item == 'flowers':
                print("Pretty flowers. Use them during an encounter to decrease the other creature's hostility.")
            elif item == 'big stick':
                print('A large stick. It will let you get items out of trees.')
            elif item == 'nesting materials':
                print('Materials for building a nest. Use them to move your home base.')
            elif item == 'fruit':
                print('A fruit. If you are an herbivore or omnivore, then eating this will reduce hunger and restore your stats.')
            elif item == 'meat':
                print('A piece of meat. If you are a carnivore or omnivore, then eating this will reduce hunger and restore your stats.')
            elif item == 'seaweed':
                print('A big nasty ball of seaweed. Use it during a fight to distract an animal and reduce its strength.')
            elif item == 'driftwood':
                print('A large piece of driftwood. Use it during a fight to try to block your opponent\'s attacks.')
            elif item == 'conch shell':
                print('A conch shell. Use it on land to calm the creatures around you and temporarily decrease their hostility.')
        else:
            print('There is nothing by that name here.')

    def useItem(self, item):
        if 'Item use' not in self.abilities and 'use items' not in self.abilities:
            print('You need to unlock the "Item use" ability before you can use items!')
            return False
        else:
            if item in self.inventory:
                if item != 'conch shell':
                    print('You use the ' + item + '.')
                if item == 'fruit':
                    self.eat(item)
                elif item == 'meat':
                    self.eat(item)
                elif item == 'healing salve':
                    print('All your stats have been restored!')
                    self.fillStats(1)
                    self.inventory['healing salve'] -= 1
                    self.inventorySize -= 1
                    self.invweight -= self.world.itemWeights['healing salve']
                    if self.inventory['healing salve'] <= 0:
                        del self.inventory['healing salve']
                elif item == 'big leaf':
                    print('You are now protected from the weather!')
                    self.world.weather = 'clear'
                    self.inventory['big leaf'] -= 1
                    self.inventorySize -= 1
                    self.invweight -= self.world.itemWeights['big leaf']
                    if self.inventory['big leaf'] <= 0:
                        del self.inventory['big leaf']
                elif item == 'nesting materials':
                    if self.location == self.home:
                        print("You're already at home!")
                    else:
                        print('You have established a new home at the current location!')
                        self.home = self.location
                        self.inventory['nesting materials'] -= 1
                        self.inventorySize -= 1
                        self.invweight -= self.world.itemWeights['nesting materials']
                        if self.inventory['nesting materials'] <= 0:
                            del self.inventory['nesting materials']
                elif item == 'conch shell':
                    if self.location.terrain == 'lake':
                        input("You can't use that here! Sea animals don't care for conch shells. Go to land to use this.")
                    else:
                        print('The sound of the conch calms the creatures around you, and briefly decreases their hostility!')
                        self.world.hostilityDec = True
                        self.conchUses += 1
                else:
                    print("Now's not the time to use that!")
                    return False
                return True
            elif item in self.location.items:
                print('You must pick an item up before you can use it!')
            else:
                print("There's no item by that name in your inventory.")
            return False

    def useBattleItem(self, item, target):
        if item in self.inventory:
            if item == 'sticky sap':
                target.speed -= target.speed // 2
                self.inventory['sticky sap'] -= 1
                self.inventorySize -= 1
                self.invWeight -= self.world.itemWeights['sticky sap']
                if self.inventory['sticky sap'] <= 0:
                    del self.inventory['sticky sap']
            elif item == 'poison berries':
                target.health -= target.health // 4
                target.strength -= target.strength // 4
                self.inventory['poison berries'] -= 1
                self.inventorySize -= 1
                self.invWeight -= self.world.itemWeights['poison berries']
                if self.inventory['poison berries'] <= 0:
                    del self.inventory['poison berries']
            elif item == 'healing salve':
                self.fillStats()
                self.inventory['healing salve'] -= 1
                self.inventorySize -= 1
                self.invWeight -= self.world.itemWeights['healing salve']
                if self.inventory['healing salve'] <=0:
                    del self.inventory['healing salve']
            elif item == 'flowers':
                target.hostility -= target.hostility // 3
                self.inventory['flowers'] -= 1
                self.inventorySize -= 1
                self.invWeight -= self.world.itemWeights['flowers']
                if self.inventory['flowers'] <=0:
                    del self.inventory['flowers']
            elif item == 'seaweed':
                target.strength -= random.randint(2,5)
                self.inventory['seaweed'] -= 1
                self.inventorySize -= 1
                self.invWeight -= self.world.itemWeights['seaweed']
                if self.inventory['seaweed'] <=0:
                    del self.inventory['seaweed']
            elif item == 'driftwood':
                self.inventory['driftwood'] -= 1
                self.inventorySize -= 1
                self.invWeight -= self.world.itemWeights['driftwood']
                if self.inventory['driftwood'] <=0:
                    del self.inventory['driftwood']
                return True
            elif item == 'seaweed':
                target.strength -= 2*target.level
                self.inventory['seaweed'] -= 1
                self.inventorySize -= 1
                self.invWeight -= self.world.itemWeights['seaweed']
                if self.inventory['seaweed'] <=0:
                    del self.inventory['seaweed']

    def go(self, dir):
        if dir.lower() == 'north':
            if self.location.exits['north'] == None:
                print('You may not go north. Try again.')
                return False
            elif self.location.exits['north'].terrain == 'lake':
                if 'semiaquatic' not in self.abilities: # You have to have the "Semiaquatic" skill to access lake terrain
                    print('There is water in that direction, and you cannot swim. Try again.')
                    return False
                else:
                    self.location = self.location.exits['north']
                    return True
            else:
                self.going = 'north'
                self.location = self.location.exits['north']
                return True
        if dir.lower() == 'south':
            if self.location.exits['south'] == None:
                print('You may not go south. Try again.')
                return False
            elif self.location.exits['south'].terrain == 'lake':
                if 'semiaquatic' not in self.abilities:
                    print('There is water in that direction, and you cannot swim. Try again.')
                    return False
                else:
                    self.location = self.location.exits['south']
                    return True
            else:
                self.going = 'south'
                self.location = self.location.exits['south']
                return True
        if dir.lower() == 'east':
            if self.location.exits['east'] == None:
                print('You may not go east. Try again.')
                return False
            elif self.location.exits['east'].terrain == 'lake':
                if 'semiaquatic' not in self.abilities:
                    print('There is water in that direction, and you cannot swim. Try again.')
                    return False
                else:
                    self.location = self.location.exits['east']
                    return True
            else:
                self.going = 'east'
                self.location = self.location.exits['east']
                return True
        if dir.lower() == 'west':
            if self.location.exits['west'] == None:
                print('You may not go west. Try again.')
                return False
            elif self.location.exits['west'].terrain == 'lake':
                if 'semiaquatic' not in self.abilities:
                    print('There is water in that direction, and you cannot swim. Try again.')
                    return False
                else:
                    self.location = self.location.exits['west']
                    return True
            else:
                self.going = 'west'
                self.location = self.location.exits['west']
                return True
        else:
            print("Sorry, I don't understand. Choose north, south, east or west.")
            return False

    def stats(self):
        if self.diet == 'herbivore':
            print("You are an herbivore.")
        elif self.diet == 'carnivore':
            print("You are a carnivore.")
        print("Your location is " + str(self.location.coordinates))
        print("Hunger = " + str(self.hunger))
        print("Health = " + str(self.health))
        print('Type: \n \t "all stats" for all stats; \n \t "inventory" for abilities and inventory; \n \t "location" for details on location')

    def allstats(self):
        if self.diet == 'herbivore':
            print("You are an herbivore.")
        elif self.diet == 'carnivore':
            print("You are a carnivore.")
        print("You may travel " + self.dirstring +".")
        print("You may travel " + str(self.inventorySize))
        print("Hunger = " + str(self.hunger))
        print("Health = " + str(self.health))
        print("Strength = " + str(self.strength))
        print("Sociability = " + str(self.sociability))
        print("Speed = " + str(self.speed))
        print("Intelligence = " + str(self.intelligence))
        print("Abilities = " + str(self.abilities))
        print("Inventory = " + str(self.inventory))
        print("Inventory size = " + str(self.inventorySize))
        print("Inventory cap = " + str(self.inventoryCap))
        print("Inventory weight = " + str(self.invweight))
        print("Inventory max weight = " + str(self.maxinvweight))
        print("Friends: " + str(len(self.friends)))
        print("Defeated: " + str(self.defeated))

    def attack(self, creature):
        if self.location.creature == None:
            print('There is no creature here.')
            return
        else:
            fleeing = False
            defense = False
            while self.health > 0 and creature.health > 0:
                clear()
                print('Creature health: ' + str(creature.health))
                print('Creature strength: ' + str(creature.strength))
                print('Creature hostility: ' + str(creature.hostility))
                print()
                print('Health: ' + str(self.health))
                print('Strength: ' + str(self.strength))
                print('You may:')
                print('\t attack')
                if 'item use' in self.abilities:
                    print('\t use item')
                print('\t flee')
                choice = input('What will you do? ')
                choice = choice.lower()
                while choice != 'attack' and choice != 'flee' and 'item' != choice:
                    if 'item use' in self.abilities:
                        print('Invalid command. Choose "attack," "item" or "flee."')
                    else:
                        print('Invalid command. Choose "attack" or "flee."')
                    choice = input('What will you do? ')
                while 'item' in choice.lower() and len(self.inventory) == 0:
                    print('Your inventory is empty!')
                    choice = input('What will you do? ')
                while 'item' in choice.lower() and 'item use' not in self.abilities == 0:
                    print('You can\'t do that!')
                    choice = input('What will you do? ')

                print()

                if self.speed >= creature.speed:
                    # If the player is faster, the player goes first
                    if choice.lower() in 'attack':
                        attackStrength = random.randint(self.strength // 2, self.strength)
                        print("You attack!")
                        print("The creature takes " + str(attackStrength) + " damage!")
                        print("The creature's hostility increases!")
                        creature.health -= self.strength
                        creature.hostility += 3
                    elif 'item' in choice.lower():
                        print("Items: ")
                        orderedInventory = asOrderedList(self.inventory)
                        for kvp in orderedInventory:
                            print('\t' + kvp[0] + ' x' + str(kvp[1]))
                        itemChoice = input('Pick an item. ')
                        if self.useBattleItem(itemChoice, creature):
                            defense = True
                    elif choice.lower() in 'flee':
                        print("You flee!")
                        break

                    creatureAttackChance = creature.hostility * .1
                    creatureChoice = random.random()
                    if creatureChoice < creature.fleeRate:
                        print("The creature flees!")
                        fleeing = True
                        break
                    elif creatureChoice < creatureAttackChance + creature.fleeRate:
                        creatureAttackStrength = random.randint(creature.strength // 2, creature.strength)
                        print("The creature attacks!")
                        if defense == True:
                            if random.random() < 0.5:
                                creatureAttackStrength = 0
                                print('Your driftwood barrier protects you!')
                        print("You take " + str(creatureAttackStrength) + " damage!")
                        self.health -= creatureAttackStrength
                    else:
                        print(random.choice(['The creature does nothing!', 'The creature awaits your next move.', 'The creature is watching you closely...']))

                else:
                    # If the creature is faster, the creature goes first
                    creatureAttackChance = creature.hostility * .1
                    creatureChoice = random.random()
                    if creatureChoice < creature.fleeRate:
                        print("The creature flees!")
                        fleeing = True
                        break
                    elif creatureChoice < creatureAttackChance + creature.fleeRate:
                        creatureAttackStrength = random.randint(creature.strength // 2, creature.strength)
                        print("The creature attacks!")
                        if defense == True:
                            if random.random() < 0.5:
                                creatureAttackStrength = 0
                                print('Your driftwood barrier protects you!')
                        print("You take " + str(creatureAttackStrength) + " damage!")
                        self.health -= creatureAttackStrength
                    else:
                        creatureChoice = random.choice(['The creature does nothing!', 'The creature awaits your next move.', 'The creature is watching you closely...'])

                    if choice.lower() in 'attack':
                        attackStrength = random.randint(self.strength // 2, self.strength)
                        print("You attack!")
                        print("The creature takes " + str(attackStrength) + " damage!")
                        print("The creature's hostility increases!")
                        creature.health -= self.strength
                        creature.hostility += 3
                    elif 'item' in choice.lower():
                        print("Items: ")
                        orderedInventory = asOrderedList(self.inventory)
                        for kvp in orderedInventory:
                            print('\t' + kvp[0] + ' x' + str(kvp[1]))
                        itemChoice = input('Pick an item. ')
                        if self.useBattleItem(itemChoice, creature):
                            defense = True
                    elif choice.lower() in 'flee':
                        print("You flee!")
                        break
                    if type(creatureChoice) == str:
                        # If the creature does nothing, we say so at the end of the turn.
                        print(creatureChoice)

                    print()

                    if self.ally != None:
                        if random.choice([True, False]):
                            if choice.lower() in 'attack':
                                attackStrength = random.randint(self.ally.strength // 2, self.ally.strength)
                                print("Your ally attacks!")
                                print("The creature takes " + str(attackStrength) + " damage!")
                                print("The creature's hostility increases!")
                                creature.health -= attackStrength
                                creature.hostility += 3

                input('Press enter to continue.')

            print()
            if fleeing == True:
                r = random.choice(self.world.squares)
                if creature in self.world.aquaticCreatures:
                    while r.creature != None and r.terrain != 'lake':
                        r.random.choice(self.world.squares)
                    r.creature = creature
                    creature.location = r
                    self.location.creature = None
                else:
                    while r.creature != None:
                        r = random.choice(self.world.squares)
                    r.creature = creature
                    creature.location = r
                    self.location.creature = None
            elif creature.health <= 0 and self.health > 0:
                print("You've defeated the creature!")
                print("You gain " + str(creature.experience) + " experience!")
                self.experience += creature.experience
                self.defeated += 1
                self.location.creature = None
                self.location.items['meat'] = random.randint(1,3)
                if random.random() < .15:
                    if self.location == 'lake':
                        itemDrop = random.choice(self.world.waterItems)
                    else:
                        itemDrop = random.choice(self.world.landItems)
                    print('The creature dropped an item!')
                    if itemDrop in self.location.items:
                        self.location.items[itemDrop] += 1
                    else:
                        self.location.items[itemDrop] = 1
            elif self.health <= 0:
                self.die()
            return True

    def befriend(self, creature):
        if self.location.creature == None:
            print('There is no creature here.')
            return
        else:
            fleeing = False
            defense = False
            while self.health > 0 and creature.hostility > 0:
                clear()
                print('Creature health: ' + str(creature.health))
                print('Creature strength: ' + str(creature.strength))
                print('Creature hostility: ' + str(creature.hostility))
                print()
                print('Health: ' + str(self.health))
                print('Sociability: ' + str(self.sociability))
                print('You may:')
                print('\t befriend')
                if 'item use' in self.abilities:
                    print('\t use item')
                print('\t flee')
                choice = input('What will you do? ')
                while choice.lower() not in 'befriend' and choice.lower() not in 'flee' and 'item' not in choice.lower():
                    if 'item use' in self.abilities: #why say "not in 'befriend'?
                        print('Invalid command. Choose "befriend," "item" or "flee."')
                    else:
                        print('Invalid command. Choose "befriend" or "flee."')
                    choice = input('What will you do? ')
                if 'item' in choice.lower() and len(self.inventory) == 0:
                    print('Your inventory is empty!')
                    choice = input('What will you do? ')
                if 'item' in choice.lower() and 'item use' not in self.abilities == 0:
                    print('You can\'t do that!')
                    choice = input('What will you do? ')

                print()

                if self.speed >= creature.speed:
                    # If the player is faster, the player goes first
                    if choice.lower() in 'befriend' and choice.lower() != 'f':
                        befriendSuccess = random.randint(self.sociability // 2, self.sociability)
                        print("You try to befriend the creature!")
                        print("The creature's hostility decreases!")
                        creature.hostility -= befriendSuccess
                    elif 'item' in choice.lower():
                        print("Items: ")
                        orderedInventory = asOrderedList(self.inventory)
                        for kvp in orderedInventory:
                            print('\t' + kvp[0] + ' x' + str(kvp[1]))
                        itemChoice = input('Pick an item. ')
                        if self.useBattleItem(itemChoice, creature):
                            defense = True
                    elif choice.lower() in 'flee':
                        print("You flee!")
                        break

                    creatureAttackChance = creature.hostility * .1
                    creatureChoice = random.random()
                    if creatureChoice < creature.fleeRate:
                        print("The creature flees!")
                        fleeing = True
                        break
                    elif creatureChoice < creatureAttackChance + creature.fleeRate:
                        creatureAttackStrength = random.randint(creature.strength // 2, creature.strength)
                        print("The creature attacks!")
                        if defense == True:
                            if random.random() < 0.5:
                                creatureAttackStrength = 0
                                print('Your driftwood barrier protects you!')
                        print("You take " + str(creatureAttackStrength) + " damage!")
                        self.health -= creatureAttackStrength
                    else:
                        print(random.choice(['The creature does nothing!', 'The creature awaits your next move.', 'The creature is watching you closely...']))

                else:
                    # If the creature is faster, the creature goes first
                    creatureAttackChance = creature.hostility * .1
                    creatureChoice = random.random()
                    if creatureChoice < creature.fleeRate:
                        print("The creature flees!")
                        break
                        fleeing = True
                    elif creatureChoice < creatureAttackChance + creature.fleeRate:
                        creatureAttackStrength = random.randint(creature.strength // 2, creature.strength)
                        print("The creature attacks!")
                        if defense == True:
                            if random.random() < 0.5:
                                creatureAttackStrength = 0
                                print('Your driftwood barrier protects you!')
                        print("You take " + str(creatureAttackStrength) + " damage!")
                        self.health -= creatureAttackStrength
                    else:
                        creatureChoice = random.choice(['The creature does nothing!', 'The creature awaits your next move.', 'The creature is watching you closely...'])

                    if choice.lower() in 'befriend' and choice.lower() != 'f':
                        befriendSuccess = random.randint(self.sociability // 2, self.sociability)
                        print("You try to befriend the creature!")
                        print("The creature's hostility decreases!")
                        creature.hostility -= befriendSuccess
                    elif 'item' in choice.lower():
                        print("Items: ")
                        orderedInventory = asOrderedList(self.inventory)
                        for kvp in orderedInventory:
                            print('\t' + kvp[0] + ' x' + str(kvp[1]))
                        itemChoice = input('Pick an item. ')
                        if self.useBattleItem(itemChoice, creature):
                            defense = True
                    elif choice.lower() in 'flee':
                        print("You flee!")
                        break
                    if type(creatureChoice) == str:
                        # If the creature does nothing, we say so at the end of the turn.
                        print(creatureChoice)

                    print()

                    if self.ally != None:
                        if random.choice([True, False]):
                            if choice.lower() in 'befriend':
                                allySociability = 100 // self.ally.hostility
                                if allySociability < 0:
                                    allySociability = 0
                                befriendSuccess = random.randint(allySociability // 2, allySociability)
                                print("Your ally helps befriend the creature!")
                                print("The creature's hostility decreases!")
                                creature.hostility -= befriendSuccess
                input('Press enter to continue.')

            print()
            if fleeing == True:
                r = random.choice(self.world.squares)
                if creature in self.world.aquaticCreatures:
                    while r.creature != None and r.terrain != 'lake':
                        r.random.choice(self.world.squares)
                    r.creature = creature
                    creature.location = r
                    self.location.creature = None
                else:
                    while r.creature != None:
                        r = random.choice(self.world.squares)
                    r.creature = creature
                    creature.location = r
                    self.location.creature = None
            elif creature.hostility <= 0 and self.health > 0:
                print("You've befriended the creature!")
                print("You gain " + str(creature.experience) + " experience!")
                self.experience += creature.experience
                self.friends.append(creature)
                creature.befriended = True
                if random.random() < .15:
                    if self.location == 'lake':
                        itemDrop = random.choice(self.world.waterItems)
                    else:
                        itemDrop = random.choice(self.world.landItems)
                    print('The creature dropped an item!')
                    if itemDrop in self.location.items:
                        self.location.items[itemDrop] += 1
                    else:
                        self.location.items[itemDrop] = 1
            elif self.health <= 0:
                self.die()
            return True

    def flexibleResponse(self, creature):
        if self.location.creature == None:
            print('There is no creature here.')
            return
        else:
            fleeing = False
            defense = False
            while self.health > 0 and (creature.hostility > 0 or creature.health > 0):
                clear()
                print('Creature health: ' + str(creature.health))
                print('Creature strength: ' + str(creature.strength))
                print('Creature hostility: ' + str(creature.hostility))
                print()
                print('Health: ' + str(self.health))
                print('Strength: ' + str(self.strength))
                print('Sociability: ' + str(self.sociability))
                print('You may:')
                print('\t attack')
                print('\t befriend')
                if 'item use' in self.abilities:
                    print('\t use item')
                print('\t flee')
                choice = input('What will you do? ')
                while choice.lower() not in 'attack' and choice.lower() not in 'befriend' and choice.lower() not in 'flee' and 'item' not in choice.lower():
                    if 'item use' in self.abilities:
                        print('Invalid command. Choose "attack," "befriend," "item" or "flee."')
                    else:
                        print('Invalid command. Choose "attack," "befriend," or "flee."')
                    choice = input('What will you do? ')
                if 'item' in choice.lower() and len(self.inventory) == 0:
                    print('Your inventory is empty!')
                    choice = input('What will you do? ')
                if 'item' in choice.lower() and 'item use' not in self.abilities == 0:
                    print('You can\'t do that!')
                    choice = input('What will you do? ')

                print()

                if self.speed >= creature.speed:
                    # If the player is faster, the player goes first
                    if choice.lower() in 'attack':
                        attackStrength = random.randint(self.strength // 2, self.strength)
                        print("You attack!")
                        print("The creature takes " + str(attackStrength) + " damage!")
                        print("The creature's hostility increases!")
                        creature.health -= attackStrength
                        creature.hostility += 3
                    elif choice.lower() in 'befriend' and choice.lower() != 'f':
                        befriendSuccess = random.randint(self.sociability // 2, self.sociability)
                        print("You try to befriend the creature!")
                        print("The creature's hostility decreases!")
                        creature.hostility -= befriendSuccess
                    elif 'item' in choice.lower():
                        print("Items: ")
                        orderedInventory = asOrderedList(self.inventory)
                        for kvp in orderedInventory:
                            print('\t' + kvp[0] + ' x' + str(kvp[1]))
                        itemChoice = input('Pick an item. ')
                        if self.useBattleItem(itemChoice, creature):
                            defense = True
                    elif choice.lower() in 'flee':
                        print("You flee!")
                        break

                    creatureAttackChance = creature.hostility * .1
                    creatureChoice = random.random()
                    if creatureChoice < creature.fleeRate:
                        print("The creature flees!")
                        fleeing = True
                        break
                    elif creatureChoice < creatureAttackChance + creature.fleeRate:
                        creatureAttackStrength = random.randint(creature.strength // 2, creature.strength)
                        print("The creature attacks!")
                        if defense == True:
                            if random.random() < 0.5:
                                creatureAttackStrength = 0
                                print('Your driftwood barrier protects you!')
                        print("You take " + str(creatureAttackStrength) + " damage!")
                        self.health -= creatureAttackStrength
                    else:
                        print(random.choice(['The creature does nothing!', 'The creature awaits your next move.', 'The creature is watching you closely...']))

                else:
                    # If the creature is faster, the creature goes first
                    creatureAttackChance = creature.hostility * .1
                    creatureChoice = random.random()
                    if creatureChoice < creature.fleeRate:
                        print("The creature flees!")
                        fleeing = True
                        break
                    elif creatureChoice < creatureAttackChance + creature.fleeRate:
                        creatureAttackStrength = random.randint(creature.strength // 2, creature.strength)
                        print("The creature attacks!")
                        if defense == True:
                            if random.random() < 0.5:
                                creatureAttackStrength = 0
                                print('Your driftwood barrier protects you!')
                        print("You take " + str(creatureAttackStrength) + " damage!")
                        self.health -= creatureAttackStrength
                    else:
                        creatureChoice = random.choice(['The creature does nothing!', 'The creature awaits your next move.', 'The creature is watching you closely...'])

                    if choice.lower() in 'attack':
                        attackStrength = random.randint(self.strength // 2, self.strength)
                        print("You attack!")
                        print("The creature takes " + str(attackStrength) + " damage!")
                        print("The creature's hostility increases!")
                        creature.health -= attackStrength
                        creature.hostility += 3
                    elif choice.lower() in 'befriend' and choice.lower() != 'f':
                        befriendSuccess = random.randint(self.sociability // 2, self.sociability)
                        print("You try to befriend the creature!")
                        print("The creature's hostility decreases!")
                        creature.hostility -= befriendSuccess
                    elif 'item' in choice.lower():
                        print("Items: ")
                        orderedInventory = asOrderedList(self.inventory)
                        for kvp in orderedInventory:
                            print('\t' + kvp[0] + ' x' + str(kvp[1]))
                        itemChoice = input('Pick an item. ')
                        if self.useBattleItem(itemChoice, creature):
                            defense = True
                    elif choice.lower() in 'flee':
                        print("You flee!")
                        break
                    if type(creatureChoice) == str:
                        # If the creature does nothing, we say so at the end of the turn.
                        print(creatureChoice)

                    print()

                    if self.ally != None:
                        if random.choice([True, False]):
                            if choice.lower() in 'attack':
                                attackStrength = random.randint(self.ally.strength // 2, self.ally.strength)
                                print("Your ally attacks!")
                                print("The creature takes " + str(attackStrength) + " damage!")
                                print("The creature's hostility increases!")
                                creature.health -= attackStrength
                                creature.hostility += 3
                            elif choice.lower() in 'befriend':
                                allySociability = 100 // self.ally.hostility
                                if allySociability < 0:
                                    allySociability = 0
                                befriendSuccess = random.randint(allySociability // 2, allySociability)
                                print("Your ally helps befriend the creature!")
                                print("The creature's hostility decreases!")
                                creature.hostility -= befriendSuccess
                input('Press enter to continue.')

            print()
            if fleeing == True:
                r = random.choice(self.world.squares)
                if creature in self.world.aquaticCreatures:
                    while r.creature != None and r.terrain != 'lake':
                        r.random.choice(self.world.squares)
                    r.creature = creature
                    creature.location = r
                    self.location.creature = None
                else:
                    while r.creature != None:
                        r = random.choice(self.world.squares)
                    r.creature = creature
                    creature.location = r
                    self.location.creature = None
            elif creature.health <= 0 and self.health > 0:
                print("You've defeated the creature!")
                print("You gain " + str(creature.experience) + " experience!")
                self.experience += creature.experience
                self.defeated += 1
                self.location.creature = None
                self.location.items['meat'] = random.randint(1,3)
                if random.random() < .15:
                    if self.location.terrain == 'lake':
                        itemDrop = random.choice(self.world.waterItems)
                    else:
                        itemDrop = random.choice(self.world.landItems)
                    print('The creature dropped an item!')
                    if itemDrop in self.location.items:
                        self.location.items[itemDrop] += 1
                    else:
                        self.location.items[itemDrop] = 1
                input()
            elif creature.hostility <= 0 and self.health > 0:
                print("You've befriended the creature!")
                print("You gain " + str(creature.experience) + " experience!")
                self.experience += creature.experience
                self.friends.append(creature)
                creature.befriended = True
                if random.random() < .15:
                    if self.location.terrain == 'lake':
                        itemDrop = random.choice(self.world.waterItems)
                    else:
                        itemDrop = random.choice(self.world.landItems)
                    print('The creature dropped an item!')
                    if itemDrop in self.location.items:
                        self.location.items[itemDrop] += 1
                    else:
                        self.location.items[itemDrop] = 1
            elif self.health <= 0:
                self.die()
            return True

    def recruit(self):
        if self.location.creature == None:
            print('There is no creature here for you to befriend!')
        elif self.ally != None:
            print('You need to dismiss your ally before you recruit a new one!')
        else:
            if self.location.creature in self.friends:
                self.ally = self.location.creature
                print('You have allied your friend the ' + self.ally.name + '! Your ally will follow you around and fight with you.')
            else:
                print('You must befriend a creature before it will be your ally!')

    def locationDets(self):
        print('Location coordinates: ' + str(self.location.coordinates))
        print('Terrain: ' + self.location.terrain)
        print('Weather: ' + self.location.weather)
        self.location.availableDirs()


class Solution(object):
    def maximalSquare(self, matrix):
        maximal = 0
        m = len(matrix)
        n = len(matrix[0])

        squreSizeMemo = [[0 for i in range(n+1)] for j in range(m+1)]

        for i in range(m-1, -1, -1):
            for j in range(n-1, -1, -1):
                if (matrix[i][j] == "1"):
                    squreSizeMemo[i][j] = min(squreSizeMemo[i+1][j], squreSizeMemo[i][j+1], squreSizeMemo[i+1][j+1]) + 1
                    if maximal < squreSizeMemo[i][j]: maximal = squreSizeMemo[i][j]

        return maximal*maximal

s = Solution()
print(s.maximalSquare([["1","0","1","0","0"],["1","0","1","1","1"],["1","1","1","1","1"],["1","0","0","1","0"]]))


import streamlit as st
import scraper

stock = ['AAPL', 'AMZN', 'INTC', 'GOOG', 'CSCO']

search_btn = False

if st.sidebar.checkbox("Deseja procurar alguma ação?"):
    symbol = st.sidebar.text_input("Dígite o símbolo da ação desejada")
    if len(symbol) == 4:
      new_company_info = scraper.fetch_info(symbol)
      stock.append(symbol)
    search_btn = st.sidebar.button('Buscar')

def get_new(symbol):
  if new_company_info != None:
    st.header(symbol)
    new_graph = scraper.fetch_company_data_history(chart_data, symbol)
    my_chart = st.line_chart(new_graph[chart_data], height=400, width=400)
    st.header(f'Informações: {stock_symbol_info}')
    st.text(scraper.fetch_info(symbol))
  return my_chart

stock_symbol_info = st.sidebar.selectbox(
  'Informações da ação',
  ['', *stock],
  )

stock_chart = st.sidebar.multiselect(
  'Ações para mostrar no gráfico',
  scraper.DEFAULT_COMPANIES,
  default=scraper.DEFAULT_COMPANIES
  )

chart_data = st.sidebar.radio(
  'Gráfico do volume ou da cotação de fechamento ajustado', ('Volume', 'Adj Close')
  )

def main():
  if search_btn: get_new(str(symbol))
  else:
    st.header('Gráficos')
    if len(stock_chart) > 0:
      scraper.render_graph(chart_data, [*stock_chart])
    if (stock_symbol_info):
      st.header(f'Informações: {stock_symbol_info}')
      st.text(scraper.fetch_info(stock_symbol_info))

if __name__ == "__main__":
    main()


from jinja2 import Environment, FileSystemLoader, select_autoescape

env = Environment(
    loader=FileSystemLoader('templates'),
    autoescape=select_autoescape(['html', 'xml'])
)

def render_run_plan(workout, routes, sunrise_sunset, forecast, dress):
    template = env.get_template('run_plan.html')
    return template.render(workout=workout, routes=routes, sunrise_sunset=sunrise_sunset,
                           forecast=forecast, dress=dress)


import ast
import os

# Third party imports
from setuptools import find_packages, setup

HERE = os.path.abspath(os.path.dirname(__file__))

def get_version(module='spyder_reports'):
    """Get version."""
    with open(os.path.join(HERE, module, '_version.py'), 'r') as f:
        data = f.read()
    lines = data.split('\n')
    for line in lines:
        if line.startswith('VERSION_INFO'):
            version_tuple = ast.literal_eval(line.split('=')[-1].strip())
            version = '.'.join(map(str, version_tuple))
            break
    return version

def get_description():
    """Get long description."""
    with open(os.path.join(HERE, 'README.rst'), 'r') as f:
        data = f.read()
    return data

REQUIREMENTS = ['spyder>=3.2.0', 'pweave', 'matplotlib']

setup(
    name='spyder-reports',
    version=get_version(),
    keywords=['Spyder', 'Plugin'],
    url='https://github.com/spyder-ide/spyder-reports',
    license='MIT',
    author='Spyder Project Contributors',
    author_email='admin@spyder-ide.org',
    description='Spyder-IDE plugin for Markdown reports using Pweave.',
    long_description=get_description(),
    packages=find_packages(exclude=['contrib', 'docs', 'tests*']),
    install_requires=REQUIREMENTS,
    include_package_data=True,
    package_data={'spyder_reports.utils': ['*.md']},
    classifiers=[
        'Development Status :: 4 - Beta',
        'Intended Audience :: Developers',
        'Intended Audience :: Science/Research',
        'License :: OSI Approved :: MIT License',
        'Operating System :: MacOS',
        'Operating System :: Microsoft :: Windows',
        'Operating System :: POSIX :: Linux',
        'Programming Language :: Python :: 2.7',
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6'
    ])


import ast
from django.db.models import Q
from django.db import transaction
from django.core.exceptions import ValidationError as DjangoValidationError
from rest_framework.permissions import IsAuthenticated
from rest_framework.exceptions import ValidationError
from rest_framework.generics import ListAPIView

from common_config.api_code import HTTP_201_CREATED, HTTP_400_BAD_REQUEST, HTTP_OK, HTTP_500_INTERNAL_SERVER_ERROR
from common_config.api_message import ADD_SERVICE, UPDATE_SERVICE, INVALID_PAGE_SIZE, \
    DELETE_SERVICE, EXTRA_QUERY_PARAMS, INVALID_PAGE_NUMBER, INVALID_BOOLEAN_FLAG, BLANK_PARAM, INVALID_SORT_BY, \
    INVALID_SORT_BY_FIELD_PARAM, REQUIRED_PARAMS, INVALID_STATUS_FILTER, INVALID_SERVICE_IMAGE_ID
from common_config.constant import SERVICE_CATEGORY
from common_config.logger.logging_handler import logger
from common_config.generics import get_object_or_404
from utils.api_response import APIResponse
from utils.permissions import IsAuthorized
from utils.pagination import Pagination
from utils.views.service import ServiceListCreateMixin, ServiceRetrieveUpdateDeleteMixin

from services.models.service import Service
from services.serializers.service import ServiceCreateSerializer, ServiceViewSerializer, ServiceListSerializer, \
    ServiceUpdateSerializer
from price_groups.tasks.store_service import linked_services_to_store_task, linked_service_and_options_to_store_task

class ServiceListCreateView(ServiceListCreateMixin):
    """
    An Api View which provides a method to add new service or view list services.
    Accepts the following GET/POST header parameters: access token
    Returns the success/fail message.
    """
    queryset = Service.objects.all()
    serializer_class = ServiceCreateSerializer
    pagination_class = Pagination
    permission_classes = (IsAuthenticated, IsAuthorized,)
    permission_required = ('add_service', 'list_service',)
    query_filter_params = ["is_active", "include_deleted", "page", "page_size", "status", "sort_by", "search",
                           "sort_by_field"]

    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.errors = dict()
        self.params = dict()

    def validate_query_param(self, page_size, page):
        # check pre define query parameter if contain extra query param then raise error message
        if len(self.params) > 0 and not all(key in self.query_filter_params for key in self.params.keys()):
            extra_param = [key for key in self.params if key not in self.query_filter_params]
            self.errors.setdefault("message", []).append(EXTRA_QUERY_PARAMS.format(extra_param))

        # check page size must  number
        if "page_size" in self.params and not page_size.isnumeric():
            self.errors.setdefault("page_size", []).append(INVALID_PAGE_SIZE)

        if "page" in self.params and not page.isnumeric():
            self.errors.setdefault("page", []).append(INVALID_PAGE_NUMBER)

        if "status" in self.params:
            try:
                self.params['status'] = ast.literal_eval(self.params['status'])
            except Exception as err:
                self.errors.setdefault("status", []).append(INVALID_STATUS_FILTER.format(
                    type(self.params['status']).__name__))

            if not isinstance(self.params['status'], list):
                self.errors.setdefault("status", []).append(INVALID_STATUS_FILTER.format(
                    type(self.params['status']).__name__))

        if "is_active" in self.params:
            try:
                eval(self.params['is_active'])
            except Exception as err:
                self.errors.setdefault("is_active", []).append(
                    INVALID_BOOLEAN_FLAG.format("is_active", self.params['is_active']))

        if "sort_by" in self.params:
            if self.params['sort_by'] == "":
                self.errors.setdefault("sort_by", []).append(BLANK_PARAM)
            elif self.params['sort_by'].lower() not in ["asc", "desc"]:
                self.errors.setdefault("sort_by", []).append(INVALID_SORT_BY.format(self.params['sort_by']))

        if "search" in self.params and self.params['search'] == "":
            self.errors.setdefault("search", []).append(BLANK_PARAM)

        if "sort_by_field" in self.params and self.params['sort_by_field'] not in ["name", "description", "status",
                                                                                "price"]:
            self.errors.setdefault("sort_by_field", []).append(INVALID_SORT_BY_FIELD_PARAM)

        if "sort_by_field" in self.params and "sort_by" not in self.params:
            self.errors.setdefault("sort_by", []).append(REQUIRED_PARAMS)

        if "include_deleted" in self.params:
            try:
                eval(self.params['include_deleted'])
            except Exception as err:
                self.errors.setdefault("include_deleted", []).append(
                    INVALID_BOOLEAN_FLAG.format("include_deleted", self.params['include_deleted']))
            else:
                if not self.errors:
                    # validate view soft deleted object view permission
                    IsAuthorized.has_include_deleted_permission(self.request, "list_service")

    def filter_queryset(self, params):
        filter_kwargs = {'is_active': True}
        if "is_active" in params and params['is_active'] in ['False']:
            filter_kwargs['is_active'] = False

        if "status" in params:
            filter_kwargs['status__in'] = params.get('status')

        if "sort_by_field" in params:
            if params['sort_by_field'] == "name":
                sort_by_field = "name"

            elif params['sort_by_field'] == "status":
                STATUS_CHOICE = Service.STATUS_CHOICES

                # sort service status
                service_status = sorted(STATUS_CHOICE, key=lambda tup: tup[1], reverse=True)

                # get sorted status
                sorted_list = [x[0] for x in service_status]

                from django.db.models import Case, When

                # sort by field
                sort_by_field = Case(
                    *[When(status=status, then=pos) for pos, status in enumerate(sorted_list)])

            elif params['sort_by_field'] == "description":
                sort_by_field = "description"

            else:
                sort_by_field = "price"
        else:
            sort_by_field = "created_on"

        query = Q()

        if "search" in params:
            query = Q(name__icontains=params['search']) | Q(description__icontains=params['search']) | \
                    Q(Q(category_tags__name__icontains=params['search']) &
                      Q(category_tags__entity_type=SERVICE_CATEGORY))

        for item in filter_kwargs:
            query = query & Q(**{item: filter_kwargs[item]})

        if "sort_by" in params and params['sort_by'] == "asc":
            return self.queryset.filter(query).order_by(sort_by_field)

        return self.queryset.filter(query).order_by(sort_by_field).reverse()

    def get(self, request, *args, **kwargs):
        """
        In this method validate request query parameters and filter and return service list.
        return success/error message.
        """
        self.params = request.query_params.copy()
        page_size = self.params.get('page_size', None)
        page = self.params.get('page', None)

        # validate sales order params
        self.validate_query_param(page_size, page)

        if self.errors:
            return APIResponse(self.errors, HTTP_400_BAD_REQUEST)

        error_msg, status_code = None, None

        try:
            # filter and get all service based on query params
            queryset = self.filter_queryset(self.params)
        except DjangoValidationError as err:
            error_msg, status_code = err.args[0], HTTP_400_BAD_REQUEST
        except Exception as err:
            logger.error("Unexpected error occurred :  %s.", err.args[0])
            error_msg, status_code = err.args[0], HTTP_500_INTERNAL_SERVER_ERROR

        if error_msg is not None:
            return APIResponse({"message": error_msg}, status_code)

        is_pagination = False

        # set api request page number
        if page is not None:
            self.paginator.page = page
            is_pagination = True

            # set request api page size number
            if page_size is None:
                page_size = 10

            self.paginator.page_size = page_size

        return self.paginator.generate_response(queryset, ServiceListSerializer, request, is_pagination)

    @transaction.atomic
    def post(self, request, *args, **kwargs):
        """
        In this method validate service from data and created new service.
        return success/error message.
        """
        request_data = request.data.copy()

        try:
            # validate service and service option fields value
            serializer, validate_data = self.validate(request_data)
        except ValidationError as err:
            return APIResponse(err.args[0], HTTP_400_BAD_REQUEST)
        except Exception as err:
            logger.error("Unexpected error occurred :  %s.", err)
            return APIResponse({"message": err.args[0]}, HTTP_400_BAD_REQUEST)

        # get last transaction save point id
        sid = transaction.savepoint()

        try:
            # add new service
            instance, priceGroupServiceIdList = serializer.create(validate_data)
        except ValidationError as err:
            # roll back transaction if any exception occur while adding service and service option
            transaction.savepoint_rollback(sid)
            return APIResponse(err.args[0], HTTP_400_BAD_REQUEST)
        except Exception as err:
            # roll back transaction if any exception occur while adding service and service option
            transaction.savepoint_rollback(sid)
            logger.error("Unexpected error occurred :  %s.", err.args[0])
            return APIResponse({"message": err.args[0]}, HTTP_400_BAD_REQUEST)

        # convert model object into json
        data = ServiceViewSerializer(instance).data
        data['message'] = ADD_SERVICE

        if priceGroupServiceIdList:
            # system user assign services to store
            linked_services_to_store_task.delay({'priceGroupServiceIdList': priceGroupServiceIdList})

        return APIResponse(data, HTTP_201_CREATED)

class ServiceRetrieveUpdateDeleteView(ServiceRetrieveUpdateDeleteMixin):
    """
    An Api View which provides a method to get, update and delete service.
    Accepts the following GET/PUT/DELETE header parameters: access token
    Returns the success/fail message.
    """

    queryset = Service.objects.all()
    serializer_class = ServiceUpdateSerializer
    permission_classes = (IsAuthenticated, IsAuthorized,)
    permission_required = ('change_service', 'view_service', 'delete_service',)
    lookup_field = 'pk'

    def get_object(self):
        queryset = self.filter_queryset(self.get_queryset())

        # Perform the lookup filtering.
        lookup_url_kwarg = self.lookup_url_kwarg or self.lookup_field
        filter_kwargs = {self.lookup_field: self.kwargs[lookup_url_kwarg]}

        # get object
        obj = get_object_or_404(queryset, "service_id", **filter_kwargs)
        return obj

    def get(self, request, *args, **kwargs):
        # get service object
        instance = self.get_object()

        # serialize service objects
        serializer = ServiceViewSerializer(instance)
        return APIResponse(serializer.data, HTTP_OK)

    @transaction.atomic
    def put(self, request, *args, **kwargs):
        # get service object
        instance = self.get_object()

        # get request form data
        request_data = request.data

        try:
            # validate service and service option fields value
            serializer, validated_data = self.validate(request_data)
        except ValidationError as err:
            return APIResponse(err.args[0], HTTP_400_BAD_REQUEST)
        except Exception as err:
            logger.error("Unexpected error occurred :  %s.", err)
            return APIResponse({"message": err.args[0]}, HTTP_400_BAD_REQUEST)

        if "del_images" in validated_data and len(validated_data['del_images']) > 0:
            del_images = validated_data.get("del_images")
            errors = {}
            images = [x.id for x in instance.images.all()]

            for x in del_images:
                if x.id not in images:
                    errors.setdefault("del_images", []).append(INVALID_SERVICE_IMAGE_ID.format(x.id))

            if len(errors) > 0:
                return APIResponse(errors, HTTP_400_BAD_REQUEST)

        # get last transaction save point id
        sid = transaction.savepoint()

        try:
            # update service
            instance, priceGroupServiceIdList = serializer.update(instance, validated_data)
        except ValidationError as err:
            logger.error("validation error occurred 1 :  %s.", err.args[0])
            # roll back transaction if any exception occur while adding service and service option
            transaction.savepoint_rollback(sid)
            return APIResponse(err.args[0], HTTP_400_BAD_REQUEST)
        except Exception as err:
            logger.error("Unexpected error occurred 2 :  %s.", err.args[0])
            # roll back transaction if any exception occur while update service and service option
            transaction.savepoint_rollback(sid)
            return APIResponse({"message": err.args[0]}, HTTP_400_BAD_REQUEST)

        # convert model object into json
        data = ServiceViewSerializer(instance).data
        data['message'] = UPDATE_SERVICE

        task_payload = {}
        if priceGroupServiceIdList:
            task_payload['priceGroupServiceIdList'] = priceGroupServiceIdList

        if "createOptionIds" in request.session:
            task_payload['createOptionIds'] = request.session['createOptionIds']
            del request.session['createOptionIds']

        if task_payload:
            # system user assign services to store
            linked_service_and_options_to_store_task.delay(task_payload)

        return APIResponse(data, HTTP_OK)

    @transaction.atomic
    def delete(self, request, *args, **kwargs):
        # validate and get service object
        instance = self.get_object()

        # get last transaction save point id
        sid = transaction.savepoint()

        try:
            # soft delete service
            instance.delete()
        except Exception as err:
            # roll back transaction if any exception occur while delete service
            transaction.savepoint_rollback(sid)
            logger.error("Unexpected error occurred :  %s.", err.args[0])
            return APIResponse({"message": err.args[0]}, HTTP_400_BAD_REQUEST)

        return APIResponse({'message': DELETE_SERVICE}, HTTP_OK)

class UpdateServiceOptionSequenceNumber(ListAPIView):
    """
    An Api View which provides a method to update service option sequence number.
    Accepts the following GET header parameters: access token
    Returns the success/fail message.
    """

    queryset = Service.objects.all()
    permission_classes = (IsAuthenticated, IsAuthorized,)
    permission_required = ('change_service',)

    def get(self, request, *args, **kwargs):
        services = Service.objects.all()

        for service_obj in services:

            options = service_obj.options.all().order_by("id")
            sequence = 1

            for option_obj in options:
                option_obj.sequence = sequence
                option_obj.save()

                # update price group service option
                for price_list_option_obj in option_obj.price_group_options.all():
                    price_list_option_obj.sequence = sequence
                    price_list_option_obj.save()

                sequence += 1

        return APIResponse({'message': "Service option sequence updated successfully."}, HTTP_OK)