
local builtin_item = minetest.registered_entities["__builtin:item"]

pmb_item_ent = {}

pmb_item_ent.magnet_radius = 3
pmb_item_ent.magnet_speed = 8
pmb_item_ent.grace_period = 0.05
pmb_item_ent.key = "sneak"

local has_aom_settings = minetest.get_modpath("aom_settings") ~= nil
if has_aom_settings then
    aom_settings.register_setting("item_auto_pickup", true, "Enable auto pickup")
    aom_settings.register_setting("item_auto_pickup_delay", 3, "Pickup delay")
    aom_settings.register_setting("item_auto_pickup_max_dist", 2.5, "Pickup max distance")
end

local function get_eyepos(player, offset)
    local eyepos = vector.add(player:get_pos(), vector.multiply(player:get_eye_offset(), 0.1))
    eyepos.y = eyepos.y + player:get_properties().eye_height + (offset or 0)
    return eyepos
end

local function squaredist(p1, p2)
    return (((p1.x - p2.x) ^ 2) + ((p1.y - p2.y) ^ 2) + ((p1.z - p2.z) ^ 2))
end

function pmb_item_ent.player_can_pickup(self, player)
    local ctrl = player:get_player_control() or {}
    -- for compat with in game settings
    if has_aom_settings then
        -- test for if it actually has the setting enabled
        local auto_pickup = aom_settings.get_setting(player, "item_auto_pickup", false)
        if not auto_pickup then return false end
        -- no pickup if this player dropped this item within this time
        -- still pick up instantly for things not dropped by the same player though
        local pickup_delay = aom_settings.get_setting(player, "item_auto_pickup_delay", 2)
        if pickup_delay <= 0 then pickup_delay = 0.2 end -- prevent drop spam
        local is_dropper = self.dropped_by == player:get_player_name()
        if not ((not is_dropper) or (self.age > pickup_delay)) then return false end
        -- don't pick up items past the player set distance
        local max_dist = aom_settings.get_setting(player, "item_auto_pickup_max_dist", 1)
        local pos = self.object:get_pos()
        local ppos = player:get_pos()
        pos.y = ppos.y
        local dist = squaredist(pos, ppos)
        if not (auto_pickup and (dist < max_dist^2)) then return false end
        -- all checks passed, so let the player pick up
        return true
    -- for default fallback
    elseif ctrl[pmb_item_ent.key] and (self.age > 1) then
        return true
    end
    return false
end

