"""
Your name: Leslie Butler

All of your code must go in this file.
"""
import json
import random
import math
import sys
import time
import itertools

def load_game_info(path):
    """
    Load gameInfo.json into a python dictionary.

    :return: Dictionary
    :precondition: gameInfo.json exists and is formatted correctly.
    :postcondition: Will return gameInfo.json as a python dictionary.
    """
    with open(path, "r", encoding="utf8") as file:
        output = file.read()
        final_json = json.loads(output)
        return final_json

def load_game_map(path):
    """
    Load the gamemap into a 2d list.

    :return: 2d list
    :precondition: map.txt exists and is a rectangle with a blank line at the end.
    :postcondition: Will return map.txt as a 2d list of all the characters.
    """
    with open(path, "r", encoding="utf8") as file:
        output = file.readlines()
        for i in range(len(output)):
            output[i] = list(output[i])[:-1]

        final_map = []
        for i in range(len(output)):
            row = []
            for pos in range(len(output[i])):
                row.append(output[i][pos])
            final_map.append(row)
        return final_map

def populate_game_map(game_info, game_map, player_position):
    """
    Populates the game map with enemies and items.

    :param game_info: game_info dictionary.
    :param game_map: game_map list.
    :param player_position: player_position list.
    :return: game_info dictionary.
    :precondition: game_info, game_map, player_position are formatted correctly.
    :postcondition: Will return the game_info dictionary updated to have items and enemies in rooms.
    """
    for row in range(len(game_map)):
        game_info["Map"]["Game Map"][row] = {}
        for room in range(len(game_map[row])):
            game_info["Map"]["Game Map"][row][room] = {}
            game_info["Map"]["Game Map"][row][room]["Piece"] = game_map[row][room]
            game_info["Map"]["Game Map"][row][room]["Enemies"] = []
            game_info["Map"]["Game Map"][row][room]["Description"] = random.choice(game_info["Map"]["Descriptions"])
            populate_enemies(game_info, row, room, player_position, game_map)
            populate_items(game_info, row, room, player_position)
    return game_info

def populate_enemies(game_info, row, room, player_position, game_map):
    """
    Will decide what enemies (if any) will be placed in the room.

    :param game_info: game_info dictionary
    :param row: int
    :param room: int
    :param player_position: player_position list
    :param game_map: game_map list
    :return: None
    :precondition: game_info, player_position, game_map are formatted correctly. row and room are within the bounds of the map.
    :postcondition: Will update the game_info dictionary with the enemies in the room.
    """
    random_enemies = game_info["Map"]["Pieces"][game_map[row][room]]["Enemies"] == ["Random"]
    boss_enemy = game_info["Map"]["Pieces"][game_map[row][room]]["Enemies"] == ["Boss"]
    if random_enemies and random.randrange(0, 100) < 20 and [row, room] != player_position:
        for i in range(random.randrange(1, 3)):
            random_enemy = random.choice(list(game_info["Enemies"].keys()))
            while game_info["Enemies"][random_enemy]["Boss"] == "True":
                random_enemy = random.choice(list(game_info["Enemies"].keys()))
            game_info["Map"]["Game Map"][row][room]["Enemies"].append(random_enemy)
    elif boss_enemy:
        boss = random.choice(list(game_info["Enemies"].keys()))
        while game_info["Enemies"][boss]["Boss"] == "False":
            boss = random.choice(list(game_info["Enemies"].keys()))
        game_info["Map"]["Game Map"][row][room]["Enemies"].append(boss)

def populate_items(game_info, row, room, player_position):
    """
    Will decide what items (if any) will be placed in the room.

    :param game_info: game_info dictionary
    :param row: int
    :param room: int
    :param player_position: player_position list
    :return: None
    :precondition: game_info and player_position are formatted correctly. row and room are within the bounds of the map.
    """
    game_info["Map"]["Game Map"][row][room]["Item"] = ''
    item_can_place = (random.randrange(0, 100) < 40 or [row, room] == player_position)
    if game_info["Map"]["Game Map"][row][room]["Piece"] != "░" and item_can_place:
        random_item = random.choice(list(game_info["Items"].keys()))
        item_is_not_unique = game_info["Items"][random_item]["Unique"] != "True"
        if [row, room] == player_position:
            game_info["Map"]["Game Map"][row][room]["Item"] = "Medkit"
        elif item_is_not_unique or game_info["Items"][random_item]["Used"] == "False":
            game_info["Map"]["Game Map"][row][room]["Item"] = random_item
            game_info["Items"][random_item]["Used"] = "True"

def print_map(game_map, player_position, game_info):
    """
    Prints the map to the screen so the player can see it.

    :param game_map: game_map list
    :param player_position: player_position list
    :param game_info: game_info dictionary
    :return: None
    :precondition: game_map, player_position, game_info are formatted correctly.
    :postcondition: Will print the map to the terminal window.
    """
    final_map = ""
    view_range = [2, 4]  # Vertical, Horizontal
    for row in range(len(game_map)):
        if abs(player_position[0] - row) <= view_range[0]:  # Check vertical distance
            for room in range(len(game_map[row])):
                if abs(player_position[1] - room) <= view_range[1]:  # Check horizontal distance
                    final_map += add_piece(game_info, player_position, row, room, game_map)
            final_map += "\n"
    print(final_map)
    print('(n)orth, (s)outh, (e)ast, (w)est to move')
    print(game_info["Map"]["Game Map"][player_position[0]][player_position[1]]["Description"])
    print_items(game_info["Map"]["Game Map"][player_position[0]][player_position[1]]["Item"], game_info)

