import bpy
import mathutils
from mathutils import Vector
import random
import bgl
import blf
import math

def limpar_cena():
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete()

def cria_chao(largura=10, comprimento=60):
    bpy.ops.mesh.primitive_plane_add(size=1, location=(0, comprimento/2 - 5, 0))
    chao = bpy.context.object
    chao.scale = (largura/2, comprimento/2, 1)
    chao.name = "Ground"
    mat = bpy.data.materials.new("GroundMat")
    mat.diffuse_color = (0.2, 0.2, 0.2, 1)
    chao.data.materials.append(mat)
    chao.select_set(False)
    return chao

def cria_obstaculo(posicao, escala=(1,1,2)):
    bpy.ops.mesh.primitive_cube_add(location=posicao)
    obst = bpy.context.object
    obst.name = "Obstacle"
    obst.scale = escala
    mat = bpy.data.materials.new("ObsMat")
    mat.diffuse_color = (1, 0, 0, 1)
    obst.data.materials.append(mat)
    obst.select_set(False)
    return obst

def cria_plataforma_movel(posicao, escala=(2,4,0.5)):
    bpy.ops.mesh.primitive_cube_add(location=posicao)
    plat = bpy.context.object
    plat.name = "MovingPlatform"
    plat.scale = escala
    mat = bpy.data.materials.new("PlatformMat")
    mat.diffuse_color = (0, 0, 1, 1)
    plat.data.materials.append(mat)
    plat.select_set(False)
    plat["move_dir"] = 1
    return plat

def cria_jogador():
    bpy.ops.mesh.primitive_cube_add(size=1, location=(0, 0, 1))
    player = bpy.context.object
    player.name = "Player"
    mat = bpy.data.materials.new("PlayerMat")
    mat.diffuse_color = (0, 1, 0, 1)
    player.data.materials.append(mat)
    player.select_set(False)
    return player

def cria_camera_luz():
    bpy.ops.object.camera_add(location=(0, -20, 12))
    cam = bpy.context.object
    cam.rotation_euler = (math.radians(60), 0, 0)
    cam.name = "Camera"
    bpy.context.scene.camera = cam

    bpy.ops.object.light_add(type='SUN', location=(15, -15, 15))
    luz = bpy.context.object
    luz.name = "SunLight"
    luz.rotation_euler = (math.radians(50), 0, math.radians(30))
    luz.data.energy = 4

def constroi_cenario():
    limpar_cena()
    cria_chao()
    cria_camera_luz()
    cria_jogador()

