import pygame
import math
import sys

# Initialize Pygame
pygame.init()

# Screen dimensions
WIDTH, HEIGHT = 1000, 600
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Angry Birds-like Game with Realistic Falling Walls")

# Colors
WHITE = (255, 255, 255)
BROWN = (139, 69, 19)
GREEN = (34, 139, 34)
RED = (255, 0, 0)
YELLOW = (255, 255, 0)
GRAY = (128, 128, 128)
BLACK = (0, 0, 0)
BLUE = (0, 0, 255)

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

# Gravity constants
GRAVITY_BIRD = 0.5  # Gravity affecting the bird
GRAVITY_BLOCK = 0.3  # Gravity affecting the blocks for slower fall

# Maximum pull distance for the slingshot
MAX_PULL = 100

# Define Game Objects

class Bird:
    def __init__(self, x, y):
        self.radius = 15
        self.color = RED
        self.initial_pos = (x, y)
        self.x = float(x)
        self.y = float(y)
        self.vel_x = 0.0
        self.vel_y = 0.0
        self.launched = False
        self.alive = True

    def update(self):
        if self.launched and self.alive:
            self.vel_y += GRAVITY_BIRD  # Apply gravity to vertical velocity
            self.x += self.vel_x
            self.y += self.vel_y

            # Collision with ground
            if self.y + self.radius >= HEIGHT - 50:
                self.y = HEIGHT - 50 - self.radius
                self.vel_y *= -0.6  # Bounce with damping
                self.vel_x *= 0.7   # Horizontal damping

            # Collision with walls
            if self.x + self.radius > WIDTH or self.x - self.radius < 0:
                self.alive = False

            # Collision with ceiling
            if self.y - self.radius < 0:
                self.y = self.radius
                self.vel_y *= -0.6

    def reset(self):
        self.x, self.y = self.initial_pos
        self.vel_x = 0.0
        self.vel_y = 0.0
        self.launched = False
        self.alive = True

    def draw(self, screen):
        if self.alive:
            pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.radius)

class Pig:
    def __init__(self, x, y):
        self.radius = 15
        self.color = GREEN
        self.x = float(x)
        self.y = float(y)
        self.alive = True

    def draw(self, screen):
        if self.alive:
            pygame.draw.circle(screen, self.color, (int(self.x), int(self.y)), self.radius)

class Block:
    def __init__(self, x, y, width, height):
        self.color = BROWN
        self.initial_x = float(x)
        self.initial_y = float(y)
        self.width = width
        self.height = height
        self.rect = pygame.Rect(int(self.initial_x), int(self.initial_y), self.width, self.height)
        self.health = 3  # Number of hits it can take
        self.falling = False
        self.vel_x = 0.0
        self.vel_y = 0.0
        self.x = float(x)  # Initialize as float for precise movement
        self.y = float(y)

    def update(self):
        if self.falling:
            self.vel_y += GRAVITY_BLOCK  # Apply gravity for blocks
            # No horizontal movement
            self.y += self.vel_y
            self.rect.y = int(self.y)

            # Collision with ground
            if self.y + self.height >= HEIGHT - 50:
                self.y = HEIGHT - 50 - self.height
                self.vel_y = 0.0
                # No horizontal velocity; ensure block stays straight down

    def set_falling(self, impact_velocity_x, impact_velocity_y):
        if not self.falling:
            self.falling = True
            # Set horizontal velocity to zero to make block fall straight down
            self.vel_x = 0.0
            # Set vertical velocity based on bird's vertical impact velocity
            self.vel_y = max(impact_velocity_y * 0.5, 1.0)  # Ensure some initial velocity

    def reset(self):
        self.falling = False
        self.vel_x = 0.0
        self.vel_y = 0.0
        self.health = 3
        self.x = self.initial_x
        self.y = self.initial_y
        self.rect.x = int(self.x)
        self.rect.y = int(self.y)

    def draw(self, screen):
        if self.health > 0:
            # Draw block
            pygame.draw.rect(screen, self.color, self.rect)
            # Draw block borders
            pygame.draw.rect(screen, BLACK, self.rect, 2)  # 2-pixel thick border

            # Add internal lines for better visual distinction (optional)
            pygame.draw.line(screen, BLACK, (self.rect.x, self.rect.y), 
                             (self.rect.x + self.width, self.rect.y + self.height), 1)
            pygame.draw.line(screen, BLACK, (self.rect.x + self.width, self.rect.y), 
                             (self.rect.x, self.rect.y + self.height), 1)

            # Draw health bar above the block
            health_bar_width = self.width
            health_bar_height = 5
            health_ratio = self.health / 3
            pygame.draw.rect(
                screen,
                GREEN,
                (self.rect.x, self.rect.y - 10, health_bar_width * health_ratio, health_bar_height)
            )
            pygame.draw.rect(
                screen,
                BLACK,
                (self.rect.x, self.rect.y - 10, health_bar_width, health_bar_height),
                1
            )