def add_piece(game_info, player_position, row, room, game_map):
    """
    Decides how each piece of the map will look when printed.

    :param game_info: game_info dictionary
    :param player_position: player_position list
    :param row: int
    :param room: int
    :param game_map: game_map list
    :return: string
    :precondition: game_info, player_position, game_map are formatted correctly. row, room are within the bounds of the map.
    :postcondition: Will return the map piece with correct formatting (colouring)
    """
    final_piece = ''
    if [row, room] == player_position:
        final_piece = f'\033[{game_info["Colours"]["Player"]}m' + game_map[row][
            room] + f'\033[{game_info["Colours"]["Normal"]}m'
    elif len(game_info["Map"]["Game Map"][row][room]["Enemies"]) > 0:
        final_piece = f'\033[{game_info["Colours"]["Enemy"]}m' + game_map[row][
            room] + f'\033[{game_info["Colours"]["Normal"]}m'
    else:
        final_piece = f'\033[{game_info["Colours"][game_info["Map"]["Pieces"][game_map[row][room]]["Colour"]]}m' + \
                     game_map[row][room] + f'\033[{game_info["Colours"]["Normal"]}m'
    return final_piece

def print_items(room_item, game_info):
    """
    Print the item in the room.

    :param room_item: item name string
    :param game_info: game_info dictionary
    :return: None
    :precondition: item name is a key in the game_info dictionary. game_info is formatted correctly.
    :postcondition: Prints the room description of the item to the terminal window.
    """
    if room_item != "":
        print(random.choice(game_info["Items"][room_item]["Room Description"]))

def check_level(game_map, new_position, game_info):
    """
    Verifies the player is allowed to pass the room.

    :param game_map: game_map list
    :param new_position: new_position list
    :param game_info: game_info dictionary
    :return: Bool
    :precondition: game_map, new_position, game_info are formatted correctly. new_position is within the bounds of the map.
    :postcondition: Will return True if the player's level is high enough to pass. False otherwise, printing a message to the player.
    """
    if game_map[new_position[0]][new_position[1]] == "2":
        if int(game_info["Player"]["Level"]) >= 2:
            return True
    elif game_map[new_position[0]][new_position[1]] == "3":
        if int(game_info["Player"]["Level"]) >= 3:
            return True
    else:
        return True
    print(get_message("Messages.LEVELMESSAGE", game_info))
    time.sleep(2)
    return False

def move_player(game_map, player_position, move_direction, game_info, last_player_position):
    """
    Changes the player's position.

    :param game_map: game_map list
    :param player_position: player_position list
    :param move_direction: move_direction string
    :param game_info: game_info dictionary
    :param last_player_position: last_player_position list
    :return: player_position list, last_player_position list
    :precondition: All params are formatted correctly.
    :postcondition: Will return the player's new position, as well as their old position. Both as lists.
    """
    possible_directions = game_info["Map"]["Pieces"][game_map[player_position[0]][player_position[1]]]["Dir"]
    if move_direction in possible_directions:
        last_player_position = player_position.copy()
        if move_direction.lower() == "north" or move_direction.lower() == "n":
            if check_level(game_map, [player_position[0] - 1, player_position[1]], game_info):
                player_position[0] = player_position[0] - 1
        elif move_direction.lower() == "south" or move_direction.lower() == "s":
            if check_level(game_map, [player_position[0] + 1, player_position[1]], game_info):
                player_position[0] = player_position[0] + 1
        elif move_direction.lower() == "east" or move_direction.lower() == "e":
            if check_level(game_map, [player_position[0], player_position[1] + 1], game_info):
                player_position[1] = player_position[1] + 1
        elif move_direction.lower() == "west" or move_direction.lower() == "w":
            if check_level(game_map, [player_position[0], player_position[1] - 1], game_info):
                player_position[1] = player_position[1] - 1
        regenerate_health(game_info)
    return player_position, last_player_position

def initiate_battle(game_info, player_position, last_player_position):
    """
    Will initialize a battle and start the battle loop, if an enemy is present in the room.

    :param game_info: game_info dictionary
    :param player_position: player_position list
    :param last_player_position: last_player_position list
    :return: player_position list
    :precondition: All params are formatted correctly. player_position and last_player_position are within the bounds of the map.
    :postcondition: returns the player_position if the battle is won or no enemies. Otherwise will return last_player_position. game_info will have been updated.
    """
    enemies = game_info["Map"]["Game Map"][player_position[0]][player_position[1]]["Enemies"]
    current_battle = {}
    battle_message = "In the room you find "
    for enemy in range(len(enemies)):
        if type(game_info["Map"]["Game Map"][player_position[0]][player_position[1]]["Enemies"]) is not dict:
            battle_message += add_enemy(current_battle, game_info, enemies, enemy)
        else:
            current_battle = game_info["Map"]["Game Map"][player_position[0]][player_position[1]]["Enemies"]
            battle_message = "So, you've come back to finish the fight. They are still......"
            break
    if len(current_battle) > 0:
        print(battle_message[:-6] + " blocking your path.")
        return battle(game_info, current_battle, player_position, last_player_position)
    return player_position

def add_enemy(current_battle, game_info, enemies, enemy):
    """
    Adds an enemy to the battle.

    :param current_battle: current_battle dictionary
    :param game_info: game_info dictionary
    :param enemies: enemies list
    :param enemy: enemy dictionary
    :return: string
    :precondition: Params are formatted correctly.
    :postcondition: Will edit current_battle to contain a new enemy. Will return a string for the battle_message.
    """
    current_battle[enemy] = game_info["Enemies"][enemies[enemy]].copy()
    if current_battle[enemy]["Vengeful"] == "True":
        current_battle[enemy]["Atk"] = 0
    if current_battle[enemy]["Speed"] > game_info["Player"]["Speed"]:
        print(get_message("Messages.SPEED_PRE_ATTACK", game_info).replace("!ENEMY", current_battle[enemy]["Name"]))
        attacked(game_info, current_battle[enemy], False)
    return f"an {current_battle[enemy]['Name']}, and "