class PlatformRunnerGame:
    def __init__(self):
        self.player = bpy.data.objects.get("Player")
        self.cam = bpy.data.objects.get("Camera")
        self.ground = bpy.data.objects.get("Ground")
        
        if any(o is None for o in [self.player, self.cam, self.ground]):
            raise Exception("Player, Camera ou Ground não configurados corretamente.")
        
        self.speed = 0.2
        self.jump_speed = 0.3
        self.gravity = -0.02
        
        self.is_jumping = False
        self.velocity_z = 0
        
        self.pressed_keys = set()
        
        self.score = 0
        self.running = False
        
        self._handle = None
        
        self.obstaculos = []
        self.plataformas = []
        self.spawn_interval = 5
        self.spawn_ahead_distance = 50
        self.despawn_distance = 10
        
        self.next_spawn_y = 0

    def start(self):
        if self.running:
            print("Jogo já rodando")
            return
        
        self.running = True
        self.player.location = Vector((0, 0, 1))
        self.score = 0
        self.is_jumping = False
        self.velocity_z = 0
        self.pressed_keys.clear()

        # Reset obstáculos e plataformas
        for obj in list(bpy.data.objects):
            if obj.name.startswith("Obstacle") or obj.name.startswith("MovingPlatform"):
                bpy.data.objects.remove(obj, do_unlink=True)
        self.obstaculos.clear()
        self.plataformas.clear()
        self.next_spawn_y = 0

        # Spawn inicial
        self.spawn_initial()

        self._handle = bpy.types.SpaceView3D.draw_handler_add(self.draw_hud, (), "WINDOW", "POST_PIXEL")
        bpy.app.timers.register(self.redraw)
        print("Jogo iniciado!")

    def stop(self):
        self.running = False
        print(f"Jogo finalizado! Pontuação: {self.score}")
        if self._handle:
            bpy.types.SpaceView3D.draw_handler_remove(self._handle, "WINDOW")
            self._handle = None

    def redraw(self):
        if self.running:
            bpy.context.area.tag_redraw()
            return 0.03

    def spawn_initial(self):
        faixa_x = [-4, 0, 4]
        y = self.next_spawn_y
        for _ in range(12):
            n_obs = random.choice([1, 2])
            posicoes_x = random.sample(faixa_x, n_obs)
            for x in posicoes_x:
                if random.random() < 0.25 and x == 0:
                    plat = cria_plataforma_movel((x, y, 1))
                    self.plataformas.append(plat)
                else:
                    obst = cria_obstaculo((x, y, 1))
                    self.obstaculos.append(obst)
            y += self.spawn_interval
        self.next_spawn_y = y

    def procedural_obstacles(self):
        player_y = self.player.location.y
        while self.next_spawn_y < player_y + self.spawn_ahead_distance:
            faixa_x = [-4, 0, 4]
            n_obs = random.choice([1,2])
            posicoes_x = random.sample(faixa_x, n_obs)
            for x in posicoes_x:
                if random.random() < 0.25 and x == 0:
                    plat = cria_plataforma_movel((x, self.next_spawn_y, 1))
                    self.plataformas.append(plat)
                else:
                    obst = cria_obstaculo((x, self.next_spawn_y, 1))
                    self.obstaculos.append(obst)
            self.next_spawn_y += self.spawn_interval

        limite_atrasado = player_y - self.despawn_distance
        objs_para_remover = [obj for obj in self.obstaculos + self.plataformas if obj.location.y < limite_atrasado]
        for obj in objs_para_remover:
            if obj in self.obstaculos:
                self.obstaculos.remove(obj)
            if obj in self.plataformas:
                self.plataformas.remove(obj)
            bpy.data.objects.remove(obj, do_unlink=True)

    def mover_plataformas(self):
        for plat in self.plataformas:
            mov_dir = plat["move_dir"]
            plat.location.x += 0.05 * mov_dir
            if plat.location.x > 3:
                plat["move_dir"] = -1
            elif plat.location.x < -3:
                plat["move_dir"] = 1

    def movimentar_player(self):
        mov_x = 0
        mov_y = 0
        moving_sideways = False
        
        if 'A' in self.pressed_keys:
            mov_x -= 1
            moving_sideways = True
        if 'D' in self.pressed_keys:
            mov_x += 1
            moving_sideways = True
            
        if 'W' in self.pressed_keys:
            mov_y += self.speed * 2
        else:
            mov_y += self.speed

        direction = Vector((mov_x, mov_y, 0))
        if direction.length > 0:
            direction.normalize()
            direction *= self.speed if mov_x != 0 else self.speed*2

        new_pos = self.player.location + Vector((direction.x, direction.y, 0))

        if not self.colisao_obstaculos(new_pos):
            if not self.is_jumping:
                self.player.location.x = new_pos.x
                self.player.location.y = new_pos.y

        # Animação simples de balanço
        t = bpy.context.scene.frame_current / 10.0
        if moving_sideways and not self.is_jumping:
            self.player.rotation_euler.z = 0.2 * math.sin(t * 10)
            self.player.scale = (1, 1 + 0.1 * math.sin(t * 20), 1)
        else:
            self.player.rotation_euler.z = 0
            self.player.scale = (1,1,1)

        if self.is_jumping:
            self.velocity_z += self.gravity
            new_z = self.player.location.z + self.velocity_z
            if new_z <= 1:
                new_z = 1
                self.is_jumping = False
                self.velocity_z = 0
            self.player.location.z = new_z

        plat_embaixo = self.verifica_plataforma_embaixo()
        if plat_embaixo and not self.is_jumping:
            self.player.location.x += plat_embaixo["move_dir"] * 0.05

        nova_pontuacao = int(self.player.location.y)
        if nova_pontuacao > self.score:
            self.score = nova_pontuacao

    def verifica_plataforma_embaixo(self):
        px, py, pz = self.player.location
        for plat in self.plataformas:
            plx, ply, plz = plat.location
            if (abs(px - plx) <= plat.scale.x and abs(py - ply) <= plat.scale.y and
                (pz - plz) <= plat.scale.z + 0.1 and (pz - plz) >= 0):
                return plat
        return None

    def colisao_obstaculos(self, pos):
        px, py, pz = pos
        for obst in self.obstaculos:
            ox, oy, oz = obst.location
            sx, sy, sz = obst.scale
            if (ox - sx <= px <= ox + sx and
                oy - sy <= py <= oy + sy and
                abs(pz - oz) <= sz + 0.5):
                return True
        return False

    def draw_hud(self):
        font_id = 0
        bgl.glEnable(bgl.GL_BLEND)
        blf.position(font_id, 20, 80, 0)
        blf.size(font_id, 24, 72)
        blf.color(font_id, 1, 1, 1, 1)
        blf.draw(font_id, f"Pontuação: {self.score}")
        blf.position(font_id, 20, 50, 0)
        blf.size(font_id, 16, 72)
        blf.draw(font_id, "Use A/D para mover lateral, W para correr, Space para pular.")
        bgl.glDisable(bgl.GL_BLEND)

    def modal(self, context, event):
        if not self.running:
            return {'CANCELLED'}

        if event.type == 'ESC' and event.value == 'PRESS':
            self.stop()
            return {'CANCELLED'}

        if event.value == 'PRESS':
            if event.type in {'A','D','W'}:
                self.pressed_keys.add(event.type)
            if event.type == 'SPACE' and not self.is_jumping:
                self.is_jumping = True
                self.velocity_z = self.jump_speed

        if event.value == 'RELEASE':
            if event.type in self.pressed_keys:
                self.pressed_keys.remove(event.type)

        self.procedural_obstacles()
        self.mover_plataformas()
        self.movimentar_player()
        self.atualizar_camera()

        return {'RUNNING_MODAL'}

    def atualizar_camera(self):
        offset = Vector((0, -15, 10))
        self.cam.location = self.player.location + offset
        self.cam.rotation_euler = (math.radians(60), 0, 0)

