import pygame
import math
import random
import os
from pygame.locals import *

# Initialize pygame
pygame.init()

# Screen dimensions
SCREEN_WIDTH = 1000
SCREEN_HEIGHT = 600
GAME_WIDTH = 10
SCREEN = pygame.display.set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame.display.set_caption("Simple Tower Defense")

# Colors
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
GRAY = (200, 200, 200)
DARK_GRAY = (50, 50, 50)
GREEN = (0, 255, 0)
RED = (255, 0, 0)
BLUE = (0, 0, 255)
LIGHT_BLUE = (173, 216, 230)
YELLOW = (255, 255, 0)
ORANGE = (255, 165, 0)

# Fonts
FONT = pygame.font.SysFont("arial", 20)
BIG_FONT = pygame.font.SysFont("arial", 40)

# Clock
clock = pygame.time.Clock()
FPS = 60

# Game Variables
money = 300
lives = 10
score = 0
selected_tower = None

# Path waypoints (smooth path)
path = [
    (50, 100),
    (250, 100),
    (250, 300),
    (450, 300),
    (450, 500),
    (650, 500),
    (650, 200),
    (850, 200),
    (850, 400),
    (950, 400)
]

# Classes

class Particle:
    def __init__(self, position, color, velocity, lifetime=30, radius=3):
        self.position = list(position)
        self.color = color
        self.velocity = velocity
        self.lifetime = lifetime
        self.radius = radius

    def update(self):
        self.position[0] += self.velocity[0]
        self.position[1] += self.velocity[1]
        self.lifetime -= 1
        # Fade out
        if self.lifetime > 0:
            self.color = (
                max(self.color[0] - 5, 0),
                max(self.color[1] - 5, 0),
                max(self.color[2] - 5, 0)
            )

    def draw(self, surface):
        if self.lifetime > 0:
            pygame.draw.circle(surface, self.color, (int(self.position[0]), int(self.position[1])), self.radius)

class Enemy:
    def __init__(self, path, enemy_type="basic"):
        self.path = path
        self.current_point = 0
        self.position = list(self.path[self.current_point])
        self.speed = 1.0
        self.max_health = 100
        self.health = self.max_health
        self.radius = 15
        self.enemy_type = enemy_type
        self.color = RED if enemy_type == "basic" else YELLOW
        self.direction = 0

    def move(self):
        if self.current_point < len(self.path) - 1:
            target = self.path[self.current_point + 1]
            dir_vector = [target[0] - self.position[0], target[1] - self.position[1]]
            distance = math.hypot(dir_vector[0], dir_vector[1])
            if distance != 0:
                dir_vector[0] /= distance
                dir_vector[1] /= distance

            # Move towards the target
            self.position[0] += dir_vector[0] * self.speed
            self.position[1] += dir_vector[1] * self.speed

            # Check if reached the target
            if distance < self.speed:
                self.current_point += 1
        else:
            # Reached the end
            global lives
            lives -= 1
            enemies.remove(self)

    def draw(self, surface):
        # Draw enemy
        pygame.draw.circle(surface, self.color, (int(self.position[0]), int(self.position[1])), self.radius)
        # Draw health bar
        health_bar_width = self.radius * 2
        health_bar_height = 4
        health_ratio = self.health / self.max_health
        pygame.draw.rect(surface, RED, (self.position[0] - self.radius, self.position[1] - self.radius - 10, health_bar_width, health_bar_height))
        pygame.draw.rect(surface, GREEN, (self.position[0] - self.radius, self.position[1] - self.radius - 10, health_bar_width * health_ratio, health_bar_height))

class Tower:
    def __init__(self, position, tower_type="basic"):
        self.position = position
        self.tower_type = tower_type
        self.range = 150
        self.damage = 25
        self.firerate = 60  # Frames between shots
        self.cool_down = 0
        self.radius = 20
        self.color = BLUE
        self.projectile_speed = 7
        self.target = None

        if tower_type == "sniper":
            self.range = 250
            self.damage = 50
            self.firerate = 120
            self.color = ORANGE

    def shoot(self, enemies, projectiles):
        if self.cool_down > 0:
            self.cool_down -= 1
            return

        # Find the nearest enemy within range
        nearest_enemy = None
        min_distance = float('inf')
        for enemy in enemies:
            distance = math.hypot(enemy.position[0] - self.position[0], enemy.position[1] - self.position[1])
            if distance < self.range and distance < min_distance:
                nearest_enemy = enemy
                min_distance = distance

        if nearest_enemy:
            # Create a projectile
            projectile = Projectile(self.position, nearest_enemy, self.damage, self.projectile_speed)
            projectiles.append(projectile)
            self.cool_down = self.firerate
            # Generate particles
            for _ in range(5):
                velocity = [random.uniform(-1, 1), random.uniform(-1, 1)]
                particle = Particle(self.position, YELLOW, velocity, lifetime=15, radius=2)
                particles.append(particle)

    def draw(self, surface):
        # Draw tower
        pygame.draw.circle(surface, self.color, (int(self.position[0]), int(self.position[1])), self.radius)
        # Draw range (optional)
        pygame.draw.circle(surface, GRAY, (int(self.position[0]), int(self.position[1])), self.range, 1)