def battle(game_info, current_battle, player_position, last_player_position):
    """
    Main battle loop.

    :param game_info: game_info dictionary
    :param current_battle: current_battle dictionary
    :param player_position: player_position list
    :param last_player_position: last_player_position list
    :return: list
    :precondition: All params are formatted correctly.
    :postcondition: Will update the game_info dictionary, and return the player's position.
    """
    while len(current_battle) > 0:
        time.sleep(1)
        if game_info['Player']['HP'] <= 0:
            time.sleep(1)
            die(game_info)
        has_attacked = False
        numbered_input = get_possible_moves(game_info, "Battle", player_position, current_battle)
        player_input = input(get_message("Messages.BATTLEPROMPT", game_info))
        input_type, player_input = interperet_input(player_input, True, game_info, numbered_input)
        if input_type == "Attack":
            for enemy in current_battle:
                if ((len(player_input.split(" ")) > 1) and (
                        player_input.split(" ")[1] in current_battle[enemy]["Name"])) or (
                        len(player_input.split(" ")) == 1):
                    current_battle[enemy] = attack(game_info, current_battle[enemy])
                    has_attacked = True
                    kill_enemy(game_info, current_battle, enemy)
                    break
            if not has_attacked:
                print("I can't find that enemy.")
        elif input_type == "Inventory":
            display_inventory(game_info)
        elif input_type == "Info":
            check_enemies(current_battle)
        elif input_type == "Flee":
            flee_from_battle(current_battle, game_info)
            game_info["Map"]["Game Map"][player_position[0]][player_position[1]]["Enemies"] = current_battle
            return last_player_position
        elif input_type == "Use":
            item = player_input.split(" ")[1]
            use_item(game_info, item)
        elif input_type == "Pass":
            has_attacked = True
        elif input_type == "Quit":
            quit()
        if has_attacked:
            for enemy in current_battle.copy():
                if random.randrange(0, 100) < 25 and current_battle[enemy]["Boss"] == "False":
                    enemy_flee(game_info, current_battle, enemy)
                    continue
                attacked(game_info, current_battle[enemy], False)
    game_info["Map"]["Game Map"][player_position[0]][player_position[1]]["Enemies"] = []
    return player_position

def kill_enemy(game_info, current_battle, enemy):
    """
    Kills an enemy and removes it from the battle.

    :param game_info: game_info dictionary
    :param current_battle: current_battle dictionary
    :param enemy: enemy dictionary
    :return: None
    :precondition: all params are formatted correctly.
    :postcondition: Enemy will be removed from the battle after printing the death message.
    """
    if current_battle[enemy]["HP"] <= 0:
        game_info['Player']['Kills'] += 1
        game_info['Player']['Exp'] += current_battle[enemy]["Exp"]
        print(get_message(f'Enemies.{current_battle[enemy]["Name"]}.TEXT.DEATH', game_info))
        level_up(game_info, current_battle[enemy])
        del (current_battle[enemy])

def check_enemies(current_battle):
    """
    Prints the stats of the enemies.

    :param current_battle: current_battle dictionary
    :return: None
    :precondition: current_battle is formatted correctly
    :postcondition: prints the stats of all the enemies in the current battle.
    """
    ignore_stats = ["TEXT", "Exp", "Boss"]
    for enemy in current_battle:
        print("\n")
        for key, value in current_battle[enemy].items():
            if key not in ignore_stats:
                print(key + ": " + str(value))

def enemy_flee(game_info, current_battle, enemy):
    """
    Make the enemy flee from battle.

    :param game_info: game_info dictionary
    :param current_battle: current_battle dictionary.
    :param enemy: string
    :return: None
    :precondition: all params are formatted correctly.
    :postcondition: Will print the flee message, and remove the enemy from the battle.
    """
    print(get_message(f'Enemies.{current_battle[enemy]["Name"]}.TEXT.FLEE', game_info))
    time.sleep(1)
    game_info['Player']['Exp'] += current_battle[enemy]["Exp"]
    level_up(game_info, current_battle[enemy])
    del(current_battle[enemy])

def flee_from_battle(current_battle, game_info):
    """
    Exit from the current battle before winning.

    :param current_battle: current_battle dictionary.
    :param game_info: game_info dictionary.
    :return: None
    :precondition: all params are formatted correctly.
    :postcondition: Decides whether the any enemies will attack you as you flee.
    """
    for enemy in current_battle:
        if random.randrange(0, 100) < 20:
            attacked(game_info, current_battle[enemy], True)
            time.sleep(1)

def display_inventory(game_info):
    """
    Print all the stuff in your inventory to the screen.

    :param game_info: game_info dictionary.
    :return: None
    :precondition: game_info is formatted correctly
    :postcondition: Will print all the stuff in your inventory to the terminal window.
    """
    print("\nItem \t\t| Count \t| \tDescription")
    for item, count in game_info["Player"]["Inventory"].items():
        print(item, "\t\t", count, "\t\t\t", game_info["Items"][item]["Description"])
    print('\n')
    time.sleep(1)

def attack(game_info, enemy):
    """
    Attack an enemy.

    :param game_info: game_info dictionary
    :param enemy: enemy dictionary
    :return: enemy dictionary
    :precondition: All params are formatted correctly.
    :postcondition: Will update the game_info dictionary, and return the changed enemy dictionary.
    """
    time.sleep(1)
    random_number = random.randrange(0, 100)
    if random_number >= 20:
        damage = math.ceil((game_info["Player"]["Atk"] * 5) * (random_number / 100))
        damage -= enemy["Def"]
        if enemy["Vengeful"] == "True":
            enemy["Atk"] = game_info["Enemies"][enemy["Name"]]["Atk"]
        if damage <= 0:
            print(get_message(f'Enemies.{enemy["Name"]}.TEXT.DEF', game_info))
            return enemy
        enemy["HP"] -= damage
        print(get_message(f'Enemies.{enemy["Name"]}.TEXT.HIT', game_info).replace("!DMG", str(damage)))
    else:
        print(get_message(f'Enemies.{enemy["Name"]}.TEXT.MISS', game_info))
    return enemy

def attacked(game_info, enemy, player_fleeing):
    """
    Enemy attack the player.

    :param game_info: game_info dictionary
    :param enemy: enemy dictionary
    :param player_fleeing: bool
    :return: None
    :precondition: all params are formatted correctly.
    :postcondition: game_info dictionary will be updated.
    """
    time.sleep(1)
    random_number = random.randrange(0, 100)
    if random_number >= 20:
        damage = math.ceil((enemy["Atk"] * 5) * (random_number / 100))
        damage -= game_info["Player"]["Def"]
        if damage <= 0:
            print(get_message(f"Enemies.{enemy['Name']}.TEXT.ATKDEF", game_info))
            return enemy
        game_info["Player"]["HP"] -= damage
        if player_fleeing:
            print(get_message(f"Enemies.{enemy['Name']}.TEXT.FLEEATK", game_info).replace("!DMG", str(damage)))
        else:
            print(get_message(f"Enemies.{enemy['Name']}.TEXT.ATKHIT", game_info).replace("!DMG", str(damage)))
    else:
        print(get_message(f"Enemies.{enemy['Name']}.TEXT.ATKMISS", game_info))