class Slingshot:
    def __init__(self, x, y):
        self.x = x
        self.y = y
        self.band_color = BLACK
        self.band_width = 5

    def draw(self, screen, bird):
        if not bird.launched:
            # Draw left side of the slingshot
            pygame.draw.line(screen, self.band_color, (self.x - 10, self.y), (bird.x, bird.y), self.band_width)
            # Draw right side of the slingshot
            pygame.draw.line(screen, self.band_color, (self.x + 10, self.y), (bird.x, bird.y), self.band_width)
        # Draw slingshot supports
        pygame.draw.rect(screen, BROWN, (self.x - 15, self.y, 10, 50))
        pygame.draw.rect(screen, BROWN, (self.x + 5, self.y, 10, 50))

class Game:
    def __init__(self):
        self.slingshot_pos = (150, HEIGHT - 100)
        self.bird = Bird(*self.slingshot_pos)
        self.slingshot = Slingshot(*self.slingshot_pos)
        self.pigs = [
            Pig(800, HEIGHT - 65),
            Pig(850, HEIGHT - 65),
            Pig(825, HEIGHT - 115)
        ]
        self.blocks = [
            # Creating a vertical wall of 5 blocks
            Block(700, HEIGHT - 65 - 50 * i, 50, 50) for i in range(5)
        ]
        self.dragging = False
        self.font = pygame.font.SysFont(None, 36)
        self.score = 0
        self.trajectory_points = []

    def reset(self):
        self.bird.reset()
        self.pigs = [
            Pig(800, HEIGHT - 65),
            Pig(850, HEIGHT - 65),
            Pig(825, HEIGHT - 115)
        ]
        self.blocks = [
            Block(700, HEIGHT - 65 - 50 * i, 50, 50) for i in range(5)
        ]
        self.score = 0
        self.trajectory_points = []

    def handle_events(self):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()

            elif event.type == pygame.MOUSEBUTTONDOWN:
                mx, my = pygame.mouse.get_pos()
                distance = math.hypot(mx - self.bird.x, my - self.bird.y)
                if distance <= self.bird.radius and not self.bird.launched and self.bird.alive:
                    self.dragging = True

            elif event.type == pygame.MOUSEBUTTONUP:
                if self.dragging:
                    self.dragging = False
                    self.launch_bird()

            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_r:
                    self.reset()

    def launch_bird(self):
        # Calculate the difference between slingshot and release point
        mx, my = pygame.mouse.get_pos()
        dx = self.slingshot.x - mx
        dy = self.slingshot.y - my
        distance = math.hypot(dx, dy)

        # Limit the maximum pull distance
        if distance > MAX_PULL:
            dx = dx * MAX_PULL / distance
            dy = dy * MAX_PULL / distance
            distance = MAX_PULL

        # Calculate launch angle and power
        angle = math.atan2(dy, dx)
        power = distance / 2  # Adjust scaling factor for better velocity

        # Set bird's velocity based on pull
        self.bird.vel_x = math.cos(angle) * power
        self.bird.vel_y = math.sin(angle) * power
        self.bird.launched = True

        # Clear trajectory after launch
        self.trajectory_points = []

    def calculate_trajectory(self, bird_pos, velocity, steps=60, dt=0.1):
        points = []
        x, y = bird_pos
        vx, vy = velocity

        for _ in range(steps):
            vy += GRAVITY_BIRD * dt  # Apply gravity
            x += vx * dt
            y += vy * dt

            # Stop trajectory if bird hits the ground
            if y > HEIGHT - 50:
                break

            points.append((int(x), int(y)))

        return points

    def update(self):
        if self.bird.launched and self.bird.alive:
            self.bird.update()
            self.check_collisions()

        if self.dragging and self.bird.alive:
            mx, my = pygame.mouse.get_pos()
            dx = self.slingshot.x - mx
            dy = self.slingshot.y - my
            distance = math.hypot(dx, dy)

            # Limit the pull distance
            if distance > MAX_PULL:
                dx = dx * MAX_PULL / distance
                dy = dy * MAX_PULL / distance
                distance = MAX_PULL

            # Temporary position while dragging
            temp_x = self.slingshot.x - dx
            temp_y = self.slingshot.y - dy
            self.bird.x = temp_x
            self.bird.y = temp_y

            # Calculate trajectory based on current pull
            temp_angle = math.atan2(dy, dx)
            temp_power = min(math.hypot(dx, dy), MAX_PULL) / 2  # Must match launch power scaling
            temp_vel_x = math.cos(temp_angle) * temp_power
            temp_vel_y = math.sin(temp_angle) * temp_power
            self.trajectory_points = self.calculate_trajectory(
                (self.bird.x, self.bird.y),
                (temp_vel_x, temp_vel_y)
            )
        elif not self.bird.launched and not self.dragging and self.bird.alive:
            # Keep bird at slingshot position when not dragging or launched
            self.bird.x, self.bird.y = self.slingshot_pos
            self.trajectory_points = []

        # Update falling blocks
        for block in self.blocks:
            block.update()

    def check_collisions(self):
        # Check collision between bird and pigs
        for pig in self.pigs:
            if pig.alive and self.bird.alive:
                distance = math.hypot(self.bird.x - pig.x, self.bird.y - pig.y)
                if distance <= self.bird.radius + pig.radius:
                    pig.alive = False
                    self.score += 100
                    self.bird.alive = False  # Remove bird after hitting a pig

        # Check collision between bird and blocks
        for block in self.blocks:
            if block.health > 0 and not block.falling:
                # Check collision using circle-rectangle collision
                collision = self.circle_rect_collision(self.bird.x, self.bird.y, self.bird.radius, block.rect)
                if collision:
                    block.health -= 1
                    self.score += 50
                    # Set block to falling state with bird's current vertical velocity
                    block.set_falling(self.bird.vel_x, self.bird.vel_y)

    @staticmethod
    def circle_rect_collision(cx, cy, radius, rect):
        # Find the closest point on the rectangle to the circle
        closest_x = max(rect.left, min(cx, rect.right))
        closest_y = max(rect.top, min(cy, rect.bottom))

        # Calculate the distance between the circle's center and this closest point
        distance_x = cx - closest_x
        distance_y = cy - closest_y

        # If the distance is less than the circle's radius, there's a collision
        return (distance_x ** 2 + distance_y ** 2) < (radius ** 2)

    def draw_ground(self):
        pygame.draw.rect(screen, GRAY, (0, HEIGHT - 50, WIDTH, 50))

    def draw_trajectory(self):
        for point in self.trajectory_points:
            pygame.draw.circle(screen, YELLOW, point, 3)

    def draw(self):
        screen.fill(WHITE)

        # Draw ground
        self.draw_ground()

        # Draw slingshot
        self.slingshot.draw(screen, self.bird)

        # Draw trajectory prediction
        if self.dragging and self.trajectory_points:
            self.draw_trajectory()

        # Draw bird
        self.bird.draw(screen)

        # Draw pigs
        for pig in self.pigs:
            pig.draw(screen)

        # Draw blocks
        for block in self.blocks:
            block.draw(screen)

        # Draw score
        score_text = self.font.render(f"Score: {self.score}", True, BLACK)
        screen.blit(score_text, (10, 10))

        # Draw instructions
        instructions = [
            "Instructions:",
            "- Click and drag the red bird to aim.",
            "- Release the mouse button to launch.",
            "- Press 'R' to reset the game."
        ]
        for i, line in enumerate(instructions):
            instr_text = self.font.render(line, True, BLACK)
            screen.blit(instr_text, (10, 40 + i * 30))

        pygame.display.flip()

    def run(self):
        while True:
            clock.tick(FPS)
            self.handle_events()
            self.update()
            self.draw()

# Run the game
if __name__ == "__main__":
    game = Game()
    game.run() well, but there should be physics. if there are other blocks at the bottom, it can't fall through them because other blocks are independent physical object, so they don't pass through other blocks, but, for example, if the ball flew to the left, the blocks should move a little to the right without passing through other blocks.