class Projectile:
    def __init__(self, position, target, damage, speed):
        self.position = list(position)
        self.target = target
        self.damage = damage
        self.speed = speed
        self.radius = 5
        self.color = BLACK
        # Calculate direction
        dir_vector = [target.position[0] - self.position[0], target.position[1] - self.position[1]]
        distance = math.hypot(dir_vector[0], dir_vector[1])
        if distance == 0:
            self.direction = [0, 0]
        else:
            self.direction = [dir_vector[0]/distance, dir_vector[1]/distance]

    def move(self):
        self.position[0] += self.direction[0] * self.speed
        self.position[1] += self.direction[1] * self.speed

    def draw(self, surface):
        pygame.draw.circle(surface, self.color, (int(self.position[0]), int(self.position[1])), self.radius)

# Define a simple button class for GUI
class Button:
    def __init__(self, x, y, width, height, text, tower_type):
        self.rect = pygame.Rect(x, y, width, height)
        self.color = DARK_GRAY
        self.text = text
        self.tower_type = tower_type

    def draw(self, surface, font):
        pygame.draw.rect(surface, self.color, self.rect)
        pygame.draw.rect(surface, BLACK, self.rect, 2)  # Border
        text_surf = font.render(self.text, True, WHITE)
        text_rect = text_surf.get_rect(center=self.rect.center)
        surface.blit(text_surf, text_rect.topleft)

    def is_clicked(self, pos):
        return self.rect.collidepoint(pos)

# Create GUI Buttons for tower selection
button_width = 150
button_height = 60
buttons = [
    Button(GAME_WIDTH + 25, 50, button_width, button_height, "Basic Tower", "basic"),
    Button(GAME_WIDTH + 25, 150, button_width, button_height, "Sniper Tower", "sniper"),
]

# Groups
enemies = []
towers = []
projectiles = []
particles = []

# Enemy spawn timer
spawn_event = pygame.USEREVENT + 1
pygame.time.set_timer(spawn_event, 2000)  # Spawn enemy every 2 seconds

def draw_path(surface, path):
    # Draw the path with connected circles and lines
    for point in path:
        pygame.draw.circle(surface, DARK_GRAY, point, 15)
    for i in range(len(path)-1):
        pygame.draw.line(surface, DARK_GRAY, path[i], path[i+1], 10)

def draw_ui(surface):
    pygame.draw.rect(surface, GRAY, GUI_RECT)
    # Draw buttons
    for button in buttons:
        button.draw(surface, FONT)

    # Display selected tower
    if selected_tower:
        selected_text = FONT.render(f"Selected: {selected_tower.capitalize()}", True, BLACK)
    else:
        selected_text = FONT.render("Selected: None", True, BLACK)
    surface.blit(selected_text, (GAME_WIDTH + 20, 250))

    # Display money, lives, and score
    money_text = FONT.render(f"Money: ${money}", True, BLACK)
    lives_text = FONT.render(f"Lives: {lives}", True, BLACK)
    score_text = FONT.render(f"Score: {score}", True, BLACK)
    surface.blit(money_text, (GAME_WIDTH + 20, 300))
    surface.blit(lives_text, (GAME_WIDTH + 20, 330))
    surface.blit(score_text, (GAME_WIDTH + 20, 360))