def interperet_input(player_input, in_battle, game_info, numbered_input):
    """
    Interpret the player's input.

    :param player_input: string
    :param in_battle: bool
    :param game_info: game_info dictionary
    :param numbered_input: list of tuples with 2 values
    :return: string, string
    :precondition: all params are formatted correctly.
    :postcondition: Will return the type of the input, as well as the full input string.
    """
    if player_input.isnumeric():
        numeric_inputs = list_of_tuples_to_dictionary(numbered_input)
        if player_input in numeric_inputs:
            player_input = list_of_tuples_to_dictionary(numbered_input)[player_input]
        player_input = player_input[0].lower() + player_input[1:]
    if in_battle:
        for key, value in game_info["Inputs"]["Battle"].items():
            if player_input.split(" ")[0] in value:
                return key, player_input
    else:
        for key, value in game_info["Inputs"]["MapView"].items():
            if player_input.split(" ")[0] in value:
                return key, player_input
    return None, player_input

def list_of_tuples_to_dictionary(input_list):
    """
    Convert a list of tuples to a dictionary.

    :param input_list: list of tuples with 2 values.
    :return: Dictionary
    :precondition: each tuple in the list always has 2 values.
    :postcondition: returns a dictionary created from the list of tuples. {tuple1[0]: tuple1[1], tuple2[0]: tuple2[1]}
    >>> list_of_tuples_to_dictionary([(0,1), (1,2), (2,3)])
    {0: 1, 1: 2, 2: 3}
    >>> list_of_tuples_to_dictionary([(0,1)])
    {0: 1}
    """
    dictionary = {}
    for pair in input_list:
        dictionary[pair[0]] = pair[1]
    return dictionary

def regenerate_health(game_info):
    """
    Add 5 health each time the function is called.

    :param game_info: game_info dictionary
    :return: None
    :precondition: game_info dictionary is formatted correctly.
    :postcondition: game_info will be updated with the new health.
    """
    if game_info["Player"]["HP"] < game_info["Player"]["MaxHP"]:
        game_info["Player"]["HP"] += 5
        if game_info["Player"]["HP"] > game_info["Player"]["MaxHP"]:
            game_info["Player"]["HP"] = game_info["Player"]["MaxHP"]

def level_up(game_info, enemy):
    """
    Decide whether to level up the player.

    :param game_info: game_info dictionary.
    :param enemy: enemy dictionary
    :return: None
    :precondition: all params are formatted correctly.
    :postcondition: Player will be leveled up if they meet the requirements, updating game_info.
    """
    if (game_info['Player']['Exp'] > 150 and game_info['Player']['Level'] == 1) or (
            game_info['Player']['Exp'] > 300 and game_info['Player']['Level'] == 2):
        game_info["Player"]["Level"] += 1
        game_info["Player"]["MaxHP"] += int(game_info["Player"]["MaxHP"] * 0.5)

        print(get_message("Messages.LEVELUP", game_info).replace("!ENEMY", enemy["Name"]))
        time.sleep(2)

def show_player_stats(game_info):
    """
    Prints the player's stats to the screen.

    :param game_info: game_info dictionary
    :return: None
    :precondition: game_info is formatted correctly.
    :postcondition: will print all the attributes to the screen, except the ignored ones.
    """
    ignore_keys = ["MaxHP", "Inventory", "Equipped"]
    for key, value in game_info["Player"].items():
        if key not in ignore_keys:
            print(key + ": " + str(value))
            time.sleep(0.2)

def die(game_info):
    """
    Prints the deaths messages and ends the program.

    :param game_info: game_info dictionary
    :return: None
    :precondition: game_info is formatted correctly.
    :postcondition: program will end.
    """
    print(get_message("Art.Death", game_info))
    time.sleep(1)
    print(get_message("Messages.DEATH_1", game_info))
    time.sleep(2)
    print(get_message("Messages.DEATH_2", game_info))
    time.sleep(2)
    quit()

def grab_item(game_info, player_position):
    """
    Put the item in the current room into the player's inventory.

    :param game_info: game_info dictionary
    :param player_position: player_position list
    :return: None
    :precondition: all params are formatted correctly. player_position is within the bounds of the game.
    :postcondition: item will be added to the player's inventory in game_info.
    """
    item = game_info["Map"]["Game Map"][player_position[0]][player_position[1]]["Item"]
    game_info["Map"]["Game Map"][player_position[0]][player_position[1]]["Item"] = ''
    print(get_message(f"Items.{item}.TakeText", game_info))
    add_to_inventory(game_info, item)
    time.sleep(1)

def add_to_inventory(game_info, item):
    """
    Add an item to the player's inventory.

    :param game_info: game_info dictionary
    :param item: item string
    :return: None
    :precondition: all params are formatted correctly
    :postcondition: game_info player inventory will be updated
    """
    if item not in game_info["Player"]["Inventory"]:
        game_info["Player"]["Inventory"][item] = 1
    else:
        game_info["Player"]["Inventory"][item] += 1

def use_item(game_info, item):
    """
    Use an item from the player's inventory.

    :param game_info: game_info dictionary.
    :param item: item string
    :return: None
    :precondition: all params are formatted correctly.
    :postcondition: Will update the player with the item's effects.
    """
    if item in game_info["Player"]["Inventory"]:
        if "HP" in game_info["Items"][item]:
            print(get_message(f"Items.{item}.UseText", game_info))
            game_info["Player"]["HP"] += int(game_info["Items"][item]["HP"])
            if game_info["Player"]["HP"] > game_info["Player"]["MaxHP"]:
                game_info["Player"]["HP"] = game_info["Player"]["MaxHP"]
        elif "ATK" in game_info["Items"][item]:
            equip_item(game_info, item, "ATK")
        elif "DEF" in game_info["Items"][item]:
            equip_item(game_info, item, "DEF")
        game_info["Player"]["Inventory"][item] -= 1
        if game_info["Player"]["Inventory"][item] <= 0:
            del (game_info["Player"]["Inventory"][item])
    else:
        print(get_message("Messages.INVALIDITEM", game_info))

