class GameManager:

    def __init__(self):
        self.area_number = 1
        self.area = Area()
        self.hero = Hero()
        self.characters = self.create_characters()
        self.monsters = self.characters[1:]
        self.kill_count = 0

    def create_characters(self):
        characters = []
        characters.append(self.hero)
        monsters = self.create_monsters()
        for i in range(0, len(monsters)):
            characters.append(monsters[i])
        return characters

    def create_monsters(self):
        monsters = []
        boss = Boss()
        boss.level = self.area_number
        monsters.append(boss)
        skeletons = []
        number_of_skeletons = randrange(2, 5)
        for i in range(number_of_skeletons):
            skeletons.append(Skeleton('Skeleton_' + str(i + 1)))
        skeletons[0].has_key = True
        for skeleton in skeletons:
            skeleton.level = self.area_number
            monsters.append(skeleton)
        return monsters

    def set_free_tiles(self):
        free_tiles = []
        for tile in self.area.tiles:
            if (tile[0].walkable and not tile[0].has_hero
                    and not tile[0].has_monster):
                free_tiles.append(tile)
        return free_tiles

    def spawn_characters(self, canvas):
        self.spawn_hero(canvas)
        self.spawn_monsters(canvas)

    def spawn_hero(self, canvas):
        self.hero.x, self.hero.y = 0, 0
        self.area.draw_character(canvas, self.hero)
        tile = self.area.tiles[0][0]
        tile.has_hero = True

    def spawn_monsters(self, canvas):
        free_tiles = self.set_free_tiles()
        for monster in self.monsters:
            tile = free_tiles[randrange(len(free_tiles))]
            free_tiles.remove(tile)
            monster.x = tile[2][1]
            monster.y = tile[2][0]
            self.area.draw_character(canvas, monster)
            tile[0].has_monster = True

    def get_character_tile(self, character):
        return [i for i in self.area.tiles if [character.x, character.y] in i]

    def set_hero_position(self, canvas, direction):
        self.hero.turn(direction)
        canvas.delete(self.hero.name)
        self.area.draw_character(canvas, self.hero)
        destination_x, destination_y = self.calculate_destination(self.hero,
                                                                  direction)
        is_wall = self.check_walls(destination_x, destination_y)
        if is_wall:
            return
        if self.check_map(destination_x, destination_y):
            self.get_character_tile(self.hero)[0][0].has_hero = False
            self.hero.x, self.hero.y = destination_x, destination_y
            canvas.delete(self.hero.name)
            self.area.draw_character(canvas, self.hero)
            self.get_character_tile(self.hero)[0][0].has_hero = True
        else:
            return
        has_monster = self.check_monsters(destination_x, destination_y)
        if has_monster is not None:
            self.fight(canvas, self.hero, has_monster)
        # self.check_monster_move(canvas)  # enable: monsters move every 2 turn

    def check_monster_move(self, canvas):
        if self.area.turn_count % 2 != 0:
            self.move_monsters(canvas)
        self.area.increase_turn_count()

    def move_monsters(self, canvas):
        for monster in self.monsters:
            self.set_monster_position(canvas, monster)

    def set_monster_position(self, canvas, monster):
        directions = self.get_possible_moves(monster)
        direction = directions[randrange(len(directions))]
        destination_x, destination_y = self.calculate_destination(monster,
                                                                  direction)
        self.get_character_tile(monster)[0][0].has_monster = False
        canvas.delete(monster.name)
        monster.x, monster.y = destination_x, destination_y
        self.area.draw_character(canvas, monster)
        self.get_character_tile(monster)[0][0].has_monster = True
        if self.get_character_tile(monster)[0][0].has_hero:
            self.fight(canvas, monster, self.hero)

    def get_possible_moves(self, monster):
        directions = ['up', 'down', 'left', 'right']
        bad_directions = []
        for direction in directions:
            destination_x, destination_y = self.calculate_destination(monster,
                                                                      direction)
            is_wall = self.check_walls(destination_x, destination_y)
            if is_wall:
                bad_directions.append(direction)
                continue
            if not self.check_map(destination_x, destination_y):
                bad_directions.append(direction)
                continue
            has_monster = self.check_monsters(destination_x, destination_y)
            if has_monster is not None:
                bad_directions.append(direction)
                continue
        if len(bad_directions) != 0:
            for item in bad_directions:
                directions.remove(item)
        if len(directions) == 0:
            self.get_character_tile(monster)[0][0].has_monster = False
            return ['stay']
        return directions

    def calculate_destination(self, character, direction):
        if direction == 'up':
            x = character.x
            y = character.y - self.area.tile_size
        elif direction == 'down':
            x = character.x
            y = character.y + self.area.tile_size
        elif direction == 'right':
            y = character.y
            x = character.x + self.area.tile_size
        elif direction == 'left':
            y = character.y
            x = character.x - self.area.tile_size
        else:
            y = character.y
            x = character.x
        return (x, y)

    def check_walls(self, destination_x, destination_y):
        if (((destination_y * self.area.number_of_tiles) / self.area.tile_size)
                + (destination_x / self.area.tile_size) in self.area.walls):
            return True
        return False

    def check_map(self, destination_x, destination_y):
        if (destination_x < 0
            or destination_x > self.area.size - self.area.tile_size
            or destination_y < 0
                or destination_y > self.area.size - self.area.tile_size):
            return False
        return True

    def check_monsters(self, destination_x, destination_y):
        for monster in self.monsters:
            if monster.x == destination_x and monster.y == destination_y:
                return monster
        return None

    def fight(self, canvas, attacker, defender):
        while attacker.current_health > 0 and defender.current_health > 0:
            attacker.strike(attacker, defender)
            if self.check_death(canvas, defender):
                return
            defender.strike(defender, attacker)
            if self.check_death(canvas, attacker):
                return

    def check_death(self, canvas, receiver):
        if receiver.current_health <= 0:
            if isinstance(receiver, Monster):
                self.kill_monster(canvas, receiver)
                return True
        return False

    def kill_monster(self, canvas, monster):
        self.monster_dies(canvas, monster)
        self.kill_count += 1
        self.hero.level_up()

    def monster_dies(self, canvas, monster):
        canvas.delete(monster.name)
        self.characters.remove(monster)
        self.monsters.remove(monster)

    def check_next_area(self, canvas):
        if not self.check_boss() and not self.check_key():
            self.next_area(canvas)

    def check_boss(self):
        return any(isinstance(monster, Boss) for monster in self.monsters)

    def check_key(self):
        return any(monster.has_key for monster in self.monsters)

    def next_area(self, canvas):
        self.area_number += 1
        self.area = Area()
        self.hero.restore_health()
        self.characters = self.create_characters()
        self.monsters = self.characters[1:]
        self.area.draw_map(canvas)
        canvas.delete(self.hero.name)
        self.hero.direction = 'hero-down'
        self.spawn_characters(canvas)