
function aom_vehicleapi.get_ndef(pos)
    local node = minetest.get_node_or_nil(pos)
    return node, node and minetest.registered_nodes[node.name]
end

function aom_vehicleapi.is_node_walkable(pos, node, ndef)
    return ndef and ndef.walkable ~= false
end

function aom_vehicleapi.is_in_water(self, y_offset)
    local pos = self.object:get_pos()
    pos.y = pos.y + (y_offset or 0)
    local node, ndef = aom_vehicleapi.get_ndef(pos)
    if node and minetest.get_item_group(node.name, "water") > 0 then
        return true
    end
end

function aom_vehicleapi.is_in_node(self, y_offset)
    local pos = self.object:get_pos()
    pos.y = pos.y + (y_offset or 0)
    local node, ndef = aom_vehicleapi.get_ndef(pos)
    if (not node) or (ndef.walkable ~= false) then
        return true
    end
end

function aom_vehicleapi.get_polygon_AABB(polydef)
    local aabb = {999, 999, 999,-999,-999,-999}
    for i, point in ipairs(polydef.points) do
        if point[1] < aabb[1] then aabb[1] = point[1] end
        if point[1] > aabb[4] then aabb[4] = point[1] end
        if point[2] < aabb[3] then aabb[3] = point[2] end
        if point[2] > aabb[6] then aabb[6] = point[2] end
    end
    aabb[2] = polydef.height[1]
    aabb[5] = polydef.height[2]
    return aabb
end

function aom_vehicleapi.point_is_in_AABB(aabb, rpos)
    if (rpos.x < aabb[1]) or (rpos.y < aabb[2]) or (rpos.z < aabb[3])
    or (rpos.x > aabb[4]) or (rpos.y > aabb[5]) or (rpos.z > aabb[6]) then
        return false
    end
    return true
end

--TODO: use AABB first, and use this func to calculate AABB box on first run
function aom_vehicleapi.is_point_inside_polygon(polydef, rpos, ignore_height)
    if (not ignore_height) and ((rpos.y < polydef.height[1]) or (rpos.y > polydef.height[2])) then
        return false
    end
    if not polydef.AABB then
        polydef.AABB = aom_vehicleapi.get_polygon_AABB(polydef)
    end
    -- check AABB first because it's super cheap
    if not aom_vehicleapi.point_is_in_AABB(polydef.AABB, rpos) then
        return false
    end
    local x, z = rpos.x, rpos.z
    local poly = polydef.points
    local j = #poly;
    local oddNodes = false;
    for i = 1, #poly do
        if ((poly[i][2] <  z and poly[j][2] >= z
        or   poly[j][2] <  z and poly[i][2] >= z)
        and (poly[i][1] <= x or  poly[j][1] <= x)) then
            local res = (poly[i][1] +
                (z - poly[i][2]) /
                (poly[j][2] - poly[i][2]) *
                (poly[j][1] - poly[i][1]) < x
            )
            oddNodes = (oddNodes or res) and not (oddNodes == res)
        end
        j = i
    end
    return oddNodes;
end

function aom_vehicleapi.is_in_collision_polygon(self, ppos)
    local rpos = self.object:get_pos()
    rpos = ppos - rpos
    rpos = aom_vehicleapi.rotate_point(rpos, -aom_vehicleapi.get_actual_yaw(self))
    for i, polydef in pairs(self._aomv_polygon) do
        if polydef.collision
        and aom_vehicleapi.is_point_inside_polygon(polydef, rpos) then
            return true
        end
    end
    return false
end

function aom_vehicleapi.do_anti_drift(self, add_vel, dtime)
    local vel = self.object:get_velocity()
    -- reduce drift
    local alen = vector.length(add_vel)
    local vlen = vector.length(vel)
    if vlen > (self._aomv_anti_drift_threshold or 0.1) then
        local tlen = vector.length(add_vel + vel)
        local f = aom_vehicleapi.clamp(dtime * (self._aomv_anti_drift or 1), 0.01, 0.5)
        alen = tlen * (f)
        vlen = tlen * (1-f)
        add_vel = vector.normalize(add_vel) * alen + (vector.normalize(vel) * vlen)
        return add_vel
    else
        return false
    end
    return add_vel
