from math import sin, cos
import glm
from raylib.dynamic import raylib as rl, ffi


class CameraFly:
    def __init__(self, x=0, y=0, z=0, fov=70.0):
        self.last_mouse = ffi.new("struct Vector2 *", [0, 0])
        self.fov = fov
        self.yaw = -46.33
        self.pitch = 0.0
        self.front = glm.vec3()
        self.up = glm.vec3()
        self.right = glm.vec3()
        self.position = glm.vec3(x, y, z)
        self.world_up = glm.vec3(0, 1, 0)
        self.fps_facing = glm.vec3()
        self.movement_speed = 10
        self.mouse_sensitivity = 0.002
        self.last_time = rl.GetFrameTime()

    def get_camera(self):
        return ffi.new(
            "struct Camera3D *",
            [
                self.get_ray_front(),
                self.get_ray_position(),
                self.get_ray_up(),
                self.fov,
                rl.CAMERA_PERSPECTIVE
            ]
        )

    def get_ray_front(self):
        return list(self.position + self.front)
    
    def get_ray_up(self):
        return list(self.up)

    def get_ray_position(self):
        return list(self.position)

    def update_keyboard(self):
        boost = 4 if rl.IsKeyDown(rl.KEY_LEFT_SHIFT) else 1
        velocity = self.movement_speed * boost * rl.GetFrameTime()

        if rl.IsKeyDown(rl.KEY_W):
            self.position = self.position - self.fps_facing * velocity

        if rl.IsKeyDown(rl.KEY_S):
            self.position = self.position + self.fps_facing * velocity

        if rl.IsKeyDown(rl.KEY_D):
            dirr = glm.normalize(glm.cross(self.fps_facing, self.world_up))
            self.position = self.position - dirr * velocity

        if rl.IsKeyDown(rl.KEY_A):
            dirr = glm.normalize(glm.cross(self.fps_facing, self.world_up))
            self.position = self.position + dirr * velocity

        if rl.IsKeyDown(rl.KEY_SPACE):
            self.position = self.position + self.world_up * velocity

        if rl.IsKeyDown(rl.KEY_LEFT_CONTROL) or rl.IsKeyDown(rl.KEY_RIGHT_CONTROL):
            self.position = self.position - self.world_up * velocity

    def update_mouse(self):
        pos = rl.GetMousePosition()
        width = rl.GetScreenWidth()
        height = rl.GetScreenHeight()

        mouse_coordinates = ffi.new("struct Vector2 *", [
            pos.x - self.last_mouse.x,
            pos.y - self.last_mouse.y
        ])

        self.yaw   += mouse_coordinates.x * self.mouse_sensitivity
        self.pitch += (mouse_coordinates.y * self.mouse_sensitivity)
        self.pitch = min(self.pitch, 1.5)
        self.pitch = max(self.pitch, -1.5)
        self.last_mouse = rl.GetMousePosition()

    def update(self):
        # NOTE(pebaz): Comment out these lines to control them in a game loop
        self.update_keyboard()
        self.update_mouse()

        # Update all camera vectors to reflect changes
        self.fps_facing = glm.vec3(
            cos((self.yaw)) * cos((0)),
            sin((0)),
            sin((self.yaw)) * cos((0))
        )
        self.fps_facing = glm.normalize(self.fps_facing)

        front = glm.vec3(
            cos((self.yaw)) * cos((self.pitch)),
            sin((self.pitch)),
            sin((self.yaw)) * cos((self.pitch))
        )

        self.front = glm.normalize(front)
        self.right = glm.normalize(glm.cross(self.front, self.world_up))
        self.up = glm.normalize(glm.cross(self.right, self.front))


def camera_test():
    rl.SetTraceLogLevel(rl.LOG_ERROR)
    rl.SetConfigFlags(rl.FLAG_WINDOW_RESIZABLE)
    rl.InitWindow(512, 256, b'Test')
    rl.SetTargetFPS(60)
    rl.DisableCursor()

    flycam = CameraFly()


    while not rl.WindowShouldClose():
        flycam.update()
        cam = flycam.get_camera()

        rl.BeginDrawing()
        rl.ClearBackground((0, 200, 255, 255))
        rl.BeginMode3D(cam[0])

        # NOTE(pebaz): For whatever reason, this can solve a percentage of artifacts
        rl.DrawGizmo([100000000, 100000000, 100000000])

        rl.DrawGrid(32, 1)

        rl.EndMode3D()
        rl.EndDrawing()

    rl.CloseWindow()

if __name__ == '__main__':
    camera_test()