import pygame
import sys
import math
import random

# -------------------------------
# Simulation Parameters
# -------------------------------
WIDTH, HEIGHT = 800, 600
grass_count = 3000  # Thousands of blades for a lush field

# Initial wind parameters
wind_amplitude = 20    # How far things sway (pixels)
wind_speed = 2.0       # Oscillation frequency

# Debug mode flag (toggle with F3)
debug_mode = True

# -------------------------------
# Pygame Setup
# -------------------------------
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT))
pygame.display.set_caption("Optimized Grass, Tree & Collision Simulation")
clock = pygame.time.Clock()
font = pygame.font.SysFont("Arial", 18)

# -------------------------------
# Collision System
# -------------------------------
class GrassCollider:
    """
    Base collider class. Entities that affect grass or leaves should extend this class.
    Collision is defined as a circle. The collider's movement is independent.
    """
    def __init__(self, pos, radius, repulsion_strength=5):
        self.pos = list(pos)  # stored as [x, y] so it can be updated independently
        self.radius = radius
        self.repulsion_strength = repulsion_strength

    def collide_point(self, x, y):
        dx = x - self.pos[0]
        dy = y - self.pos[1]
        return (dx * dx + dy * dy) < (self.radius * self.radius)

# A sample moving entity: a bouncing ball that acts as a collider.
class Ball(GrassCollider):
    def __init__(self, pos, radius, repulsion_strength=30):
        super().__init__(pos, radius, repulsion_strength)
        self.velocity = [3, 2]

    def update(self):
        # Update position and bounce off window edges.
        self.pos[0] += self.velocity[0]
        self.pos[1] += self.velocity[1]
        if self.pos[0] - self.radius < 0 or self.pos[0] + self.radius > WIDTH:
            self.velocity[0] = -self.velocity[0]
        if self.pos[1] - self.radius < 0 or self.pos[1] + self.radius > HEIGHT:
            self.velocity[1] = -self.velocity[1]

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

# -------------------------------
# Grass Blade Class
# -------------------------------
class GrassBlade:
    def __init__(self, x, base_y, length, phase, tilt):
        self.x = x
        self.base_y = base_y
        self.length = length
        self.phase = phase      # For varied motion per blade
        self.tilt = tilt        # Base tilt for a natural look

    def draw(self, surface, t, wind_amp, wind_spd, colliders):
        segments = 10  # Number of segments along the blade
        points = []
        sin_arg = t * wind_spd  # Precompute common sine argument for efficiency
        for i in range(segments + 1):
            progress = i / segments
            # Base (static) position along a slightly tilted vertical line.
            base_x = self.x + progress * self.length * math.sin(self.tilt)
            base_y = self.base_y - progress * self.length * math.cos(self.tilt)
            # Wind displacement increases with height.
            displacement = wind_amp * progress * math.sin(sin_arg + self.phase)
            x_pos = base_x + displacement
            y_pos = base_y
            # For each collider, push the grass blade away if it's too close.
            for collider in colliders:
                if collider.collide_point(x_pos, y_pos):
                    dx = x_pos - collider.pos[0]
                    dy = y_pos - collider.pos[1]
                    dist = math.hypot(dx, dy)
                    if dist > 0:
                        repulsion = collider.repulsion_strength / dist
                        x_pos += repulsion * (dx / dist)
                        y_pos += repulsion * (dy / dist)
            points.append((x_pos, y_pos))
        pygame.draw.aalines(surface, (34, 139, 34), False, points)

# Initialize a list of grass blades.
grass_blades = []
def init_grass():
    for _ in range(grass_count):
        x = random.uniform(0, WIDTH)
        # Grass base near the bottom.
        base_y = HEIGHT - random.uniform(0, 20)
        length = random.uniform(50, 100)
        phase = random.uniform(0, 2 * math.pi)
        tilt = random.uniform(-0.1, 0.1)
        grass_blades.append(GrassBlade(x, base_y, length, phase, tilt))

init_grass()

def draw_grass(surface, t, wind_amp, wind_spd, colliders):
    for blade in grass_blades:
        blade.draw(surface, t, wind_amp, wind_spd, colliders)

# -------------------------------
# Tree and Leaves Drawing
# -------------------------------
# Global leaf counter for debug display.
leaf_count = 0

