import pygame
import random
import math
import os
import sys

def resource_path(relative_path):

    try:
        base_path = sys._MEIPASS

    except Exception:
        base_path = os.path.abspath(".")

    return os.path.join(base_path, relative_path)

class MazeRunnerGame:
    def __init__(self):
        pygame.init()
        self.unit_size = 40
        self.width, self.height = 19 * self.unit_size, 15 * self.unit_size
        self.window = pygame.display.set_mode((self.width, self.height))
        pygame.display.set_caption("Maze Runner")
        self.image_folder = 'images'
        self.player_img = self.load_image('bae.png')
        self.enemy_img = self.load_random_enemy_image()
        self.maze = self.generate_maze()
        self.enemies = []
        self.player_pos = [self.unit_size, self.unit_size]
        self.collectibles = []
        self.score = 0
        self.goal_pos = self.select_goal_position()
        self.enemy_delay = 200
        self.enemy_timer = 0
        self.enemy_cooldown_time = 1000
        self.lives = 3
        self.run = True
        self.can_pass_walls = False
        self.pass_walls_timer = 0
        self.spawn_collectibles(5)  # Spawn initial collectibles
        self.spawn_enemies()  # Ensure enemies are spawned
        self.attack_mode = False
        self.attack_mode_timer = 0
        self.red_square_pos = None  # This will be set by spawn_red_square
        self.spawn_red_square()
        self.check_red_square_collision()
        self.draw_red_square()
        pygame.font.init()  # Initialize the font module
        self.font = pygame.font.SysFont("arial", 24)  # Choose an appropriate font and size

    def load_image(self, filename):
        filepath = resource_path(os.path.join(self.image_folder, filename))
        img = pygame.image.load(filepath)
        return pygame.transform.scale(img, (self.unit_size, self.unit_size))

    def load_random_enemy_image(self):
        enemy_images = ['huh.png', 'tower.png', 'chikaa.png', 'profv2.png']
        chosen_image = random.choice(enemy_images)
        return self.load_image(chosen_image)

    def generate_maze(self):
        maze_width, maze_height = self.width // self.unit_size, self.height // self.unit_size
        maze = [['#' for _ in range(maze_width)] for _ in range(maze_height)]
        def dfs(x, y):
            directions = [(1, 0), (-1, 0), (0, 1), (0, -1)]
            random.shuffle(directions)
            for dx, dy in directions:
                nx, ny = x + 2 * dx, y + 2 * dy
                if 0 <= nx < maze_width and 0 <= ny < maze_height and maze[ny][nx] == '#':
                    maze[y + dy][x + dx] = ' '
                    maze[ny][nx] = ' '
                    dfs(nx, ny)
        start_x, start_y = random.randrange(1, maze_width, 2), random.randrange(1, maze_height, 2)
        dfs(start_x, start_y)
        return maze

    def spawn_red_square(self):
        maze_width, maze_height = self.width // self.unit_size, self.height // self.unit_size
        valid_spawn = False
        while not valid_spawn:
            x = random.randint(1, maze_width - 2) * self.unit_size
            y = random.randint(1, maze_height - 2) * self.unit_size
            if self.maze[y // self.unit_size][x // self.unit_size] == ' ':
                self.red_square_pos = (x, y)
                valid_spawn = True

    def check_red_square_collision(self):
        if self.red_square_pos and math.dist(self.player_pos, self.red_square_pos) < self.unit_size:
            self.attack_mode = True
            self.attack_mode_timer = 2000  # around 9 seconds of attack mode
            self.red_square_pos = None  # Remove the current red square
            # Respawn the red square at a new location
            self.spawn_red_square()
            self.spawn_enemies()  # Ensure enemies are spawned after reaching the goal if needed

    def attack_enemies(self):
        if not self.attack_mode:
            return  # Exit if not in attack mode

        for enemy in self.enemies[:]:
            if math.dist(self.player_pos, enemy['position']) < self.unit_size:
                self.enemies.remove(enemy)  # Remove the enemy on collision
                self.score += 100  # Increase the score by 100 for each enemy killed

    def draw_red_square(self):
        if self.red_square_pos:
            pygame.draw.rect(self.window, (255, 0, 0), (*self.red_square_pos, self.unit_size, self.unit_size))

    def select_goal_position(self):
        maze_width, maze_height = self.width // self.unit_size, self.height // self.unit_size
        valid_positions = [(x, y) for x in range(1, maze_width - 1) for y in range(1, maze_height - 1) if self.maze[y][x] == ' ']
        goal_pos = random.choice(valid_positions)
        return goal_pos[0] * self.unit_size, goal_pos[1] * self.unit_size

    def move_entity(self, entity, direction):
        x, y = entity
        next_x, next_y = x, y

        # Determine the next position based on the direction
        if direction == "LEFT":
            next_x -= self.unit_size
        elif direction == "RIGHT":
            next_x += self.unit_size
        elif direction == "UP":
            next_y -= self.unit_size
        elif direction == "DOWN":
            next_y += self.unit_size

        # Check if the entity is the player and if they can pass through walls
        is_player = entity == self.player_pos
        can_move_through_walls = is_player and self.can_pass_walls and self.pass_walls_timer > 0

        # Allow moving through walls only if it's the player with the ability activated
        if can_move_through_walls or (0 <= next_x < self.width and 0 <= next_y < self.height and self.maze[next_y // self.unit_size][next_x // self.unit_size] != '#'):
            x, y = next_x, next_y
        return [x, y]

    def spawn_enemies(self):
        maze_width, maze_height = self.width // self.unit_size, self.height // self.unit_size
        for _ in range(3):  # Adjust the number as needed
            valid_spawn = False
            while not valid_spawn:
                x = random.randint(0, maze_width - 1) * self.unit_size
                y = random.randint(0, maze_height - 1) * self.unit_size
                if self.maze[y // self.unit_size][x // self.unit_size] == ' ':
                    self.enemies.append({
                        'position': [x, y],
                        'cooldown_timer': 0,
                        'movement_cooldown': 100  # Movement cooldown in milliseconds or game loops
                    })
                    valid_spawn = True

    def spawn_collectibles(self, count):
        while len(self.collectibles) < count:
            maze_width, maze_height = self.width // self.unit_size, self.height // self.unit_size
            valid_positions = [(x, y) for x in range(maze_width) for y in range(maze_height) if self.maze[y][x] == ' ']
            collectible_pos = random.choice(valid_positions)
            self.collectibles.append((collectible_pos[0] * self.unit_size, collectible_pos[1] * self.unit_size))

    def check_collectibles_collision(self):
        for collectible in self.collectibles[:]:
            if math.dist(self.player_pos, collectible) < self.unit_size:
                self.collectibles.remove(collectible)
                self.score += 10
                if len(self.collectibles) < 5:
                    self.spawn_collectibles(5)  # Maintain a minimum of 5 collectibles

    def draw_lives(self):
        font = pygame.font.SysFont(None, 24)
        lives_text = font.render(f'Lives: {self.lives}', True, (255, 255, 255))
        self.window.blit(lives_text, (10, 10))

    def draw_score(self):
        font = pygame.font.SysFont(None, 24)
        score_text = font.render(f'Score: {self.score}', True, (255, 255, 0))
        self.window.blit(score_text, (self.width - 120, 10))

    def draw_collectibles(self):
        for collectible in self.collectibles:
            pygame.draw.rect(self.window, (255, 255, 0), (*collectible, self.unit_size, self.unit_size))

    def check_collision(self):
        if self.attack_mode:
            # Checks if player got the attack power up, if so the player takes no damage
            return

        for enemy in self.enemies:
            if (self.player_pos[0] < enemy['position'][0] + self.unit_size and
                    self.player_pos[0] + self.unit_size > enemy['position'][0] and
                    self.player_pos[1] < enemy['position'][1] + self.unit_size and
                    self.player_pos[1] + self.unit_size > enemy['position'][1]):
                if enemy['cooldown_timer'] == 0:
                    self.lives -= 1
                    enemy['cooldown_timer'] = self.enemy_cooldown_time

    def get_chase_direction(self, enemy_position):
        player_x, player_y = self.player_pos
        enemy_x, enemy_y = enemy_position

        if abs(player_x - enemy_x) > abs(player_y - enemy_y):
            return "LEFT" if player_x < enemy_x else "RIGHT"
        else:
            return "UP" if player_y < enemy_y else "DOWN"

    def update_enemy_positions(self):
        for enemy in self.enemies:
            # Decrease the cooldown timer if it's above 0
            if enemy['movement_cooldown'] > 0:
                enemy['movement_cooldown'] -= 1
                continue  # Skip moving this enemy if it's still cooling down

            # Reset the cooldown timer after the enemy moves
            enemy['movement_cooldown'] = 100  # Reset to its original value or another value for variable speeds

            # logic to decide movement (random or chasing)
            if random.random() < 0.5:  # 50% chance to chase
                chase_direction = self.get_chase_direction(enemy['position'])
                enemy['position'] = self.move_entity(enemy['position'], chase_direction)
            else:
                direction = random.choice(["LEFT", "RIGHT", "UP", "DOWN"])
                enemy['position'] = self.move_entity(enemy['position'], direction)

    def update_attack_mode(self):
        if self.attack_mode and self.attack_mode_timer > 0:
            self.attack_mode_timer -= 1
        elif self.attack_mode_timer <= 0:
            self.attack_mode = False

    def game_loop(self):
        while self.run:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    self.run = False
                elif event.type == pygame.KEYDOWN:
                    if event.key == pygame.K_a:  # Change from K_LEFT to K_a
                        self.player_pos = self.move_entity(self.player_pos, "LEFT")
                    elif event.key == pygame.K_d:  # Change from K_RIGHT to K_d
                        self.player_pos = self.move_entity(self.player_pos, "RIGHT")
                    elif event.key == pygame.K_w:  # Change from K_UP to K_w
                        self.player_pos = self.move_entity(self.player_pos, "UP")
                    elif event.key == pygame.K_s:  # Change from K_DOWN to K_s
                        self.player_pos = self.move_entity(self.player_pos, "DOWN")

            self.enemy_timer += 1
            if self.enemy_timer >= self.enemy_delay:
                self.enemy_timer = 0
                for enemy in self.enemies:
                    direction = random.choice(["LEFT", "RIGHT", "UP", "DOWN"])
                    enemy['position'] = self.move_entity(enemy['position'], direction)

            for enemy in self.enemies:
                if enemy['cooldown_timer'] > 0:
                    enemy['cooldown_timer'] -= 1
            self.update_enemy_positions()
            self.check_collision()
            self.check_collectibles_collision()

            if math.dist(self.player_pos, self.goal_pos) < self.unit_size:
                self.can_pass_walls = True
                self.pass_walls_timer = 1500  # around 5 seconds
                self.goal_pos = self.select_goal_position()
                self.spawn_enemies()  # Ensure enemies are spawned after reaching the goal if needed

            if self.can_pass_walls:
                self.pass_walls_timer -= 1
                if self.pass_walls_timer <= 0:
                    self.can_pass_walls = False

            if self.can_pass_walls:
                pass_walls_text = self.font.render("Wall Pass Active", True, (0, 255, 0))
                self.window.blit(pass_walls_text, (10, self.height - 40))

            if self.attack_mode:
                attack_mode_text = self.font.render("Attack Mode Active", True, (255, 0, 0))
                self.window.blit(attack_mode_text, (10, self.height - 70))

            pygame.display.update()
            self.update_attack_mode()
            self.window.fill((255, 255, 255))

            for i in range(len(self.maze)):
                for j in range(len(self.maze[i])):
                    if self.maze[i][j] == '#':
                        pygame.draw.rect(self.window, (0, 0, 0), (j * self.unit_size, i * self.unit_size, self.unit_size, self.unit_size))
            self.window.blit(self.player_img, self.player_pos)
            for enemy in self.enemies:
                self.window.blit(self.enemy_img, enemy['position'])
            pygame.draw.rect(self.window, (0, 255, 0), (*self.goal_pos, self.unit_size, self.unit_size))  # Draw goal
            self.draw_lives()
            self.draw_score()
            self.draw_collectibles()
            self.check_red_square_collision()  # Check collision with the red square
            self.attack_enemies()  # Check if player attacks enemies

            # Rendering section before display update
            self.draw_red_square()  # Draw the red square

            pygame.display.update()

            if self.lives <= 0:
                print("Game over! You ran out of lives.")
                self.run = False

if __name__ == "__main__":
    game = MazeRunnerGame()
    game.game_loop()
    pygame.quit()