import pygame
import sys
import math
import os

# Initialize Pygame
pygame.init()

# Screen dimensions
WIDTH, HEIGHT = 800, 600
SCREEN = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Top-Down Car and Ball Simulation")

# Clock for controlling frame rate
CLOCK = pygame.time.Clock()
FPS = 60

# Colors
WHITE = (255, 255, 255)

# Paths to images
CAR_IMAGE_PATH = os.path.join('items', 'car.png')
BALL_IMAGE_PATH = os.path.join('items', 'ball.png')

# Load and scale images
def load_and_scale_image(path, scale_factor=1.0):
    try:
        image = pygame.image.load(path).convert_alpha()
        if scale_factor != 1.0:
            size = image.get_size()
            image = pygame.transform.scale(
                image, (int(size[0] * scale_factor), int(size[1] * scale_factor))
            )
        return image
    except pygame.error as e:
        print(f"Error loading image '{path}': {e}")
        sys.exit()

# Scaling factors
CAR_SCALE = 0.3  # Make the car smaller
BALL_SCALE = 0.01  # Ball scale set to 0.1

CAR_IMAGE = load_and_scale_image(CAR_IMAGE_PATH, CAR_SCALE)
BALL_IMAGE = load_and_scale_image(BALL_IMAGE_PATH, BALL_SCALE)

# Constants
GRAVITY = 0  # No gravity for top-down view
FRICTION = 0.99  # Slight friction to slow down over time

# Helper function to rotate an image while keeping its center
def rotate_image(image, angle):
    """Rotates an image while keeping its center."""
    rotated_image = pygame.transform.rotate(image, angle)
    rotated_rect = rotated_image.get_rect(center=image.get_rect().center)
    return rotated_image, rotated_rect

class GameObject:
    def __init__(self, image, x, y):
        self.original_image = image
        self.image = image
        self.rect = self.image.get_rect(center=(x, y))
        self.pos = pygame.math.Vector2(x, y)
        self.vel = pygame.math.Vector2(0, 0)
        self.acc = pygame.math.Vector2(0, 0)
        self.angle = 0  # For rotation
        self.mask = pygame.mask.from_surface(self.image)

    def update(self):
        # Update velocity and position
        self.vel += self.acc
        self.vel *= FRICTION
        self.pos += self.vel
        self.rect.center = self.pos

        # Reset acceleration
        self.acc = pygame.math.Vector2(0, 0)

    def draw(self, surface):
        surface.blit(self.image, self.rect.topleft)

class Car(GameObject):
    def __init__(self, image, x, y):
        super().__init__(image, x, y)
        self.speed = 0
        self.rotation_speed = 5  # Degrees per frame

    def handle_keys(self):
        keys = pygame.key.get_pressed()
        if keys[pygame.K_UP]:
            # Accelerate forward
            direction = pygame.math.Vector2(
                math.cos(math.radians(self.angle)), -math.sin(math.radians(self.angle))
            )
            self.acc += direction * 0.5
        if keys[pygame.K_DOWN]:
            # Accelerate backward
            direction = pygame.math.Vector2(
                math.cos(math.radians(self.angle)), -math.sin(math.radians(self.angle))
            )
            self.acc -= direction * 0.5
        if keys[pygame.K_LEFT]:
            # Rotate left (counter-clockwise)
            self.angle += self.rotation_speed
            self.angle %= 360  # Ensure the angle stays within 0-359
            self.image, self.rect = rotate_image(self.original_image, self.angle)
            self.rect.center = self.pos
            self.mask = pygame.mask.from_surface(self.image)
        if keys[pygame.K_RIGHT]:
            # Rotate right (clockwise)
            self.angle -= self.rotation_speed
            self.angle %= 360
            self.image, self.rect = rotate_image(self.original_image, self.angle)
            self.rect.center = self.pos
            self.mask = pygame.mask.from_surface(self.image)

    def update(self):
        self.handle_keys()
        super().update()

        # Keep car within screen bounds
        if self.pos.x < self.rect.width / 2:
            self.pos.x = self.rect.width / 2
            self.vel.x = 0
        elif self.pos.x > WIDTH - self.rect.width / 2:
            self.pos.x = WIDTH - self.rect.width / 2
            self.vel.x = 0
        if self.pos.y < self.rect.height / 2:
            self.pos.y = self.rect.height / 2
            self.vel.y = 0
        elif self.pos.y > HEIGHT - self.rect.height / 2:
            self.pos.y = HEIGHT - self.rect.height / 2
            self.vel.y = 0
        self.rect.center = self.pos  # Update rect position