def equip_item(game_info, item, stat):
    """
    Equip an item.

    :param game_info: game_info dictionary.
    :param item: item string
    :param stat: string
    :return: None
    :precondition: all params are formatted correctly. item and stat are valid options.
    :postcondition: Item will be equipped.
    """
    if game_info["Player"]["Equipped"][game_info["Items"][item]["Slot"]] != "":
        slot_item = game_info["Player"]["Equipped"][game_info["Items"][item]["Slot"]]
        print(get_message("Messages.UNEQUIP", game_info).replace("!ITEM", slot_item))
        game_info["Player"][stat.title()] -= game_info["Items"][slot_item][stat]
        add_to_inventory(game_info, item)
    game_info["Player"]["Equipped"][game_info["Items"][item]["Slot"]] = item
    print(get_message(f"Items.{item}.UseText", game_info))
    game_info["Player"][stat.title()] += game_info["Items"][item][stat]

def get_message(path, game_info):
    """
    Get a value from the game_info dictionary.

    :param path: string
    :param game_info: game_info dictionary
    :return: string or int
    :precondition: all params are formatted correctly. path is a valid location in the dictionary. E.g. Messages.UNEQUIP
    :postcondition: Will return the value of the specified path, as well as making any replacements needed.
    """
    current_item = game_info
    for turn in path.split("."):
        current_item = current_item[turn]
    if isinstance(current_item, list):
        message = random.choice(current_item)
        message = replace_message(message, game_info)
        return message
    return current_item

def replace_message(message, game_info):
    """
    Replace all values in a message with proper values.

    :param message: string
    :param game_info: game_info dictionary
    :return: string
    :precondition: all params are formatted correctly.
    :postcondition: will have replaced any parts of the string that needed replacement.
    """
    for key, value in game_info["Replacements"].items():
        message = message.replace(key, str(get_message(value, game_info)))
    return message

def get_possible_moves(game_info, context, player_position, enemies=[]):
    """
    Will create a numbered list of all the moves you can make.

    :param game_info: game_info dictionary
    :param context: string
    :param player_position: player_position list
    :param enemies: (optional) enemies list
    :return: List of tuples
    :precondition: all params are formatted correctly.
    :postcondition: Will return a list of tuples, formatted as (number, possible move).
    """
    output_list = []
    ignore = ["Move", "Use", "Take", "Quit"]
    move_number = itertools.count()
    possible_inputs = game_info["Inputs"][context]
    for move in possible_inputs:
        if move not in ignore:
            output_list.append((str(next(move_number)), move))
    for item in get_usable_items(game_info):
        output_list.append((str(next(move_number)), item))
    if context == "MapView" and get_grabbable_item(game_info, player_position) != '':
        output_list.append((str(next(move_number)), get_grabbable_item(game_info, player_position)))
    elif context == "Battle":
        output_list.extend(get_attackable_enemies(enemies, move_number))
    print('\n' + '\n'.join(map(': '.join, output_list)))
    return output_list

def get_attackable_enemies(enemies, iterator):
    """
    Create a numbered list of all the enemies.

    :param enemies: enemies dictionary
    :param iterator: iterable
    :return: list of tuples
    :precondition: all params are formatted correctly.
    :postcondition: Will return a list of tuples, formatted as (number, possible move).
    """
    all_enemies = []
    for enemy in enemies:
        all_enemies.append((str(next(iterator)), f'Attack {enemies[enemy]["Name"]}'))
    return all_enemies

def get_usable_items(game_info):
    """
    Get a list of all the items the player can use.

    :param game_info: game_info dictionary
    :return: list
    :precondition: game_info is formatted correctly.
    :postcondition: will return a list of all the items you can use
    """
    usable_items = []
    for item in game_info["Player"]["Inventory"]:
        usable_items.append(f"Use {item}")
    return usable_items

def get_grabbable_item(game_info, player_position):
    """
    Get the item you can grab in the room.

    :param game_info: game_info dictionary
    :param player_position: player_position list
    :return: string
    :precondition: all params are formatted correctly
    :postcondition: will return a string formatted as 'Take {item}'
    """
    item = game_info["Map"]["Game Map"][player_position[0]][player_position[1]]["Item"]
    if item != '':
        return f"Take {item}"
    return ''

def choose_class(game_info):
    """
    Get the player to choose their class

    :param game_info: game_info dictionary
    :return: list
    :precondition: game_info is formatted correctly.
    :postcondition: will return the starting position of the selected class, and update the game_info dictionary.
    """
    classes = {}
    iterable = itertools.count()
    for possible_class in game_info["Classes"]:
        classes[next(iterable)] = possible_class
    while True:
        for key, value in classes.items():
            print(f"{key}: {value}")
        player_class = input(get_message("Messages.WELCOME_3", game_info))
        if player_class.isnumeric() and int(player_class) in classes:
            for stat, value in game_info["Classes"][classes[int(player_class)]].items():
                if stat not in ["Starting Pos"]:
                    print(f"{stat}: {value}")
            if input(get_message("Messages.CONFIRMATION", game_info)).lower() == 'y':
                apply_class(game_info, classes[int(player_class)])
                return game_info["Classes"][classes[int(player_class)]]["Starting Pos"]

def apply_class(game_info, chosen_class):
    """
    Set the player's stats to the chosen class.

    :param game_info: game_info dictionary
    :param chosen_class: string
    :return: None
    :precondition: all params are formatted correctly. chosen_class is a valid class.
    :postcondition: player stats will be changed to the class's base stats.
    """
    game_info["Player"]["Class"] = chosen_class
    for attribute in game_info["Player"].keys():
        if attribute in game_info["Classes"][chosen_class].keys():
            game_info["Player"][attribute] = game_info["Classes"][chosen_class][attribute]

