import pygame
import pygame.gfxdraw
from PIL import Image
import random
from math import cos, sin
#import time

##### CONFIG #####
window_size = (1280, 720)

fps = 60

# pixel size in percentage of window height (0.0 - 1.0)
pixel_size = 0.015

# min and max distance from screen edge to pixel in window height percentage (0.0 - 1.0)
min_distribution_edge_dist = 0.1
max_distribution_edge_dist = 0.2

initial_vel_randomness = 1

# max random delay of pixel simulation start in seconds
max_random_delay = 2


## simulation settings
# 0.0 - inf
stiffness = 40

# 0.0 - 1.0
damping = 0.08


audio = True

##################

damping_multiplier = 1 - damping

delta_time = 1 / fps

half_window_size = (window_size[0] / 2, window_size[1] / 2)

print("=====================")

aspect_ratio = window_size[0] / window_size[1]
half_aspect_ratio = aspect_ratio / 2
print(f"Aspect ratio: {aspect_ratio}")

pixel_size_px = pixel_size * window_size[1]
print(f"Pixel size: {pixel_size_px}px")


# import logo image
logo = Image.open("logo_new.png").convert("1")


logo_size = (logo.size[0] * pixel_size, logo.size[1] * pixel_size)
print(f"Logo size: {logo_size}")

# offset in world space so the logo is in the center
logo_offset = (logo_size[0] / 2, logo_size[1] / 2)
print(f"Logo offset: {logo_offset}")


print("=====================")




pixels_target_pos = []

for y in range(logo.size[1]):
    for x in range(logo.size[0]):
        if logo.getpixel((x, y)) == 255:
            pixels_target_pos.append((x * pixel_size - logo_offset[0], y * pixel_size - logo_offset[1]))

pixels_target_pos = tuple(pixels_target_pos)


## distribute pixels in random positions off the screen

pixels_pos_and_vel = []

for pixel in pixels_target_pos:
    # select random screen edge axis using 1 and aspect_ratio as weights | 0 = x, 1 = y
    edge_axis = random.choices((0, 1), weights=[1, aspect_ratio])[0]

    # select random edge | (x axis): 0 = left, 1 = right | (y axis): 0 = top, 1 = bottom
    edge = random.choice((-1, 1))

    # random position on edge
    if edge_axis == 0: # x axis (vertical edges)
        pos = [half_aspect_ratio * edge, random.uniform(-.5, .5)]
    else: # y axis (horizontal edges)
        pos = [random.uniform(-half_aspect_ratio, half_aspect_ratio), edge / 2]

    # offset pos by random 0 to distribution_edge_dist_px
    pos[edge_axis] += random.uniform(min_distribution_edge_dist, max_distribution_edge_dist) * edge


    # pos, velocity, delay
    pixels_pos_and_vel.append([
        pos, 
        [random.uniform(-initial_vel_randomness, initial_vel_randomness), random.uniform(-initial_vel_randomness, initial_vel_randomness)],
        random.uniform(0, max_random_delay) + 1
    ])




# pygame init
pygame.init()
window = pygame.display.set_mode(window_size)

clock = pygame.time.Clock()

# font
font = pygame.font.SysFont("Arial", 20)


if audio:
    intro_sound = pygame.mixer.Sound("prototype_sound.wav")


############# another config stuff ##############

rot_zoom_delay = 6
rot_speed_increase = 0.2
zoom_speed_increase = 2


zoom_speed = 0.04

fade_delay = 1.6
fade_speed = 255*4


zoom = 1
fade_val = 255

################################################

rot_speed = 0

angle = 0

can_rot = False


#time_start = time.perf_counter() + 1


