#main.py, This is the function that should be run to start the simulation and record it

#we need pygame, sys, os, subprocess, and ffmpeg to make the main function work
import pygame
import sys
import os
import subprocess
from ffmpeg import *

#we import the other python files and their functions/global variables
from settings import *
from heroes import Hero
from enemies_normal import Enemy

pygame.init() #this is how to start the pygame window/moduel
ffmpeg_process = None #ffmpeg is like a global variable that needs to be set to none before recording starts

def start_recording(): #this function will start recording
    global ffmpeg_process


    output_file = "hanzhen_final_project_test.mp4" #this should be changed to whatever the file name of the recording will be called

    
    if os.path.exists(output_file): #if the file already exists then we remove the file so error doens't happen.
        os.remove(output_file)

    # I copied these ffmpeg settings and many of the later ffmpeg commands from chatgpt because I was having trouble
    # getting it to work. I think ffmpeg is different for recording pygame vs recording matplotlib
    ffmpeg_cmd = [
        #I had to install ffmpeg and put it in my Final Project Folder. I spent a lot of time trying to get this to work
        #and this was the only way it worked. 
        "C:/Users/elliot/eps 109 assignments/Final Project/ffmpeg-2025-11-24-git-c732564d2e-essentials_build/ffmpeg-2025-11-24-git-c732564d2e-essentials_build/bin/ffmpeg.exe",
        "-y",
        "-f", "rawvideo",
        "-vcodec", "rawvideo",
        "-pix_fmt", "rgb24",
        "-s", f"{WIDTH}x{HEIGHT}",
        "-r", "60",
        "-i", "-",                    
        "-an",
        "-vcodec", "libx264",
        "-pix_fmt", "yuv420p",
        output_file
    ]

    #the code below starts the ffmpeg process and starts recording
    ffmpeg_process = subprocess.Popen(
        ffmpeg_cmd,
        stdin=subprocess.PIPE
    )
    print('ffmpeg recording has started (pls work)') #print statement just to show it worked



