import random
import pygame
import pymunk
import numpy as np
import cv2_utils
import pyo_utils

#############################################
#   Andrew Chao - andrewchao7000@gmail.com  #
#   6/1/2026                                #
#############################################

#classes
class Ball:
    def __init__(self, color, radius, inital_position, space):
        self.color = color
        self.radius = radius

        #body
        density = 0.01
        mass = (np.pi * radius**2) * density
        moment = pymunk.moment_for_circle(mass, 0, radius)
        self.body = pymunk.Body(mass, moment)
        self.body.position = inital_position

        #poly
        self.poly = pymunk.Circle(self.body, radius)
        self.poly.elasticity = 0.7
        self.poly.friction = 0.5

        #collision
        self.poly.collision_type = 1 #COLLISION NUM 1

        #add to space
        space.add(self.body, self.poly)

        #glow
        self.glow_radius = self.radius * 2.5
        self.glow_surf = pygame.Surface((self.glow_radius * 2, self.glow_radius * 2))

        for i in range(int(self.glow_radius), 0, -1):
            ratio = i / self.glow_radius
            intensity = (1.0 - ratio) ** 2

            r = int(self.color[0] * intensity)
            g = int(self.color[1] * intensity)
            b = int(self.color[2] * intensity)

            pygame.draw.circle(self.glow_surf, (r, g, b), (self.glow_radius, self.glow_radius), i)
        self.glow_surf.set_colorkey((0, 0, 0))

    def draw(self, screen):
        pygame.draw.circle(screen, self.color, self.body.position, self.radius)

    def draw_glow(self, screen, size, intensity):
        for i in range(1, intensity):
            self.__draw_circle_alpha(screen, self.color, self.body.position, 25 + (i * size), 10)

    def remove_from_space(self, space):
        space.remove(self.body, self.poly)

    def __draw_circle_alpha(self, surface, color, center, radius, alpha=255, width=0):
        r, g, b = color[:3]

        diameter = radius * 2
        temp_surface = pygame.Surface((diameter, diameter), pygame.SRCALPHA)

        pygame.draw.circle(temp_surface, (r, g, b, alpha), (radius, radius), radius, width)

        target_x = center[0] - radius
        target_y = center[1] - radius
        surface.blit(temp_surface, (target_x, target_y))
class Platform:
    def __init__(self, color, poly_points, space):
        self.color = color

        #body
        self.body = pymunk.Body(body_type=pymunk.Body.STATIC)
        self.body.position = (0,0)

        #poly
        poly_points = poly_points.reshape(-1, 2).tolist() #reshape to right format
        self.poly = pymunk.Poly(self.body, poly_points)

        self.poly.elasticity = 1.0  # 1.0 means it absorbs no energy (hard surface)
        self.poly.friction = 0.5  # Gives the surface some grip

        # collision
        self.poly.collision_type = 2  # COLLISION NUM 2

        #add to space
        space.add(self.body, self.poly)

    def draw(self, screen):
        vertices = self.poly.get_vertices()

        point_list = []
        for v in vertices:
            point = self.body.local_to_world(v)
            point_list.append((int(point.x), int(point.y)))

        pygame.draw.polygon(screen, self.color, point_list)

    def remove_from_space(self, space):
        space.remove(self.body, self.poly)

#audio
def play_ball_on_platform_hit_sound(object_data, space, data):
    hit_force = object_data.total_impulse.length

    if hit_force > 500:
        volume = min(hit_force / 30000.0, 0.3)

        if volume < 0.01:
            return True

        ball_shape, platform_shape = object_data.shapes
        pyo_utils.play_bounce_sound(volume)

    return True
def play_ball_on_ball_hit_sound(object_data, space, data):
    hit_force = object_data.total_impulse.length

    if hit_force > 500:
        volume = min(hit_force / 1000.0, 1.0)
        if volume < 0.05:
            return
        ball_shape, platform_shape = object_data.shapes
        # TODO MAKE NEW SOUND if u want:)



    return True