def draw_leaf(surface, x, y, colliders):
    global leaf_count
    # Adjust leaf position if a collider is nearby.
    for collider in colliders:
        if collider.collide_point(x, y):
            dx = x - collider.pos[0]
            dy = y - collider.pos[1]
            dist = math.hypot(dx, dy)
            if dist > 0:
                repulsion = collider.repulsion_strength / dist
                x += repulsion * (dx / dist)
                y += repulsion * (dy / dist)
    pygame.draw.circle(surface, (50, 205, 50), (int(x), int(y)), 5)
    leaf_count += 1

def draw_tree(surface, x, y, length, angle, level, t, wind_amp, wind_spd, colliders):
    if level == 0 or length < 2:
        return

    # Wind-induced sway for the branch.
    sway = math.radians(wind_amp * 0.3 * math.sin(t * wind_spd + level))
    new_angle = angle + sway

    # Calculate branch endpoint.
    end_x = x + length * math.sin(new_angle)
    end_y = y - length * math.cos(new_angle)
    
    # Apply collision repulsion at the branch endpoint.
    for collider in colliders:
        if collider.collide_point(end_x, end_y):
            dx = end_x - collider.pos[0]
            dy = end_y - collider.pos[1]
            dist = math.hypot(dx, dy)
            if dist > 0:
                repulsion = collider.repulsion_strength / dist
                end_x += repulsion * (dx / dist)
                end_y += repulsion * (dy / dist)
    
    # Draw the branch.
    thickness = max(1, level)
    branch_color = (139, 69, 19) if level > 2 else (34, 139, 34)
    pygame.draw.line(surface, branch_color, (x, y), (end_x, end_y), thickness)
    
    # Draw a leaf at lower recursion levels.
    if level <= 2:
        draw_leaf(surface, end_x, end_y, colliders)

    # Recursively draw left and right branches.
    new_length = length * 0.7
    branch_angle = math.radians(30)
    draw_tree(surface, end_x, end_y, new_length, new_angle - branch_angle, level - 1, t, wind_amp, wind_spd, colliders)
    draw_tree(surface, end_x, end_y, new_length, new_angle + branch_angle, level - 1, t, wind_amp, wind_spd, colliders)

# -------------------------------
# Create Colliders
# -------------------------------
colliders = []
# Add a bouncing ball as a collider.
ball = Ball([400, 300], 20, repulsion_strength=30)
colliders.append(ball)

# -------------------------------
# Main Loop
# -------------------------------
running = True
start_time = pygame.time.get_ticks() / 1000.0

while running:
    t = pygame.time.get_ticks() / 1000.0 - start_time

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            running = False
        elif event.type == pygame.KEYDOWN:
            # Adjust wind parameters.
            if event.key == pygame.K_UP:
                wind_amplitude += 5
            elif event.key == pygame.K_DOWN:
                wind_amplitude = max(0, wind_amplitude - 5)
            elif event.key == pygame.K_RIGHT:
                wind_speed += 0.5
            elif event.key == pygame.K_LEFT:
                wind_speed = max(0.5, wind_speed - 0.5)
            elif event.key == pygame.K_F3:
                debug_mode = not debug_mode  # Toggle debug overlay

    # Update colliders (the collider itself is not pushed by grass/leaves).
    ball.update()

    # Draw background.
    screen.fill((135, 206, 235))
    ground_rect = pygame.Rect(0, int(HEIGHT * 0.8), WIDTH, int(HEIGHT * 0.2))
    pygame.draw.rect(screen, (85, 107, 47), ground_rect)

    # Draw grass blades.
    draw_grass(screen, t, wind_amplitude, wind_speed, colliders)

    # Reset leaf counter and draw the fractal tree.
    leaf_count = 0
    tree_base_x = int(WIDTH * 0.2)
    tree_base_y = int(HEIGHT * 0.8)
    tree_length = 100
    tree_angle = 0  # vertical
    tree_levels = 7
    draw_tree(screen, tree_base_x, tree_base_y, tree_length, tree_angle, tree_levels, t, wind_amplitude, wind_speed, colliders)

    # Draw the collider (ball).
    ball.draw(screen)

    # Debug Menu overlay.
    if debug_mode:
        debug_texts = [
            f"FPS: {clock.get_fps():.1f}",
            f"Grass Blades: {len(grass_blades)}",
            f"Leaves: {leaf_count}",
            f"Colliders: {len(colliders)}",
            f"Wind Amplitude: {wind_amplitude}",
            f"Wind Speed: {wind_speed}"
        ]
        for i, text in enumerate(debug_texts):
            text_surface = font.render(text, True, (0, 0, 0))
            screen.blit(text_surface, (10, 10 + i * 20))

    pygame.display.flip()
    clock.tick(60)

pygame.quit()
sys.exit()