class Ball(GameObject):
    def __init__(self, image, x, y):
        super().__init__(image, x, y)
        self.elasticity = 0.9  # Bounciness factor
        self.mask = pygame.mask.from_surface(self.image)

    def update(self):
        # No gravity in top-down view, but can apply friction
        super().update()

        # Keep ball within screen bounds
        if self.pos.x <= self.rect.width / 2:
            self.pos.x = self.rect.width / 2
            self.vel.x *= -self.elasticity
        elif self.pos.x >= WIDTH - self.rect.width / 2:
            self.pos.x = WIDTH - self.rect.width / 2
            self.vel.x *= -self.elasticity
        if self.pos.y <= self.rect.height / 2:
            self.pos.y = self.rect.height / 2
            self.vel.y *= -self.elasticity
        elif self.pos.y >= HEIGHT - self.rect.height / 2:
            self.pos.y = HEIGHT - self.rect.height / 2
            self.vel.y *= -self.elasticity
        self.rect.center = self.pos  # Update rect position

class Game:
    def __init__(self):
        # Initialize car at the center
        self.car = Car(CAR_IMAGE, WIDTH / 2, HEIGHT / 2)
        # Initialize ball at some position
        self.ball = Ball(BALL_IMAGE, WIDTH / 2 + 150, HEIGHT / 2)
        # Optionally, set initial velocity for the ball
        self.ball.vel = pygame.math.Vector2(-2, 0)

    def check_collision(self):
        # Use masks for collision detection
        offset = (
            int(self.ball.rect.left - self.car.rect.left),
            int(self.ball.rect.top - self.car.rect.top),
        )
        collision_point = self.car.mask.overlap(self.ball.mask, offset)
        if collision_point:
            # Collision detected, handle collision response
            # Calculate the normal vector
            dx = self.ball.pos.x - self.car.pos.x
            dy = self.ball.pos.y - self.car.pos.y
            distance = math.hypot(dx, dy)
            if distance == 0:
                distance = 1  # Prevent division by zero

            # Normal vector
            nx = dx / distance
            ny = dy / distance

            # Relative velocity
            rvx = self.ball.vel.x - self.car.vel.x
            rvy = self.ball.vel.y - self.car.vel.y

            # Relative velocity along the normal
            vel_along_normal = rvx * nx + rvy * ny

            # Do not resolve if velocities are separating
            if vel_along_normal > 0:
                return

            # Calculate restitution (bounciness)
            restitution = min(self.ball.elasticity, 0.5)  # Assuming car has less elasticity

            # Impulse scalar
            j = -(1 + restitution) * vel_along_normal
            j /= (1 / 1 + 1 / 1)  # Assuming mass=1 for both

            # Apply impulse
            impulse = pygame.math.Vector2(j * nx, j * ny)
            self.ball.vel += impulse
            self.car.vel -= impulse  # Opposite direction for car

            # Separate the objects to prevent sticking
            overlap = 1  # You may adjust this value
            self.ball.pos += pygame.math.Vector2(nx, ny) * (overlap / 2)
            self.car.pos -= pygame.math.Vector2(nx, ny) * (overlap / 2)
            self.car.rect.center = self.car.pos
            self.ball.rect.center = self.ball.pos

    def run(self):
        running = True
        while running:
            CLOCK.tick(FPS)
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    running = False

            # Update game objects
            self.car.update()
            self.ball.update()

            # Check collisions
            self.check_collision()

            # Draw everything
            SCREEN.fill(WHITE)
            # Draw objects
            self.car.draw(SCREEN)
            self.ball.draw(SCREEN)

            # Update the display
            pygame.display.flip()

        pygame.quit()
        sys.exit()

if __name__ == "__main__":
    # Ensure the 'items' directory exists with required images
    required_files = [CAR_IMAGE_PATH, BALL_IMAGE_PATH]
    for file in required_files:
        if not os.path.isfile(file):
            print(f"Required file '{file}' not found. Please ensure it exists.")
            sys.exit()

    game = Game()
    game.run()

Add goals and score display, implement replay after a goal, provide full code