def win(game_info):
    """
    Print the win messages and exit the program.

    :param game_info: game_info dictionary
    :return: None
    :precondition: game_info is formatted correctly.
    :postcondition: Program will end.
    """
    print(get_message("Art.Win", game_info))
    print(get_message("Messages.WIN_1", game_info))
    time.sleep(3)
    print(get_message("Messages.WIN_2", game_info))
    time.sleep(3)
    print(get_message("Messages.WIN_3", game_info))
    time.sleep(3)
    quit()

def check_for_win(game_map, player_position):
    """
    Check to see if the player has exited the board.

    :param game_map: game_map list
    :param player_position: player_position list
    :return: bool
    :precondition: all params are formatted correctly.
    :postcondition: Returns True if player has left the board, False otherwise.
    """
    out_of_bounds_negative = player_position[0] < 0 or player_position[1] < 0
    out_of_bounds_positive = player_position[0] > len(game_map) or player_position[1] > len(game_map[0])
    return out_of_bounds_positive or out_of_bounds_negative

def game(game_info_path="./gameInfo.json", map_path="./map.txt"):
    """
    Drives the game

    :return: None
    """
    game_info = load_game_info(game_info_path)
    game_map = load_game_map(map_path)

    print(get_message("Messages.WELCOME_1", game_info))
    time.sleep(2)
    game_info["Player"]["Name"] = input(get_message("Messages.WELCOME_2", game_info))
    time.sleep(2)
    player_position = choose_class(game_info)
    time.sleep(2)
    print(get_message("Messages.INTRODUCTION", game_info))
    time.sleep(2)
    game_info["Player"]["MaxHP"] = game_info["Player"]["HP"]

    last_player_position = [24, 16]
    game_info = populate_game_map(game_info, game_map, player_position)
    while True:
        player_position = initiate_battle(game_info, player_position, last_player_position)
        print_map(game_map, player_position, game_info)
        numbered_input = get_possible_moves(game_info, "MapView", player_position)
        player_input = input(get_message("Messages.MAPPROMPT", game_info))
        input_type, player_input = interperet_input(player_input, False, game_info, numbered_input)
        if input_type != "Error":
            if input_type == "Move":
                player_position, last_player_position = move_player(game_map, player_position, player_input, game_info,
                                                                    last_player_position)
                if player_position[0] < 0:
                    win(game_info)
                print('\n' * 50)
            elif input_type == "Info":
                show_player_stats(game_info)
            elif input_type == "Take":
                grab_item(game_info, player_position)
            elif input_type == "Inventory":
                display_inventory(game_info)
            elif input_type == "Use":
                item = player_input.split(" ", 1)[1]
                use_item(game_info, item)
                time.sleep(1)
            elif input_type == "Quit":
                quit()
        else:
            print(get_message("Messages.INVALIDCOMMAND"), game_info)

def get_kwargs(args):
    final_args = {}
    for arg in args:
        if arg.split('=')[0] == "game_info":
            final_args['game_info_path'] = arg.split('=')[1]
        elif arg.split('=')[0] == "map":
            final_args['map_path'] = arg.split('=')[1]
    return final_args

if __name__ == "__main__":
    kwargs = get_kwargs(sys.argv)
    game(**kwargs)


# -*- coding: utf-8 -*-
"""
Created on Tue Jul  9 14:56:53 2019

@author: HP
"""

import dash
import dash_table
import pandas as pd

df = realdf

app = dash.Dash(__name__)

app.layout = dash_table.DataTable(
    id='table',
    columns=[{"name": i, "id": i} for i in df.columns],
    style_table={'width': '200px'},
    data=df.to_dict('records'),
    fixed_rows={ 'headers': True, 'data': 0 },
    style_cell={'width': '100px'}

)

if __name__ == '__main__':
    app.run_server()


from collections import deque
def solution(s):
    s=deque(s)
    stack=[s.popleft()]
    while s:
        stack.append(s.popleft())
        if len(stack)>1 and stack[-1]==stack[-2]:
            stack.pop()
            stack.pop()
    if stack: return 0
    else: return 1


from collections import Counter
class Solution(object):
    def lengthOfLongestSubstringTwoDistinct(self, s):
        """
        :type s: str
        :rtype: int
        """
        left = right = maxLength = 0
        counter = Counter()
        numDistinct = 0
        while right < len(s) :
            if counter[s[right]] == 0 :
                numDistinct+=1
            counter[s[right]]+=1

            while numDistinct > 2 :
                counter[s[left]]-=1
                if counter[s[left]]==0 :
                    numDistinct-=1
                left+=1
            maxLength = max(maxLength,right-left+1)
            right+=1
        return maxLength


#!/usr/bin/python
from __future__ import print_function

### python lib
import os, sys, argparse, glob, re, math, pickle, cv2, time
import numpy as np

### torch lib
import torch
import torch.nn as nn
from utils import *
import torch.nn.functional as F

### custom lib
from networks.resample2d_package.modules.resample2d import Resample2d
import networks
import utils
import matplotlib.pyplot as plt

def align_to_64(frame_i0, divide):
    [b, c, h, w] = frame_i0.shape

    h_pad = int(np.floor(h/divide)+1)*divide
    w_pad = int(np.floor(w/divide)+1)*divide

    frame_i0_pad = F.pad(frame_i0, pad = [0, w_pad-w, 0, h_pad-h], mode='replicate')

    return frame_i0_pad, h_pad-h, w_pad-w