#game
def main_game_window(pygame_width, pygame_height, platform_update_frequency):
    video_capture_device_number = cv2_utils.get_video_capture_number()

    # pygame init
    Running = True
    pygame.init()
    screen = pygame.display.set_mode((pygame_width, pygame_height))
    clock = pygame.time.Clock()

    # pymunk init
    space = pymunk.Space()
    space.gravity = 0, 1962

    # pymunk collision checker
    COLLISION_TYPE_BALL = 1
    COLLISION_TYPE_PLATFORM = 2


    space.on_collision(COLLISION_TYPE_BALL, COLLISION_TYPE_PLATFORM, post_solve=play_ball_on_platform_hit_sound)
    space.on_collision(COLLISION_TYPE_BALL, COLLISION_TYPE_BALL, post_solve=play_ball_on_platform_hit_sound)

    # general lists
    ball_list = []
    platform_list = []

    ball_kill_list = []

    # button options
    draw_platform = False

    #color options
    platform_color = (25,25,25)
    previosu_colors = [
        (239, 71, 111),
        (247, 140, 107),
        (255, 209, 102),
        (6, 214, 160),
        (17, 138, 178),
        (7, 59, 76)]

    neon_ball_colors = [
        (255, 42, 109),
        (255, 126, 39),
        (254, 213, 51),
        (5, 213, 175),
        (1, 190, 254),
        (143, 0, 255)
    ]

    #trackers
    tracker = 0

    #update speed
    update_every_x_frame = platform_update_frequency
    frame_count = 0

    #inital user calibration
    lower_bound = np.array([0, 0, 0])
    upper_bound = np.array([179, 255, 255])

    cv2_utils.calibrate_color(video_capture_device_number, lower_bound, upper_bound)
    calibration_points = cv2_utils.find_calibration_points(video_capture_device_number)
    homography_matrix = cv2_utils.calculate_homography_matrix(calibration_points, pygame_width, pygame_height)

    #create capture
    capture = cv2_utils.get_capture(video_capture_device_number)


    # loop
    while Running:

        #user event checker
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                Running = False

            if event.type == pygame.KEYDOWN:

                # 'q' to quit
                if event.key == pygame.K_q:
                    Running = False


                # 'c' to open color calibration
                if event.key == pygame.K_c:

                    capture.release()
                    cv2_utils.calibrate_color(lower_bound, upper_bound)
                    capture = cv2_utils.get_capture(video_capture_device_number)

                # 'n' to start/stop drawing platforms
                if event.key == pygame.K_n:
                    draw_platform = not draw_platform

        # take a physics step
        space.step(1.0 / 60)

        # fill screen
        screen.fill("white")

        if frame_count > update_every_x_frame:
            # get platforms corners from webcam
            platform_polygon_list = cv2_utils.capture_webcam_frame_and_get_platform_polygons(capture, homography_matrix, lower_bound, upper_bound, pygame_width, pygame_height)

            #delete platforms
            for platform in platform_list:
                platform.remove_from_space(space)

            platform_list.clear()

        #check for dead balls
        for ball in ball_list:
            if ball.body.position.y > pygame_height + ball.radius + 100:
                ball_kill_list.append(ball)

        #delete balls
        for ball in ball_kill_list:
            ball.remove_from_space(space)
            ball_list.remove(ball)

        ball_kill_list.clear()

        # add platforms to empty platform list
        if frame_count > update_every_x_frame:
            for platform_polygon in platform_polygon_list:
                platform_list.append(Platform(platform_color, platform_polygon, space))

        # add balls to balls list
        if tracker > 2:
            tracker = 0

            #stream
            #ball_list.append(Ball(neon_ball_colors[random.randint(0,5)], 15, (pygame_width/2, 30), space))
            #rain
            spawn_x = random.randint(15, pygame_width - 15)
            spawn_y = random.randint(-20, 30)
            ball_list.append(Ball(neon_ball_colors[random.randint(0, 5)], 15, (spawn_x, spawn_y), space))


        #draw platforms
        if draw_platform:
            for platform in platform_list:
                platform.draw(screen)

        #draw balls
        for ball in ball_list:
            ball.draw(screen)
            ball.draw_glow(screen,12,3)

        # display work onto screen
        pygame.display.flip()

        # fps cap
        clock.tick(30)

        #update tracker
        tracker+=1
        if frame_count > update_every_x_frame:
            frame_count = 0

        frame_count += 1

    pyo_utils.server.stop()


#test stuff ignrore
def test():
    #pygame init
    Running = True
    pygame.init()
    screen = pygame.display.set_mode((800,800))
    clock = pygame.time.Clock()

    #pymunk init
    space = pymunk.Space()
    space.gravity = 0,5000

    #general init
    balls = []

    #loop
    while Running:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                Running = False

        #take a physics step
        space.step(1.0/60)

        #fill screen
        screen.fill("black")

        #get mouse position
        mouse_pos = pygame.mouse.get_pos()
        balls.append(Ball((0,255,0),15,mouse_pos,space))
        for ball in balls:
            ball.draw(screen)


        #misc


        #display work onto screen
        pygame.display.flip()

        #fps cap
        clock.tick(60)
