Engine.DebugLua(true)

local Objects     = {}
local Velocities  = {}
local CachedVecs  = {}
local CachedPoses = {}
local CameraObj
local CachedCamPos = nil

local CamSpeed   = 800
local MinX, MinY = 0, 0
local MaxX, MaxY = 5096, 5096
local Speed      = 1000

local function clamp(v, lo, hi)
    return (v < lo and lo) or (v > hi and hi) or v
end

function OnInit()
    math.randomseed(os.time())
    Engine.LogInfo("Initial Speed: " .. Speed)

    for i = 0, 99 do
        local tag = "Light_" .. i
        local obj = Engine.GetObjectByTag(tag)
        if obj then
            table.insert(Objects, obj)

            local ang = math.random() * 2 * math.pi
            Velocities[obj]  = { x = math.cos(ang) * Speed, y = math.sin(ang) * Speed }
            CachedVecs[obj]  = Vector2(0, 0)      -- reused Vector2 for SetPosition
            CachedPoses[obj] = obj:GetPosition() -- only once at init
        else
            Engine.LogError("Object '" .. tag .. "' not found!")
        end
    end

    CameraObj = Engine.GetObjectByTag("Camera")
    if CameraObj then
        CachedCamPos = CameraObj:GetPosition()
    else
        Engine.LogError("Camera object not found!")
    end
end

function OnUpdate(dt)
    if Engine.KeyDown(Keycode.Equal) or Engine.KeyDown(Keycode.KPAdd) then
        Speed = Speed + 100 * dt
        Engine.LogInfo(string.format("Speed increased to %.1f", Speed))
    elseif Engine.KeyDown(Keycode.Minus) or Engine.KeyDown(Keycode.KPSubtract) then
        Speed = Speed - 100 * dt
        if Speed < 0 then Speed = 0 end
        Engine.LogInfo(string.format("Speed decreased to %.1f", Speed))
    end

    -- update light positions
    for _, obj in ipairs(Objects) do
        local vel = Velocities[obj]
        local pos = CachedPoses[obj]

        local ang = math.atan(vel.y, vel.x)
        vel.x = math.cos(ang) * Speed
        vel.y = math.sin(ang) * Speed

        local nx = pos.x + vel.x * dt
        local ny = pos.y + vel.y * dt

        if nx < MinX or nx > MaxX then
            vel.x = -vel.x
            nx = clamp(nx, MinX, MaxX)
        end
        if ny < MinY or ny > MaxY then
            vel.y = -vel.y
            ny = clamp(ny, MinY, MaxY)
        end

        pos.x = nx
        pos.y = ny

        local cachedVec = CachedVecs[obj]
        cachedVec.x = nx
        cachedVec.y = ny
        obj:SetPosition(cachedVec)
    end

    -- camera movement
    if not CameraObj then
        CameraObj = Engine.GetObjectByTag("Camera")
        if CameraObj then
            CachedCamPos = CameraObj:GetPosition()
        else
            Engine.LogError("Camera object not found!")
            return
        end
    end

    local moved = false
    if Engine.KeyDown(Keycode.W) then CachedCamPos.y = CachedCamPos.y - CamSpeed * dt moved = true end
    if Engine.KeyDown(Keycode.S) then CachedCamPos.y = CachedCamPos.y + CamSpeed * dt moved = true end
    if Engine.KeyDown(Keycode.A) then CachedCamPos.x = CachedCamPos.x - CamSpeed * dt moved = true end
    if Engine.KeyDown(Keycode.D) then CachedCamPos.x = CachedCamPos.x + CamSpeed * dt moved = true end
    if moved then CameraObj:SetPosition(CachedCamPos) end
end