class PLATFORMRUNNER_OT_game(bpy.types.Operator):
    bl_idname = "wm.platformrunner_game"
    bl_label = "Rodar Jogo Platform Runner"

    _game_instance = None

    def execute(self, context):
        if self._game_instance and self._game_instance.running:
            self._game_instance.stop()
            self._game_instance = None
            self.report({'INFO'}, "Jogo parado")
            return {'FINISHED'}
        try:
            self._game_instance = PlatformRunnerGame()
            self._game_instance.start()
            context.window_manager.modal_handler_add(self)
            self.report({'INFO'}, "Jogo iniciado! Use A/D/W/Space.")
            return {'RUNNING_MODAL'}
        except Exception as e:
            self.report({'ERROR'}, f"Erro: {str(e)}")
            return {'CANCELLED'}

    def modal(self, context, event):
        if not self._game_instance:
            return {'CANCELLED'}
        return self._game_instance.modal(context, event)

def menu_func(self, context):
    self.layout.operator(PLATFORMRUNNER_OT_game.bl_idname, text="Iniciar Platform Runner Game")

def register():
    bpy.utils.register_class(PLATFORMRUNNER_OT_game)
    bpy.types.VIEW3D_MT_object.append(menu_func)

def unregister():
    bpy.utils.unregister_class(PLATFORMRUNNER_OT_game)
    bpy.types.VIEW3D_MT_object.remove(menu_func)

if __name__ == "__main__":
    constroi_cenario()
    register()
    print("Script carregado. Use o menu Object > Iniciar Platform Runner Game para jogar.")