if __name__ == "__main__":

    parser = argparse.ArgumentParser(description='Fast Blind Video Temporal Consistency')

    parser.add_argument('-method', type=str, required=True, help='test model name')
    parser.add_argument('-model_name', type=str, required=True, help='test model name')
    parser.add_argument('-epoch', type=int, required=True, help='epoch')
    parser.add_argument('-streak_tag',          type=str,     default=1,               help='Whether the model handle rain streak')
    parser.add_argument('-haze_tag',            type=str,     default=1,               help='Whether the model handle haze')
    parser.add_argument('-flow_tag',            type=str,     default=1,               help='Whether the model handle haze')

    parser.add_argument('-dataset', type=str, required=True, help='dataset to test')
    parser.add_argument('-phase', type=str, default="test", choices=["train", "test"])
    parser.add_argument('-data_dir', type=str, default='data', help='path to data folder')
    parser.add_argument('-list_dir', type=str, default='lists', help='path to list folder')

    parser.add_argument('-checkpoint_dir', type=str, default='checkpoints', help='path to checkpoint folder')
    parser.add_argument('-task', type=str, required=True, help='evaluated task')
    parser.add_argument('-list_filename', type=str, required=True, help='evaluated task')
    parser.add_argument('-redo', action="store_true", help='Re-generate results')
    parser.add_argument('-gpu', type=int, default=1, help='gpu device id')

    opts = parser.parse_args()
    opts.cuda = True

    opts.size_multiplier = 2 ** 3  ## Inputs to TransformNet need to be divided by 4

    print(opts)

    if opts.cuda and not torch.cuda.is_available():
        raise Exception("No GPU found, please run without -cuda")

    opts_filename = os.path.join(opts.checkpoint_dir, opts.model_name, "opts.pth")
    print("Load %s" % opts_filename)
    with open(opts_filename, 'rb') as f:
        model_opts = pickle.load(f)

    model_filename = os.path.join(opts.checkpoint_dir, opts.model_name, "model_epoch_%d.pth" % opts.epoch)
    print("Load %s" % model_filename)
    state_dict = torch.load(model_filename)

    opts.rgb_max = 1.0
    opts.fp16 = False

    three_dim_model = networks.__dict__['TDModel'](opts, 3, 64)
    fusion_model = networks.__dict__['TDModel'](opts, 3, 64)
    FlowNet = networks.FlowNet2(opts, requires_grad=False)

    three_dim_model.load_state_dict(state_dict['three_dim_model'])
    fusion_model.load_state_dict(state_dict['fusion_model'])
    FlowNet.load_state_dict(state_dict['flow_model'])

    device = torch.device("cuda" if opts.cuda else "cpu")
    three_dim_model = three_dim_model.cuda()
    fusion_model = fusion_model.cuda()
    FlowNet = FlowNet.cuda()
    flow_warping = Resample2d().cuda()

    three_dim_model.eval()
    fusion_model.eval()

    list_filename = opts.list_filename

    with open(list_filename) as f:
        video_list = [line.rstrip() for line in f.readlines()]

    times = []

    for v in range(len(video_list)):
        video = video_list[v]

        print("Test %s on %s video %d/%d: %s" % (opts.dataset, opts.phase, v + 1, len(video_list), video))

        input_dir = os.path.join(opts.data_dir,  opts.phase, video)
        output_dir = os.path.join(opts.data_dir, opts.phase, "output", opts.model_name, opts.method, "epoch_%d" % opts.epoch, opts.task,
                                  opts.dataset, video)

        print(input_dir)
        print(output_dir)

        if not os.path.isdir(output_dir):
            os.makedirs(output_dir)

        frame_list = glob.glob(os.path.join(input_dir, "*.jpg"))
        output_list = glob.glob(os.path.join(output_dir, "*.jpg"))

        if len(frame_list) == len(output_list) and not opts.redo:
            print("Output frames exist, skip...")
            continue

        for t in range(1+3, len(frame_list)-3):
            frame_i0 = utils.read_img(os.path.join(input_dir, "%05d.jpg" % (t-3)))
            frame_i1 = utils.read_img(os.path.join(input_dir, "%05d.jpg" % (t-2)))
            frame_i2 = utils.read_img(os.path.join(input_dir, "%05d.jpg" % (t-1)))
            frame_i3 = utils.read_img(os.path.join(input_dir, "%05d.jpg" % (t)))
            frame_i4 = utils.read_img(os.path.join(input_dir, "%05d.jpg" % (t+1)))
            frame_i5 = utils.read_img(os.path.join(input_dir, "%05d.jpg" % (t+2)))
            frame_i6 = utils.read_img(os.path.join(input_dir, "%05d.jpg" % (t+3)))

            with torch.no_grad():
                frame_i0 = utils.img2tensor(frame_i0).cuda()
                frame_i1 = utils.img2tensor(frame_i1).cuda()
                frame_i2 = utils.img2tensor(frame_i2).cuda()
                frame_i3 = utils.img2tensor(frame_i3).cuda()
                frame_i4 = utils.img2tensor(frame_i4).cuda()
                frame_i5 = utils.img2tensor(frame_i5).cuda()
                frame_i6 = utils.img2tensor(frame_i6).cuda()

                frame_i0, f_h_pad, f_w_pad = align_to_64(frame_i0, 64)
                frame_i1, f_h_pad, f_w_pad = align_to_64(frame_i1, 64)
                frame_i2, f_h_pad, f_w_pad = align_to_64(frame_i2, 64)
                frame_i3, f_h_pad, f_w_pad = align_to_64(frame_i3, 64)
                frame_i4, f_h_pad, f_w_pad = align_to_64(frame_i4, 64)
                frame_i5, f_h_pad, f_w_pad = align_to_64(frame_i5, 64)
                frame_i6, f_h_pad, f_w_pad = align_to_64(frame_i6, 64)

                [b, c, h, w] = frame_i0.shape

                flow_warping = Resample2d().cuda()

                flow_i30 = FlowNet(frame_i3, frame_i0)
                warp_i0 = flow_warping(frame_i0, flow_i30)
                flow_i31 = FlowNet(frame_i3, frame_i1)
                warp_i1 = flow_warping(frame_i1, flow_i31)
                flow_i32 = FlowNet(frame_i3, frame_i2)
                warp_i2 = flow_warping(frame_i2, flow_i32)

                flow_i34 = FlowNet(frame_i3, frame_i4)
                warp_i4 = flow_warping(frame_i4, flow_i34)
                flow_i35 = FlowNet(frame_i3, frame_i5)
                warp_i5 = flow_warping(frame_i5, flow_i35)
                flow_i36 = FlowNet(frame_i3, frame_i6)
                warp_i6 = flow_warping(frame_i6, flow_i36)

                warp_i0 = warp_i0.view(b, c, 1, h, w)
                warp_i1 = warp_i1.view(b, c, 1, h, w)
                warp_i2 = warp_i2.view(b, c, 1, h, w)
                warp_i4 = warp_i4.view(b, c, 1, h, w)
                warp_i5 = warp_i5.view(b, c, 1, h, w)
                warp_i6 = warp_i6.view(b, c, 1, h, w)
                frame_i3 = frame_i3.view(1, c, 1, h, w)

                frame_input = torch.cat((warp_i0.detach(), warp_i1.detach(), warp_i2.detach(), warp_i2.detach(), warp_i4.detach(), warp_i5.detach(), warp_i6.detach()), 2)
                frame_pred = three_dim_model(frame_input)
                frame_i3_rs = frame_i3.view(b, c, 1, h, w)
                frame_target = frame_i3_rs

                frame_input2 = torch.cat((warp_i0.detach(), warp_i1.detach(), warp_i2.detach(), frame_pred.detach(), frame_i3_rs,  warp_i4.detach(), warp_i5.detach()), 2)
                frame_pred_rf = fusion_model(frame_input2) + frame_pred.detach()

                frame_pred_rf = frame_pred_rf.view(b, c, 1, h, w)
                frame_pred_rf = frame_pred_rf[:, :, :, 0:h-f_h_pad, 0:w-f_w_pad]

            fusion_frame_pred = utils.tensor2img(frame_pred_rf.view(1, c, h-f_h_pad, w-f_w_pad))

            output_filename = os.path.join(output_dir, "%05d_res.png"%t)
            utils.save_img(fusion_frame_pred, output_filename)

            frame_input = utils.tensor2img(frame_i3.view(1, c, h, w))
            output_filename = os.path.join(output_dir, "%05d_input.png"%t)
            utils.save_img(frame_input, output_filename)

    if len(times) > 0:
        time_avg = sum(times) / len(times)
        print("Average time = %f seconds (Total %d frames)" % (time_avg, len(times)))


