
import turtle
import random
import time
import os
import sys

# =========================
# Parâmetros / Constantes
# =========================
LARGURA, ALTURA = 600, 900
BORDA_X = (LARGURA // 2) - 20
BORDA_Y = (ALTURA // 2) - 10

PLAYER_SPEED = 20
PLAYER_BULLET_SPEED = 16
BULLET_COOLDOWN = 0.1    # Cooldown entre tiros (em segundos)
LAST_SHOT_TIME = [0]     # Guarda o momento do último tiro

ENEMY_ROWS = 3
ENEMY_COLS = 10
ENEMY_SIZE = 32
ENEMY_SPACING_X = 18 + ENEMY_SIZE
ENEMY_SPACING_Y = 60
ENEMY_START_Y = BORDA_Y - ENEMY_SIZE    # Topo visível
ENEMY_START_X = - BORDA_X
ENEMY_FALL_SPEED = 0.5
ENEMY_DRIFT_STEP = 2
ENEMY_FIRE_PROB = 0.006
ENEMY_BULLET_SPEED = 8
ENEMY_INVERT_CHANCE = 0.05
ENEMY_DRIFT_CHANCE = 0.5

PLAYER_BULLETS = []
ENEMY_BULLETS = []
COUNTER = 0
COLLISION_RADIUS = 10
HIGHSCORES_FILE = "highscores.txt"
SAVE_FILE = "savegame.txt"
TOP_N = 10

STATE = None  # Usado apenas para callbacks do teclado

# =========================
# Top Resultados (Highscores)
# =========================

def ler_highscores(filename):
    top_list = []
    if os.path.exists(filename):
        with open(filename, 'r') as f:
            flines = f.readlines()
            if len(flines) != 0:
                for linha in flines:
                    elementos = linha.strip().split(":")
                    score = elementos[0]
                    jogador = elementos[1]
                    top_list.append((int(score), jogador))
        return top_list
    return 0

def atualizar_highscores(filename, score):
    top = ler_highscores(filename)
    def addscore(mode = "insert"):
        jogador = input("Gravar score no top? Se sim escreva nome de jogador, senão carregue Return: ")
        if jogador.isalpha() == False:
            return
        if mode == "insert":
            index = 0
            for i in range(len(top)):
                if int(top[i][0]) >= score:
                    index = i+1
            top.insert(index, (score, jogador))
        elif mode == "append":
            top.append((score, jogador))
        with open(filename, 'w') as f:
            for i in top:
                f.write(f"{i[0]}:{i[1]}\n")
        f.close()
        print("Registo do score bem sucedido")
    if top != 0:
        if len(top)<(TOP_N-1):
            addscore()
        elif score>top[-1][0]:
            del top[-1]
            addscore()
    else:
        top = []
        addscore("append")
        return

# =========================
# Guardar / Carregar estado (texto)
# =========================

def guardar_estado_txt(filename):
    with open(filename, 'w') as salvar:
        pontos = state["score"]
        salvar.write(f"SCORE {pontos}\n")
        p = state["player"]
        px = p.xcor()
        py = p.ycor()
        salvar.write(f"PLAYER {px} {py}\n")
        for pb in state["player_bullets"]:
            pbx = pb.xcor()
            pby = pb.ycor()
            salvar.write(f"PLAYER_BULLET {pbx} {pby}\n")
        for e in state["enemies"]:
            ex = e.xcor()
            ey = e.ycor()
            salvar.write(f"ENEMY {ex} {ey}\n")
        for eb in state["enemy_bullets"]:
            ebx = eb.xcor()
            eby = eb.ycor()
            salvar.write(f"ENEMY_BULLET {ebx} {eby}\n")
        for em in state["enemy_moves"]:
            salvar.write(f"ENEMY_MOVE {em}\n")
    salvar.close()

def carregar_estado_txt(filename):
    if verificar_nome_ficheiro(filename) != 0:
        filename += ".txt"
        if os.path.exists(filename):    
            carregado = {
                "player": None,
                "enemies": [],
                "enemy_moves": [],
                "player_bullets": [],
                "enemy_bullets": [],
                "score": 0,
            }
            with open(filename, "r") as salvo:
                for linha in salvo:
                    elementos = linha.strip().split()
                    tipo = elementos[0]
                    if tipo == "PLAYER":
                        px = float(elementos[1])
                        py = float(elementos[2])
                        carregado["player"] = ("p", px, py)
                    elif tipo == "PLAYER_BULLET":
                        bx = float(elementos[1])
                        by = float(elementos[2])
                        carregado["player_bullets"].append("pb")
                        carregado["player_bullets"].append(bx)
                        carregado["player_bullets"].append(by)
                    elif tipo == "ENEMY":
                        ex = float(elementos[1])
                        ey = float(elementos[2])
                        carregado["enemies"].append("e")
                        carregado["enemies"].append(ex)
                        carregado["enemies"].append(ey)
                    elif tipo == "ENEMY_BULLET":
                        ebx = float(elementos[1])
                        eby = float(elementos[2])
                        carregado["enemy_bullets"].append("eb")
                        carregado["enemy_bullets"].append(ebx)
                        carregado["enemy_bullets"].append(eby)
                    elif tipo == "ENEMY_MOVE":
                        em = float(elementos[1])
                        carregado["enemy_moves"].append("em")
                        carregado["enemy_moves"].append(em)
                    elif tipo == "SCORE":
                        carregado["score"] = int(elementos[1])
            salvo.close()
            return carregado
    else:
        return None

# =========================
# Criação de entidades (jogador, inimigo e balas)
# =========================

def criar_entidade(state, x, y, tipo="enemy"):
    if tipo == "player":
        entidade = turtle.Turtle(visible=False, shape="player.gif")
        entidade.up()
        entidade.seth(90)
    else:
        entidade = turtle.Turtle(visible=False, shape="enemy.gif")
        entidade.up()
        entidade.seth(270)
    entidade.setpos(x,y)
    entidade.showturtle()
    return entidade

def criar_bala(x, y, tipo):
    if state["game"] == "stop":
        return
    if tipo=="player":
        bala = turtle.Turtle(visible=False, shape="player_bullet.gif")
        bala.up()
        bala.seth(90)
    elif tipo=="enemy":
        bala = turtle.Turtle(visible=False, shape="enemy_bullet.gif")
        bala.up()
        bala.seth(270)
    bala.setpos(x,y)
    bala.showturtle()
    return bala

def spawn_inimigos_em_grelha(state, carregado=None):
    if carregado==None:
        for r in range(ENEMY_ROWS):
            for c in range(ENEMY_COLS):
                ex = ENEMY_START_X+(ENEMY_SPACING_X*(c+1))
                ey = (ENEMY_START_Y)-((ENEMY_SPACING_Y)*(r+1))
                inimigo = criar_entidade(state, ex, ey, "enemy")
                state["enemies"].append(inimigo)
                state["enemy_moves"].append(ENEMY_DRIFT_STEP)
    else:
        count = 0
        for n in range(len(carregado["enemy_moves"])):
            if carregado["enemy_moves"][n] == "em":
                movimento = carregado["enemy_moves"][n+1]
                state["enemy_moves"].append(movimento)
        for i in range(len(carregado["enemies"])):
            if carregado["enemies"][i] == "e":
                ex = carregado["enemies"][i+1]
                ey = carregado["enemies"][i+2]
                inimigo = criar_entidade(state, ex, ey, "enemy")
                state["enemies"].append(inimigo)
                count += 1

def restaurar_balas(state, carregado=None):
    if carregado!=None:
        for i in range(len(carregado["player_bullets"])):
            if carregado["player_bullets"][i] == "pb":
                pbx = carregado["player_bullets"][i + 1]
                pby = carregado["player_bullets"][i + 2]
                state["player_bullets"].append(criar_bala(pbx, pby, "player"))
        for k in range(len(carregado["enemy_bullets"])):
            if carregado["enemy_bullets"][k] == "eb":
                ebx = carregado["enemy_bullets"][k + 1]
                eby = carregado["enemy_bullets"][k + 2]
                state["enemy_bullets"].append(criar_bala(ebx, eby, "enemy"))
    elif carregado==None:
        return

# =========================
# Handlers de tecla 
# =========================

def mover_esquerda_handler():
    if state["game"] == "stop":
        return
    p = STATE["player"]
    px = p.xcor()
    ps = PLAYER_SPEED
    if px - ps >= -BORDA_X:
        p.setx(px - ps)
    elif px - ps < -BORDA_X:
        p.setx(-BORDA_X)

def mover_direita_handler():
    if state["game"] == "stop":
        return
    p = STATE["player"]
    px = p.xcor()
    ps = PLAYER_SPEED
    if px + ps <= BORDA_X:
        p.setx(px + ps)
    elif px - ps > -BORDA_X:
        p.setx(BORDA_X)

def time_since_last_bullet():
    thisTime=time.time_ns()/(10**9)
    return (thisTime-LAST_SHOT_TIME[0])

def disparar_handler():
    if state["game"] == "stop":
        return
    if time_since_last_bullet() >= BULLET_COOLDOWN:
        p = STATE["player"]
        px = p.xcor()
        py = p.ycor() + 10
        bala = criar_bala(px, py, "player")
        STATE["player_bullets"].append(bala)
        LAST_SHOT_TIME[0] = time.time_ns()/(10**9)

def gravar_handler():
    if state["game"] == "stop":
        return
    while True:
        nome = input("Guardar jogo? Se sim, escreva nome do ficheiro, senão carregue Return: ")
        if nome == "":
            return
        elif verificar_nome_ficheiro(nome) == 0:
            print("Escreva nome sem, espaços ou caracteres especiais: ")
            continue
        elif verificar_nome_ficheiro(nome) == 1:
            nome = nome + ".txt"
            break
    guardar_estado_txt(nome)

def terminar_handler(tipo="derrota"):
    def prepara():
        turtle.clearscreen()
        screen.bgcolor("black")
        turtle.pencolor("white")
    def escreve(x, y, title, fontsize):
        turtle.hideturtle()
        turtle.pu()
        turtle.goto(x, y)
        turtle.pd()
        turtle.write(title, align ="center", font=("Monospace", fontsize, "bold"))
    state["game"] = "stop"
    score = state["score"]
    if tipo=="derrota":
        print("GAME OVER - Derrota!")
        prepara()
        escreve(0, 100, "GAME OVER", 60)
        escreve(0, -50, "Score", 30)
        escreve(0, -100, score, 20)
        atualizar_highscores(HIGHSCORES_FILE, score)
        # turtle.exitonclick()
    elif tipo=="vitoria":
        print("VITÓRIA - Todos os inimigos destruídos!")
        prepara()
        escreve(0, 100, "VITÓRIA", 60)
        escreve(0, -50, "Score", 30)
        escreve(0, -100, score, 20)
        atualizar_highscores(HIGHSCORES_FILE, score)
        # turtle.exitonclick()
    else:
        print("Saiu do jogo (Escape pressionado)")
        prepara()
        escreve(0, 100, "GAME OVER", 60)
        escreve(0, -50, "Score", 30)
        escreve(0, -100, score, 20)
        atualizar_highscores(HIGHSCORES_FILE, score)
        # turtle.exitonclick()
    

# =========================
# Atualizações e colisões
# =========================

def atualizar_balas_player(state):
    if state["game"] == "stop":
        return
    listabalas = state["player_bullets"]
    for pb in listabalas:
        pby = pb.ycor()
        if pby != BORDA_Y:
            pb.fd(PLAYER_BULLET_SPEED)
        else:
            pb.hideturtle()
            state["player_bullets"].remove(pb)
        
def atualizar_balas_inimigos(state):
    if state["game"] == "stop":
        return
    listabalas = state["enemy_bullets"]
    for eb in listabalas:
        eby = eb.ycor()
        if eby != -BORDA_Y:
            eb.fd(ENEMY_BULLET_SPEED)
        else:
            eb.hideturtle()
            state["enemy_bullets"].remove(eb)

def atualizar_inimigos(state):
    if state["game"] == "stop":
        return
    for e in range(len(state["enemies"])):
        enemy = state["enemies"][e]
        dx = state["enemy_moves"][e]
        if random.random() < ENEMY_DRIFT_CHANCE:
            enemy.setx(enemy.xcor() + dx)
        enemy.sety(enemy.ycor() - ENEMY_FALL_SPEED)
        if enemy.xcor() > BORDA_X:
            enemy.setx(BORDA_X)
            dx *= -1
            state["enemy_moves"][e] = dx
        if enemy.xcor() < -BORDA_X:
            enemy.setx(-BORDA_X)
            dx *= -1
            state["enemy_moves"][e] = dx
        if random.random() < ENEMY_INVERT_CHANCE:
            dx *= -1
            state["enemy_moves"][e] = dx

def inimigos_disparam(state):
    if state["game"] == "stop":
        return
    for e in state["enemies"]:
        if random.random() <= ENEMY_FIRE_PROB:
            ex = e.xcor()
            ey = e.ycor()
            bala = criar_bala(ex, ey-10, "enemy")
            state["enemy_bullets"].append(bala)
 
def verificar_colisoes_player_bullets(state):
    if state["game"] == "stop":
        return
    listabalas = state["player_bullets"][:]
    for b in range(len(listabalas)):
        bala = listabalas[b]
        for n in range(len(state["enemies"])):
            e = (state["enemies"])[n]
            bx = bala.xcor()
            by = bala.ycor()
            ex = e.xcor()
            ey = e.ycor()
            c = COLLISION_RADIUS
            if ex-c < bx < ex+c and ey-c< by < ey+c:
                e.hideturtle()
                bala.hideturtle()
                del state["enemies"][n]
                del state["player_bullets"][b]
                state["score"] += 10
                break
    return state["score"]

def verificar_colisoes_enemy_bullets(state):
    if state["game"] == "stop":
        return
    listabalas = state["enemy_bullets"][:]
    for b in range(len(listabalas)):
        p = state["player"]
        bala = listabalas[b]
        ebx = bala.xcor()
        eby = bala.ycor()
        px = p.xcor()
        py = p.ycor()
        c = COLLISION_RADIUS
        if px-c < ebx < px+c and py-c < eby < py+c:
            bala.hideturtle()
            p.hideturtle()
            del state["enemy_bullets"][b]
            return "over"

def inimigo_chegou_ao_fundo(state):
    for enemy in state["enemies"]:
        ey = enemy.ycor()
        if ey < -BORDA_Y:
            return "over"

def verificar_colisao_player_com_inimigos(state):
    if state["game"] == "stop":
        return
    player = state["player"]
    px = player.xcor()
    py = player.ycor()
    c = COLLISION_RADIUS
    for enemy in state["enemies"]:
        ex = enemy.xcor()
        ey = enemy.ycor()
        if (px-c) < ex < (px+c) and py+c > ey:
            player.hideturtle()
            enemy.hideturtle()
            return "over"

def verificar_nome_ficheiro(filename):
    if filename.isalpha() == True:
        return 1
    return 0

def lista_todas_balas(state):
    if COUNTER < 1:
        COUNTER += 1
        return
    elif COUNTER == 1:
        for pb in state["player_bullets"]:
            PLAYER_BULLETS.append(pb)
        for eb in state["enemy_bullets"]:
            ENEMY_BULLETS.append(eb)
        COUNTER = 0

def verificar_turtle_bug():
    for pb in state["player_bullets"]:
        pbx = pb.xcor()
        pby = pb.ycor()
        index = state["player_bullets"].index(pb)
        pastx = PLAYER_BULLETS[index].xcor()
        pasty = PLAYER_BULLETS[index].ycor()
        if pbx == pastx and pby == pasty:
            pb.hideturtle()
            state["player_bullets"].remove(index)
    for eb in state["enemy_bullets"]:
        ebx = eb.xcor()
        eby = eb.ycor()
        index = state["enemy_bullets"].index(eb)
        pastx = ENEMY_BULLETS[index].xcor()
        pasty = ENEMY_BULLETS[index].ycor()
        if ebx == pastx and eby == pasty:
            eb.hideturtle()
            state["enemy_bullets"].remove(index)
            


# =========================
# Execução principal
# =========================

if __name__ == "__main__":
    # Pergunta inicial: carregar?
    filename = input("Carregar jogo? Se sim, escreva nome do ficheiro, senão carregue Return: ").strip()
    carregado = carregar_estado_txt(filename)

    # Ecrã
    screen = turtle.Screen()
    screen.title("Space Invaders IPRP")
    screen.bgcolor("black")
    screen.setup(width = LARGURA, height = ALTURA, startx = None, starty = None)
    screen.tracer(0)

    # Imagens obrigatórias
    for img in ["player.gif", "enemy.gif", "player_bullet.gif", "enemy_bullet.gif"]:
        if not os.path.exists(img):
            print("ERRO: imagem '" + img + "' não encontrada.")
            sys.exit(1)
        screen.addshape(img)

    # Estado base
    state = {
        "screen": screen,
        "player": None,
        "enemies": [],
        "enemy_moves": [],          
        "player_bullets": [],
        "enemy_bullets": [],
        "score": 0,
        "frame": 0,
        "files": {"highscores": HIGHSCORES_FILE, "save": SAVE_FILE},
        "game": "playing",
    }

    # Construção inicial
    if carregado != None:
        if carregado.get("player"):
            px = carregado["player"][1]
            py = carregado["player"][2]
            state["player"] = criar_entidade(state, px, py, "player")
        else:
            state["player"] = criar_entidade(state, 0, -350, "player")
        if carregado.get("score"):
            state["score"] = carregado.get("score")
        else:
            state["score"] = 0
        spawn_inimigos_em_grelha(state, carregado)
        restaurar_balas(state, carregado)
    else:
        print("New game!")
        jogador = criar_entidade(state, 0, -350, "player")
        state["player"] = jogador
        inimigo = spawn_inimigos_em_grelha(state)

    # Variavel global para os keyboard key handlers
    STATE = state

    # Teclas
    screen.listen()
    screen.onkeypress(mover_esquerda_handler, "Left")
    screen.onkeypress(mover_direita_handler, "Right")
    if time_since_last_bullet() >= BULLET_COOLDOWN:
        screen.onkeypress(disparar_handler, "space")
        LAST_SHOT_TIME[0] = time.time_ns()/(10**9)
    screen.onkeypress(gravar_handler, "g")
    screen.onkeypress(terminar_handler, "Escape")

    # Loop principal
    while True:
        atualizar_balas_player(STATE)
        atualizar_inimigos(STATE)
        inimigos_disparam(STATE)
        atualizar_balas_inimigos(STATE)
        verificar_colisoes_player_bullets(STATE)
        
        if verificar_colisao_player_com_inimigos(STATE)=="over":
            state["game"] = "stop"
            terminar_handler("derrota")
            turtle.exitonclick()
        
        elif verificar_colisoes_enemy_bullets(STATE)=="over":
            state["game"] = "stop"
            terminar_handler("derrota")
            turtle.exitonclick()

        elif inimigo_chegou_ao_fundo(STATE)=="over":
            state["game"] = "stop"
            terminar_handler("derrota")
            turtle.exitonclick()

        elif len(STATE["enemies"]) == 0:
            state["game"] = "stop"
            terminar_handler("vitoria")
            turtle.exitonclick()

        STATE["frame"] += 1
        screen.update()
        time.sleep(0.016)