end

function aom_vehicleapi.do_vehicle_to_vehicle_collision(self, dtime)
    if not self._aomv_collides_with_other_vehicles then return end
    local vehicles = {}
    local pos = self.object:get_pos()
    for i, object in ipairs(minetest.get_objects_inside_radius(pos, 100)) do
        local ent = object:get_luaentity()
        if ent and ent._aomv_collides_with_other_vehicles and (ent ~= self) then
            table.insert(vehicles, ent)
        end
    end
    local add_vel = vector.zero()
    local found = false
    local veh_pushed = {}
    local push_force = 10 * dtime
    for k, pdef in ipairs(self._aomv_pointcloud or {}) do
        local p = vector.new(pdef.pos)
        local d = vector.new(pdef.dir)
        local yaw = aom_vehicleapi.get_actual_yaw(self)
        p = aom_vehicleapi.rotate_point(p, yaw) + pos
        d = aom_vehicleapi.rotate_point(d, yaw)
        aom_vehicleapi.debug_particle(p, "#f0f", 0.2, d)
        for i = #vehicles, 1, -1 do
            local ent = vehicles[i]
            if aom_vehicleapi.is_in_collision_polygon(ent, p) then
                add_vel = add_vel - d
                found = true
                table.insert(veh_pushed, {ent=ent, vel=d*push_force})
                table.remove(vehicles, i)
            end
        end
    end
    return (found and (vector.normalize(add_vel) * push_force)) or nil
end

function aom_vehicleapi.do_collision_push(self, dtime)
    if self._collision_continue_time then
        self._collision_continue_time = self._collision_continue_time - dtime
        if self._collision_continue_time < 0 then
            if self._on_collision_push_sleep then
                self._on_collision_push_sleep(self)
            end
            self._collision_continue_time = nil
            self.object:set_properties({automatic_rotate = 0})
        end
    else
        return
    end

    local pos = self.object:get_pos()
    local yaw = aom_vehicleapi.get_actual_yaw(self)

    local push = aom_vehicleapi.get_pointcloud_collision(pos, yaw, self._aomv_pointcloud, nil, self._aomv_pointcloud_offset or vector.new(0,0,0))
    local push_from_vehicles = aom_vehicleapi.do_vehicle_to_vehicle_collision(self, dtime)
    if push_from_vehicles then push = push and (push + push_from_vehicles) or push_from_vehicles end

    if push then
        self._aomv_is_colliding = true
        push = push * dtime * 10
        push = push - self.object:get_velocity() * 0.2 -- ignore some of prev vel
        if self._on_collision_push and self._on_collision_push(self, push, dtime) then
            return push
        else
            self.object:add_velocity(push)
        end
        return push
    elseif self._aomv_is_colliding then
        self._aomv_is_colliding = false
        self._collision_continue_time = 2
        self.object:set_velocity(self.object:get_velocity() * 0.5)
    end
    return nil
end

function aom_vehicleapi.get_pointcloud_collision(pos, yaw, pointcloud, condition, offset)
    local dir_total = vector.new(0,0,0)
    local count = 0
    for i, pdef in ipairs(pointcloud) do repeat
        local p = vector.new(pdef.pos)
        local d = vector.new(pdef.dir)
        aom_vehicleapi.debug_particle(p + pos, "#494", 1)
        if offset then p = p + offset end
        p = aom_vehicleapi.rotate_point(p, yaw)
        d = aom_vehicleapi.rotate_point(d, yaw)
        local point = pos + p
        local node, ndef = aom_vehicleapi.get_ndef(point)
        aom_vehicleapi.debug_particle(point, "#fe6", 1)
        if not condition then condition = aom_vehicleapi.is_node_walkable end
        if not (condition(pos, node, ndef)) then break end
        dir_total = dir_total + vector.normalize(d)
        count = count + 1
    until true end

    if count == 0 then return nil, nil end
    return -vector.normalize(dir_total), count
end