"""golub_portal URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/1.8/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
Including another URLconf
    1. Add an import:  from blog import urls as blog_urls
    2. Add a URL to urlpatterns:  url(r'^blog/', include(blog_urls))
"""
from django.urls import path, include
from django.contrib import admin
from django.conf import settings
from django.conf.urls.static import static
from django.contrib.staticfiles.urls import staticfiles_urlpatterns
from django.views.generic import TemplateView
# from django.contrib.auth.decorators import login_required
from django.conf.urls.i18n import i18n_patterns

from core.posts import views
# from core.news.views import AdvertListView
from core.services.views import FeedbackView

urlpatterns = i18n_patterns(
    path('', views.IndexView.as_view(), name='index'),
    path(f'admin{settings.ADMIN_HASH}/', admin.site.urls),
    path('rosetta/', include('rosetta.urls')),
    path('comment/', include('comment.urls')),
    path('api/', include('comment.api.urls')),
    path('sitemap.xml', views.SiteMap.as_view(), name='sitemap'),
    path('categories/', TemplateView.as_view(template_name="categories.html"), name='categories'),
    path('create/', TemplateView.as_view(template_name="add.html"), name='add'),
    path('social/', include('social_django.urls', namespace='social')),
    path('api-auth/', include('rest_framework.urls',
                              namespace='rest_framework')),
    path('page/', include('django.contrib.flatpages.urls')),
    path('i18n/', include('django.conf.urls.i18n')),
    path('feedback/', FeedbackView.as_view(), name="feedback-form"),
    path('service/', include('core.services.urls')),
    path('api/', include('api.v1.urls')),
    path('events/', include('core.events.urls', namespace='events')),
    path('news/', include('core.news.urls', namespace='news')),
    path('adverts/', include("core.adverts.urls", namespace='adverts')),
    path('diaries/', include("core.diary.urls", namespace='diaries')),
    # path('adverts/<str:category>/', AdvertListView.as_view(), name="adverts-list"),
    # path('adverts/', AdvertListView.as_view(), name="adverts-list"),
    # Rendering index page for all urls starts with /profile/ for personal page.
    # path('profile/', login_required(TemplateView.as_view(template_name='personal/personal_index.html')),
    #      name='personal-index'),
    path('', include('core.classifier.urls')),
    path('', include('core.pro_auth.urls', namespace='pro_auth')),
    path('', include('core.posts.urls')),
    prefix_default_language=False
)

# For returning errors pages. Need to be the last.
handler404 = "django.views.defaults.page_not_found"
handler500 = "django.views.defaults.server_error"

urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
urlpatterns += staticfiles_urlpatterns()

if settings.DEBUG:
    urlpatterns = [path('silk/', include('silk.urls', namespace='silk')),] + urlpatterns


import datetime
from crawfish.models import BaseModel, EmailField
from django.db import models
from django.test import TestCase
from django.utils import timezone

class TestModel(BaseModel):
    """A test model using BaseModel abstract model"""

    test_field = models.CharField(max_length=500)

    class Meta:
        # App label is required for execution.
        app_label = "crawfish"

    def __str__(self):
        return f"TestModel(id: '{self.id}')"

class ModelsTests(TestCase):
    @classmethod
    def setUpTestData(cls):
        cls.model = TestModel

    def test_creation(self):
        model0 = self.model.objects.create()
        print(model0)
        self.assertIsNotNone(model0)
        self.assertLessEqual(model0.created_at, timezone.now())
        self.assertGreater(
            model0.created_at, timezone.now() - datetime.timedelta(minutes=1)
        )
        self.assertLessEqual(model0.updated_at, timezone.now())
        self.assertGreater(
            model0.updated_at, timezone.now() - datetime.timedelta(minutes=1)
        )

        # We can expect created_at and updated_at to be nearly equal but not exactly
        self.assertNotEqual(model0.created_at, model0.updated_at)

    def test_update(self):
        model = self.model.objects.create()
        model.test_field = "Test value"
        model.save()
        self.assertIsInstance(model.created_at, datetime.datetime)
        self.assertIsInstance(model.updated_at, datetime.datetime)

        self.assertGreater(model.updated_at, model.created_at)

class FieldsTests(TestCase):
    pass