

function pmb_pipes.is_in_filter(itemname, filter)
    for _, catch in pairs(filter) do
        if itemname == catch or minetest.get_item_group(itemname, catch) ~= 0 then
            return true
        end
    end
    return false
end

function pmb_pipes.get_first_stack(from_inv, invname, filter)
    for i=1, from_inv:get_size(invname) do
        local istack = from_inv:get_stack(invname, i)
        if (not istack:is_empty())
        and ((not filter) or (filter
            and pmb_pipes.is_in_filter(istack:get_name(), filter))) then
            return i
        end
    end
    return false
end

function pmb_pipes.set_meta_vector(pos, v)
    local meta = minetest.get_meta(pos)
    meta:set_int("v_x", v.x)
    meta:set_int("v_y", v.y)
    meta:set_int("v_z", v.z)
end

function pmb_pipes.get_meta_vector(pos)
    local meta = minetest.get_meta(pos)
    local vect = vector.new(meta:get_int("v_x"), meta:get_int("v_y"), meta:get_int("v_z"))
    return vect
end

function pmb_pipes.start_timer(pos)
    local nt = minetest.get_node_timer(pos)
    if not nt:is_started() then
        nt:start(1.0)
    end
end

function pmb_pipes.get_dir_of_node(pos)
    local node = minetest.get_node(pos)
    return minetest.facedir_to_dir(node.param2)
end

function pmb_pipes.suck_up_items(from_pos, to_pos, amount, options)
    options = options or {}
    if not amount then amount = 1 end

    -- going to
    local to_meta = minetest.get_meta(to_pos)
    local to_inv = to_meta:get_inventory()
    local to_node = minetest.get_node_or_nil(to_pos)
    if not to_node then
        -- if the node is unloaded
        return false
    end
    local to_node_def = minetest.registered_nodes[to_node.name]

    for i, obj in ipairs(minetest.get_objects_in_area(
        vector.new(
            from_pos.x - 0.5,
            from_pos.y - 0.5,
            from_pos.z - 0.5
        ),
        vector.new(
            from_pos.x + 0.5,
            from_pos.y + 0.5,
            from_pos.z + 0.5
        )
    )) do repeat
        local ent = obj:get_luaentity()
        if (not ent) or ent.name ~= "__builtin:item" then break end
        local stack = ItemStack(ent.itemstring)
        local oldstack = ItemStack(stack)
        if not ((not options.filter) or (options.filter
        and pmb_pipes.is_in_filter(stack:get_name(), options.filter))) then break end

        if not to_inv:room_for_item("main", stack) then break end

        stack:set_count(math.min(stack:get_count(), amount))
        stack = to_inv:add_item("main", stack)

        local left = oldstack:get_count() - (amount - stack:get_count())

        if left <= 0 then
            obj:remove()
        else
            oldstack:set_count(left)
            ent.set_item(ent, oldstack)
        end
    until true
    end
end

function pmb_pipes.move_item_to(from_pos, to_pos, amount, options)
    -- show the items moving for debug
    if false then
        minetest.add_particle({
            pos = to_pos,
            velocity = vector.new(0, 3, 0),
            texture = "pmb_pipes_pipe.png",
        })
    end

    options = options or {}
    -- node coming from
    local from_meta = minetest.get_meta(from_pos)
    local from_inv = from_meta:get_inventory()
    local invname = "main"
    if (not from_inv:is_empty("main")) then
        invname = "main"
    elseif (not from_inv:is_empty("output")) then
        invname = "output"
    else
        return false -- everything empty, no need to keep trying
    end

    if not amount then amount = 1 end

    -- get the first stack and it's location
    local index = pmb_pipes.get_first_stack(from_inv, invname, options.filter)
    if not index then return false end
    local itemstack = from_inv:get_stack(invname, index)
    -- going to
    local to_meta = minetest.get_meta(to_pos)
    local to_inv = to_meta:get_inventory()
    local to_node = minetest.get_node_or_nil(to_pos)
    if not to_node then
        -- if the node is unloaded, DONT go to sleep, wait instead
        return true
    end
    local to_node_def = minetest.registered_nodes[to_node.name] or {}

    local newstack = ItemStack(itemstack)
    local oldstack = ItemStack(itemstack)
    local take = math.min(oldstack:get_count(), amount)
    newstack:set_count(take)
    oldstack:set_count(oldstack:get_count() - take)

    if to_node_def._on_input_item then
        local before = newstack:get_count()
        newstack = to_node_def._on_input_item(to_pos, newstack)
        local taken = before - newstack:get_count()
        oldstack = ItemStack(itemstack)
        oldstack:take_item(taken)
        from_inv:set_stack(invname, index, oldstack)
    elseif to_inv:room_for_item("main", newstack) then
        from_inv:set_stack(invname, index, oldstack)
        minetest.after(0.001, function ()
            if (not to_inv) then return end
            newstack = to_inv:add_item("main", newstack)
            if newstack:get_count() ~= 0 then
                minetest.add_item(to_pos, newstack)
            end
            pmb_pipes.start_timer(to_pos)
        end)
    else
        -- can't put anything here so give up
        return false
    end

    return true
end