def end_game(screen, font, message, color): #this function is to end the game
    global ffmpeg_process #uses the global ffmpeg process variable

    screen.fill((0,0,0)) #turns the screen black
    text = font.render(message, True, color) #font is an input variable passed into the function. 
    #this code basically creates a pygame surface object containing a text message in the color inputted. True is for smoothing
    # .render is a function for font pygame.font objects

    screen.blit(text, (WIDTH//2 - text.get_width()//2, HEIGHT//2))
    #a pygame surface object called screen was passed in, we call its function to draw another surface (the text object)
    # onto itself at the position provided, (x, y) is the top left corner of the display messagem
    # I was too lazy to adjust y-coordinate to the height of the text so I just used height//2

    pygame.display.flip() #this will actually display the frame


    #the code below is to write the final game over message and make sure it lasts for at least 2 seconds

    if ffmpeg_process and ffmpeg_process.stdin: #this is prob not needed but just makes sure the ffmpeg is still running
        final_frame = pygame.surfarray.array3d(screen) #this converts are screen into a 3d array. 
        # each column of the array is like an x-value and each row of the array is a y-value. Then each [row][column]
        #combination has a z-feature that contains the color of the point.

        final_frame = final_frame.swapaxes(0, 1) #ffmpeg uses (y, x) format so we swap the axes of the 3d array
        for i in range(120): #so i can record the game over screen for like around 2 seconds (assuming fps is around 60)
            ffmpeg_process.stdin.write(final_frame.tobytes()) #each of these 120 frames we're writing the frame into ffmpeg to record it




    if ffmpeg_process is not None: #safety if statement to make sure ffmpeg actually started recording, prob not needed


        
        ffmpeg_process.stdin.close() #closes the stdin pipe which means ffmpeg stops letting new frames be written
        ffmpeg_process.wait() #waits to see if any more frames are coming in late before the process is terminated
        ffmpeg_process = None #completely close the recording by setting this variable to None
        print("The recording has finished") #just printing this


    pygame.quit() #quits pygame program
    sys.exit() #stops the entire python file from running, easier than returning a value and then breaking out of the while loop.
    #this just kills the entire program


screen = pygame.display.set_mode((WIDTH, HEIGHT)) #this is part of the global frame, we actually defined screen
# as a pygame surface object with width = width from settings and height = height from settings



start_recording() #we call the function we made to start recording

left_safezone = pygame.Rect(0, 0, SAFEZONE_WIDTH, HEIGHT) #I define the left safezone here. I probably could have done
# it in settings but I wanted settings to only have integer variables. This creates the left safezone that is 
#a pygame rectangle object with upper left corner at upper left of screen (0, 0), has a width that goes from 0 to safezone_width
#, and height that is just the height of the screen

right_safezone = pygame.Rect(WIDTH - SAFEZONE_WIDTH, 0, SAFEZONE_WIDTH, HEIGHT)
#same thing but creates the right endzone that is another pygame rectangle object. the upper left x position is now
#width of screen minus safezone width

#Here we create a top wall that is also a rectangle, upper left corner is (0, 0), we use screen width as width
#and height is wall thickness
top_wall = pygame.Rect(0, 0, WIDTH, WALL_THICKNESS)
#Here is the bottom wall that has upper left corner at (0, height - wall_thickness), and width = screen width and
# height is wall thickness
bottom_wall = pygame.Rect(0, HEIGHT - WALL_THICKNESS, WIDTH, WALL_THICKNESS)

#this pygame clock object is needed for dt, which is the variable that keeps track of the real time.
clock = pygame.time.Clock()

#empty list of heroes
heroes = []

#Create NUM_HEROS number of heros using a for loop. append each created Hero object into the list, pretty simple
for i in range(NUM_HEROS):
    heroes.append(Hero((0, 255, 0)))


#empty list of enemies
enemies = []

#same for loop to create NUM_ENEMIES number of enemies using a for loop, then append each created Enemy into list
for i in range(NUN_ENEMIES):
    enemies.append(Enemy())

#create pygame font object with font consolas size 32
font = pygame.font.SysFont("consolas", 32)

#initialized a timer that will appear in the simulation, it starts at 0
game_timer = 0

#this variable sets the game state as running, we will turn it to GAME_OVER when one of the game over conditions are met
game_state = RUNNING

while True: #while statement that runs forever, but it's fine because we will be checking when the game is over
    dt = clock.tick(FPS) / 1000  # This creates dt, which is the number of seconds that passed since last frame
    #we use dt to keep track of the real time because fps is not constant. divide by 1000 cuz original input is in milliseconds.
    game_timer += dt # the game timer increases by dt

    #this block here basically would quit the pygame process if the user or the computer did something to quit it
    for event in pygame.event.get(): # this creates an event iterator that goes through a list of all event
        #objects that occured in this iteration of the while loop. 
        if event.type == pygame.QUIT: #we check if any of the events = pygame.quit, which means the user (me) closed the pygame window
            pygame.quit() #if so, pygame quits
            sys.exit() #we quit the entire program

    for h in heroes: # for loop that loops through every hero
        if h.alive and right_safezone.collidepoint(h.pos): #check if each hero is alive and has reached the right safezone
            #collidepoint function checks if the right safepoint rectangle object includes the position point of the hero
            #essentialy is the center of the hero contained in the right safezone?
            end_game(screen, font, "GAME OVER, AT LEAST ONE HERO HAS REACHED THE SAFEZONE", (0,255,0)) #if yes,
            # the game is over with at least one hero reaching the right safezone.
            # we call our end game function and pass in the inputs of screen (pygame rect object), font (pygame font object)
            # game over message in string, and the color of the message which I made green for victory.

    
    if all(h.downed for h in heroes): #this statement checks if all heroes are downed
        # we don't need to check for dead heroes because they are removed from the list once they fully die
        end_game(screen, font, "GAME OVER, EVASION FAILED", (255,0,0)) #the moment all heroes are downed, there is
        # no chance of winning which means the evasion has failed. We call our end_game() function again
        #, this time using the failed message and a red color. 




    #uses a for loop through all the heroes to update the positions of each
    for h in heroes:
        h.update(dt)

    #uses a for loop to loop through all the enemies to update the positions of each
    for e in enemies:
        e.update(dt)

    # now we loop through all heroes again to check for eney collisions
    for h in heroes: #loops through each hero, each one is called h
        if h.alive: #if hero is alive
            
            
            for e in enemies: #loop through every enemy
                if h.pos.distance_to(e.pos) < h.radius + e.radius: #check if the distance between the center of h
                    # and the center of each enemy is less than their radius combined.
                    # this essentially just checks if the hero got hit by the enemy
                    h.take_hit() #we call the take_hit function in the heroes.py which will down the hero

    #loop through all heroes to call the tick_downed function in heroes.py, which decreases down timer for downed
    #heroes
    for h in heroes:
        h.tick_downed(dt)

    # loops through every hero to call the try_revive function which will see if any of them can be revived.
    for h in heroes:
        h.try_revive(heroes)

    #remove heroes that fully died by looping through all heroes and checking if any are fully dead
    for h in heroes[:]:
        if h.alive == False:
            heroes.remove(h)


    
    screen.fill((211, 211, 211)) #this fills the entire screen with a light gray color that is my background

    pygame.draw.rect(screen, SAFEZONE_COLOR, left_safezone) #this actually draws the left safezone on top of the screen
    pygame.draw.rect(screen, SAFEZONE_COLOR, right_safezone)#this actually draws the right safezone on top of the screen

    pygame.draw.rect(screen, WALL_COLOR, top_wall) #this actually draws the top wall on top of the screen
    pygame.draw.rect(screen, WALL_COLOR, bottom_wall)#this actually draws the bottom wall on top of the screen

    #here we loop through all heroes again to draw them using the function we made
    for h in heroes:
        h.draw(screen)

    #here we loop through all enemies again to draw them using the function we made
    for e in enemies:
        e.draw(screen)

    # these 2 lines here draws the game timer, which shows the text Time: (number), has smoothing, and color white
    # first a surface object was made from font.render, then we used screen.blit to draw this new surface object
    # called timer_text on top of screen with an upper left corner at the appropriate spot
    timer_text = font.render(f"Time: {int(game_timer)}", True, (255, 255, 255))
    screen.blit(timer_text, (WIDTH//2 - timer_text.get_width()//2, 10))

    # the frame gets captured by ffmpeg in the block below
    if ffmpeg_process and ffmpeg_process.stdin: #safety if statement to check recording is active
        frame = pygame.surfarray.array3d(screen) #this converts are screen into a 3d array. 
        # each column of the array is like an x-value and each row of the array is a y-value. Then each [row][column]
        #combination has a z-feature that contains the color of the point.
        frame = frame.swapaxes(0, 1) #we swap axes because ffmpeg uses (y, x) coordinates instead of (x, y)
        ffmpeg_process.stdin.write(frame.tobytes()) #this command records the frame after the conversions to bytes


    pygame.display.flip() #updates the pygame window

    #yay!