def game_over_screen(surface):
    over_text = BIG_FONT.render("GAME OVER", True, RED)
    restart_text = FONT.render("Press R to Restart or Q to Quit.", True, BLACK)
    surface.blit(over_text, (SCREEN_WIDTH//2 - over_text.get_width()//2, SCREEN_HEIGHT//2 - 50))
    surface.blit(restart_text, (SCREEN_WIDTH//2 - restart_text.get_width()//2, SCREEN_HEIGHT//2))

def restart_game():
    global enemies, towers, projectiles, particles, money, lives, score
    enemies = []
    towers = []
    projectiles = []
    particles = []
    money = 300
    lives = 10
    score = 0

def main():
    global selected_tower, enemies, towers, projectiles, money, lives, score, particles
    running = True
    game_over = False

    placing_tower = False
    placement_position = (0, 0)

    while running:
        clock.tick(FPS)

        for event in pygame.event.get():
            if event.type == QUIT:
                running = False

            elif event.type == spawn_event and not game_over:
                enemy_type = random.choice(["basic", "advanced"])
                enemy = Enemy(path, enemy_type=enemy_type)
                enemies.append(enemy)

            elif event.type == MOUSEBUTTONDOWN and not game_over:
                mouse_pos = pygame.mouse.get_pos()
                if event.button == 1:  # Left click
                    # Check if clicked on GUI buttons
                    if mouse_pos[0] > GAME_WIDTH:
                        for button in buttons:
                            if button.is_clicked(mouse_pos):
                                selected_tower = button.tower_type
                                placing_tower = True
                                break
                    else:
                        # Place tower if selected
                        if placing_tower and selected_tower and money >= 50:
                            tower = Tower(mouse_pos, selected_tower)
                            # Check if the spot is valid (not on path and not overlapping with other towers)
                            valid = True
                            for point in path:
                                dist = math.hypot(mouse_pos[0] - point[0], mouse_pos[1] - point[1])
                                if dist < 50:
                                    valid = False
                                    break
                            for existing_tower in towers:
                                dist = math.hypot(mouse_pos[0] - existing_tower.position[0], mouse_pos[1] - existing_tower.position[1])
                                if dist < 50:
                                    valid = False
                                    break
                            if valid:
                                towers.append(tower)
                                money -= 50
                                # Generate placement particles
                                for _ in range(20):
                                    angle = random.uniform(0, 2 * math.pi)
                                    velocity = [math.cos(angle) * random.uniform(1, 3), math.sin(angle) * random.uniform(1, 3)]
                                    particle = Particle(mouse_pos, LIGHT_BLUE, velocity, lifetime=30, radius=3)
                                    particles.append(particle)
                            else:
                                print("Invalid placement!")
                            placing_tower = False
                            selected_tower = None

            elif event.type == KEYDOWN and game_over:
                if event.key == K_r:
                    restart_game()
                    game_over = False
                elif event.key == K_q:
                    running = False

        if placing_tower:
            placement_position = pygame.mouse.get_pos()

        if not game_over:
            # Update enemies
            for enemy in enemies[:]:
                enemy.move()

            # Update towers
            for tower in towers:
                tower.shoot(enemies, projectiles)

            # Update projectiles
            for projectile in projectiles[:]:
                projectile.move()

            # Update particles
            for particle in particles[:]:
                particle.update()
                if particle.lifetime <= 0:
                    particles.remove(particle)

            # Check for game over
            if lives <= 0:
                game_over = True

        # Drawing
        SCREEN.fill(WHITE)

        # Draw path
        draw_path(SCREEN, path)

        # Draw towers
        for tower in towers:
            tower.draw(SCREEN)

        # Draw enemies
        for enemy in enemies:
            enemy.draw(SCREEN)

        # Draw projectiles
        for projectile in projectiles:
            projectile.draw(SCREEN)

        # Draw particles
        for particle in particles:
            particle.draw(SCREEN)

        # Draw tower placement preview
        if placing_tower and selected_tower:
            tower_radius = 20
            range_radius = 150 if selected_tower == "basic" else 250

            # Determine if placement is valid
            valid = True
            for point in path:
                dist = math.hypot(placement_position[0] - point[0], placement_position[1] - point[1])
                if dist < 50:
                    valid = False
                    break
            for existing_tower in towers:
                dist = math.hypot(placement_position[0] - existing_tower.position[0], placement_position[1] - existing_tower.position[1])
                if dist < 50:
                    valid = False
                    break

            # Draw semi-transparent range circle
            if valid:
                circle_color = (0, 255, 0, 100)  # Green with transparency
            else:
                circle_color = (255, 0, 0, 100)  # Red with transparency

            s = pygame.Surface((SCREEN_WIDTH, SCREEN_HEIGHT), pygame.SRCALPHA)
            pygame.draw.circle(s, circle_color, placement_position, range_radius, 2)
            SCREEN.blit(s, (0, 0))

            # Draw tower preview
            pygame.draw.circle(SCREEN, BLUE, placement_position, tower_radius, 2)

        # Draw UI
        draw_ui(SCREEN)

        if game_over:
            game_over_screen(SCREEN)

        pygame.display.flip()

    if __name__ == "__main__":
        main()

    pygame.quit()


MAKE MAP LOOK GOOD, CREATE IT ALL OWRKING