# main loop
while True:
    # events
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            pygame.quit()
            quit()


    window.fill((0, 0, 0))

    if fade_val > 0:

        if rot_zoom_delay > 0:
            rot_zoom_delay -= delta_time
        else: # zooming more and rotating
            rot_speed += rot_speed_increase * delta_time
            zoom_speed += zoom_speed_increase * delta_time

            angle += rot_speed * delta_time

            # rotate and zoom
            rot_cos = cos(angle)
            rot_sin = sin(angle)

            if not can_rot:
                can_rot = True

            # fade
            if fade_delay > 0:
                fade_delay -= delta_time
            else:
                fade_val -= fade_speed * delta_time
                if fade_val < 0:
                    continue

        zoom *= 1 + zoom_speed * delta_time

        # calculate pixel points positions
        scaled_pixel_size_px = pixel_size_px * zoom
        half_scaled_pixel_size_px = round(scaled_pixel_size_px / 2)
        scaled_pixel_size_px = round(scaled_pixel_size_px)

        pixel_points = (
            (0, 0),
            (scaled_pixel_size_px, 0),
            (scaled_pixel_size_px, scaled_pixel_size_px),
            (0, scaled_pixel_size_px)
        )

        pixel_points = tuple(
            (
                pixel_point[0] - half_scaled_pixel_size_px,
                pixel_point[1] - half_scaled_pixel_size_px
            )
            for pixel_point in pixel_points
        )

        # rotate pixel points
        if can_rot:
            pixel_points = tuple(
                (
                    pixel_point[0] * rot_cos - pixel_point[1] * rot_sin,
                    pixel_point[0] * rot_sin + pixel_point[1] * rot_cos
                )
                for pixel_point in pixel_points
            )


        # draw pixels
        for pixel_idx, pixel in enumerate(pixels_pos_and_vel):
            # check if delay is over
            if pixel[2] > 0:
                pixel[2] -= delta_time
                continue

            if audio:
                pygame.mixer.Sound.play(intro_sound)
                audio = False

            ## simulation (take delta_time into account)

            # get pixel pos and vel
            pos = pixel[0]
            vel = pixel[1]

            # delta pos (target pos - current pos)
            delta_pos = [pixels_target_pos[pixel_idx][0] - pos[0], pixels_target_pos[pixel_idx][1] - pos[1]]

            # update velocity
            vel[0] += delta_pos[0] * stiffness * delta_time
            vel[1] += delta_pos[1] * stiffness * delta_time

            # damping
            vel[0] *= damping_multiplier
            vel[1] *= damping_multiplier

            # update pos
            pos[0] += vel[0] * delta_time
            pos[1] += vel[1] * delta_time

            # update pixel pos and vel
            pixels_pos_and_vel[pixel_idx][0:2] = [pos, vel]


            ## draw pixel

            # rotate
            if can_rot:
                pixel_draw_pos = (
                    pos[0] * rot_cos - pos[1] * rot_sin,
                    pos[0] * rot_sin + pos[1] * rot_cos
                )
            else:
                pixel_draw_pos = pos

            # zoom
            perceived_zoom = zoom
            pixel_draw_pos = (pixel_draw_pos[0] * perceived_zoom, pixel_draw_pos[1] * perceived_zoom)

            # convert to screen space
            pixel_draw_pos = (
                pixel_draw_pos[0] * window_size[1] + half_window_size[0],
                pixel_draw_pos[1] * window_size[1] + half_window_size[1]
            )


            # draw pixel
            points = tuple(
                (round(pixel_draw_pos[0] + pixel_point[0]),
                round(pixel_draw_pos[1] + pixel_point[1])) for pixel_point in pixel_points
            )

            pygame.gfxdraw.aapolygon(
                window, 
                points,
                (fade_val,)*3
            )

            pygame.gfxdraw.filled_polygon(
                window,
                points,
                (fade_val,)*3
            )

            # DEBUG draw sphere
            #pygame.draw.circle(window, (255, 255, 255), (round(pixel_draw_pos[0]), round(pixel_draw_pos[1])), 200, 5)

            # DEBUG draw dot
            #pygame.draw.circle(window, (255, 0, 0), (round(pixel_draw_pos[0]), round(pixel_draw_pos[1])), 1)

    #time_now = time.perf_counter()
    #time_delta = time_now - time_start
#
    ## draw time
    #text = font.render(f"Time: {time_delta:.2f}", True, (255, 255, 255))
    #window.blit(text, (10, 10))


    # update
    pygame.display.update()
    delta_time = clock.tick(60) / 1000