local new_item_ent = {
    _picking_up_by = nil,
    set_item = function(self, item_name)
        builtin_item.set_item(self, item_name)
        self._get_custom_params(self)
    end,
    on_step = function(self, dtime, ...)
        builtin_item.on_step(self, dtime, ...)
        self:_do_magnet(dtime)
    end,
    _set_magnet = function(self, player)
        self._picking_up_by = player
        self:disable_physics()

        local dir = vector.direction(self.object:get_pos(), player:get_pos())
        local vel = vector.multiply(dir, 2)

        self.object:set_velocity(vel)
    end,
    _do_magnet = function(self, dtime, first)
        if self._picking_up_by then
            local player = self._picking_up_by
            local pos = self.object:get_pos()
            local target = get_eyepos(player, -1)
            local dist = vector.distance(vector.new(pos.x, target.y, pos.z), target)

            if dist < 0.1 then
                local inv = player:get_inventory()
                local stack = ItemStack(self.itemstring)
                stack:set_count(1)
                if inv:room_for_item("main", stack) then
                    minetest.sound_play("pmb_pickup_item", {
                        gain = 0.3 + math.random() * 0.1,
                        to_player = player:get_player_name(),
                        pitch = 0.8 + math.random() * 0.2
                    })
                    return self.on_punch(self, player)
                else
                    self._picking_up_by = nil
                    self:enable_physics()
                    self.object:set_velocity(vector.new(0,0,0))
                end
            elseif dist < pmb_item_ent.magnet_radius then
                local dir = vector.direction(pos, target)
                dir.y = dir.y + (0.05)
                local vel = vector.multiply(dir, pmb_item_ent.magnet_speed)

                local lvel = self.object:get_velocity()
                lvel = vector.multiply(lvel, -0.07)
                vel = vector.add(lvel, vel)

                self.object:set_velocity(vel)
            elseif dist > pmb_item_ent.magnet_radius + 0.3 then
                self:enable_physics()
                self._picking_up_by = nil
            end
        elseif not self.enable_physics then
            self:enable_physics()
        end
    end,
    _get_custom_params = function(self)
        local stack = ItemStack(self.itemstring)
        local def = minetest.registered_items[stack:get_name()]
        if def and def._override_item_entity then
            self.object:set_properties(def._override_item_entity)
        end
    end,
    on_activate = function(self, staticdata, dtime_s)
        builtin_item.on_activate(self, staticdata, dtime_s)
    end,
    on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir, damage, ...)
        -- just delete empty ones
        if self.itemstring == "" then
            self.object:remove()
            return
        end

        if tool_capabilities and tool_capabilities.damage_groups.projectile then
            return
        end

        local itemstack = ItemStack(self.itemstring)
        local itemname = itemstack:get_name()

        -- if the item has an on_pickup function, use it
        local def = minetest.registered_items[itemname]
        if def and def.on_pickup then
            local ret = def.on_pickup(itemstack, puncher, {type = "object", ref = self.object}, ...)
            if ret then
                itemstack = ItemStack(ret)
            end
        end

        -- if nothing left, delete
        if itemstack:is_empty() then
            self.itemstring = ""
            self.object:remove()
        else
            -- otherwise set this to the new stack
            self:set_item(itemstack)
        end
    end,
    get_staticdata = function(self)
        local data = {
            itemstring = self.itemstring,
            age = self.age,
            dropped_by = self.dropped_by,
            --
        }
        return minetest.serialize(data)
    end,
}

setmetatable(new_item_ent, { __index = builtin_item })
minetest.register_entity(":__builtin:item", new_item_ent)

local pl = {}

local function process_item_ent(self, player)
    if self._picking_up_by then return end
    if self.age < pmb_item_ent.grace_period then return end

    local inv = player:get_inventory()
    local stack = ItemStack(self.itemstring)
    stack:set_count(1)
    if not inv:room_for_item("main", stack) then return end
    if not pmb_item_ent.player_can_pickup(self, player) then return end
    self._set_magnet(self, player)
end

local on_globalstep = function(dtime)
    for i, player in pairs(minetest.get_connected_players()) do
        if not pl[player] then pl[player] = {t=1} end
        if pl[player].t > 0 then
            pl[player].t = pl[player].t - dtime
        else repeat
            pl[player].t = 0.5 + math.random()*0.1
            if player:get_meta():get_string("dead") == "true" then break end

            local pos = get_eyepos(player, -1)
            local objects = minetest.get_objects_inside_radius(pos, pmb_item_ent.magnet_radius)
            for k, obj in pairs(objects) do
                local ent = obj:get_luaentity()
                if ent and ent.name == "__builtin:item" then
                    process_item_ent(ent, player)
                end
            end
        until true end
    end
end
minetest.register_globalstep(on_globalstep)

-- overwrite old drop func to make it drop from eye pos
local function new_drop(itemstack, dropper, pos)
	local is_player = dropper and dropper:is_player()
	local p = vector.new(pos)
	local cnt = itemstack:get_count()
	if is_player then
		p = get_eyepos(dropper, -0.5)
	end
	local item = itemstack:take_item(cnt)
	local obj = minetest.add_item(p, item)
	if obj then
		if is_player then
            -- imitate builtin
			local dir = vector.multiply(dropper:get_look_dir(), 2.9)
            dir.y = dir.y + 2
			obj:set_velocity(dir)
			obj:get_luaentity().dropped_by = dropper:get_player_name()
		end
		return itemstack
	end
    return nil
end
-- overwrite
minetest.item_drop = new_drop
