
aom_playerapi.physics = {}

---@param c table collision_box table
---@param o table | nil vector for offset
---@param j number | nil top of colbox overflow
---@param z number | nil bottom of colbox underflow (prevent non-floor)
---@return table -- array of vector positions
function aom_playerapi.physics.collision_box_to_vertex_list(c, o, z, j)
    if o == nil then o = vector.new(0,0,0) end
    local list = {}
    z = z or 0.1
    j = j or 0.01
    -- back left
    table.insert(list, vector.new(c[1]-z+o.x, c[5]+z+o.y, c[3]-z+o.z))
    table.insert(list, vector.new(c[1]+j+o.x, c[2]-z+o.y, c[3]+j+o.z))
    -- front left
    table.insert(list, vector.new(c[1]-z+o.x, c[5]+z+o.y, c[6]+z+o.z))
    table.insert(list, vector.new(c[1]+j+o.x, c[2]-z+o.y, c[6]-j+o.z))
    -- front right
    table.insert(list, vector.new(c[4]+z+o.x, c[5]+z+o.y, c[6]+z+o.z))
    table.insert(list, vector.new(c[4]-j+o.x, c[2]-z+o.y, c[6]-j+o.z))
    -- back right
    table.insert(list, vector.new(c[4]+z+o.x, c[5]+z+o.y, c[3]-z+o.z))
    table.insert(list, vector.new(c[4]-j+o.x, c[2]-z+o.y, c[3]+j+o.z))
    return list
end




aom_playerapi._on_player_touch_nodes = {}
core.register_on_mods_loaded(function()
    for nodename, def in pairs(core.registered_nodes) do
        if def._on_player_touch or def._on_player_standing_on then
            aom_playerapi._on_player_touch_nodes[nodename] = def
        end
    end
end)

function aom_playerapi.physics.raycast_collisionbox(player, vert_list)
    local pi = aom_playerapi.check_player(player)
    pi.vert = (pi.vert or 0) % (#vert_list/2) + 1
    local vi = pi.vert * 2
    local start  = vert_list[vi - 1]
    local target = vert_list[vi]
    local ray = core.raycast(start, target, false, true)
    pi.touching_nodes[pi.vert] = {}
    local list = pi.touching_nodes[pi.vert]
    for pt in ray do
        local node = core.get_node_or_nil(pt.under)
        local ndef = node and aom_playerapi._on_player_touch_nodes[node.name]
        local is_in = false
        for vk = 1, 4 do for i, record in ipairs(pi.touching_nodes[vk] or {}) do
            if record.pos:equals(pt.under) then is_in = true; break end
        end end
        if ndef and not is_in then
            table.insert(list, {pos = pt.under, node = node, ndef = ndef})
        end
    end
end

local t = 0
local avgtime = 0
function aom_playerapi.physics.on_step_raycast(player, dtime)
    local pi = aom_playerapi.check_player(player)
    pi.touching_nodes = pi.touching_nodes or {}
    local props = player:get_properties()
    local pos = player:get_pos()
    if (pi.vert_list == nil) or (pi.vert and pi.vert == 1) then
        pi.vert_list = aom_playerapi.physics.collision_box_to_vertex_list(props.collisionbox, pos)
    end
    aom_playerapi.physics.raycast_collisionbox(player, pi.vert_list)
end

function aom_playerapi.physics.do_on_player_touch(player, dtime)
    local pi = aom_playerapi.check_player(player)
    local pos = player:get_pos()
    local iters = 0
    for i = 1, 4 do for k, record in ipairs(pi.touching_nodes[i] or {}) do
        local node = core.get_node_or_nil(record.pos)
        iters = iters + 1
        if node.name == record.node.name then
            if record.ndef._on_player_standing_on and (record.pos.y + 0.499 < pos.y) then
                record.ndef._on_player_standing_on(record.pos, player, dtime)
            end
            if record.ndef._on_player_touch then
                record.ndef._on_player_touch(record.pos, player, dtime)
            end
        end
    end end
end

function aom_playerapi.physics.on_step(player, dtime)
    local pi = aom_playerapi.check_player(player)
    if not pi.last_pos then pi.last_pos = player:get_pos() end
    if not pi.avg_vel then pi.avg_vel = player:get_velocity() end

    aom_playerapi.physics.do_on_player_touch(player, dtime)
    aom_playerapi.on_player_step(player, dtime, pi)

    pi.real_vel = player:get_pos() - pi.last_pos
    pi.last_pos = player:get_pos()
    pi.avg_vel = ((pi.real_vel*10) + (pi.avg_vel*5)) /6
end

local playerindex = 1
function aom_playerapi.physics.iterate_players_raycast(conplayers, num, dtime)
    local max = #conplayers
    if max == 0 then return end
    num = math.min(num, max)
    for i = 1, num do
        if playerindex > max then playerindex = 1 end
        aom_playerapi.physics.on_step_raycast(conplayers[playerindex], dtime)
        playerindex = playerindex + 1
    end
end

core.register_globalstep(function(dtime)
    -- local clo = os.clock()
    local conplayers = core.get_connected_players()
    local num = aom_playerapi.get_setting(nil, "performance_player_raycast_max", 10)
    aom_playerapi.physics.iterate_players_raycast(conplayers, num, dtime)
    for i, player in ipairs(conplayers) do
        aom_playerapi.physics.on_step(player, dtime)
    end
    -- avgtime = (avgtime*29 + ((os.clock() - clo) * 1000)) / 30
    -- core.log(avgtime .. " ms avg")
end)


