--	item_drop code by cyisfor
--	https://github.com/cyisfor/item_drop

local movers = {}

local removedAlreadyDammit = {}

local vector_fixedNormalize = function(v)
	assert(v)
	local len = vector.length(v)
	if len == 0 then
		-- no length 1 vector will ever equal this
		return vector.new(0,0,0)
	else
		return vector.divide(v, len)
	end
end

local function removeObject(object)
	movers[object] = nil
	removedAlreadyDammit[object] = true
	object:remove()
end

-- returns whether the pickup failed or not.
-- nil pickupRadius means to infinity and beyond
local function pickup(player, inv, object, pickupRadius)
	if removedAlreadyDammit[object] then
		-- this gets called after the timeout, as well as when it hits the player
		return true
	end
	if player == nil then return true end

	-- itemstring is serialized item so includes metadata
	local lua = object:get_luaentity()
	local item = ItemStack(lua.itemstring)
	if inv and inv:room_for_item("main", item) then
		inv:add_item("main", item)
		if object:get_luaentity().itemstring ~= "" then
			minetest.sound_play("item_drop_pickup", {
				to_player = player:get_player_name(),
				gain = 0.4,
			}, true)
		end
		lua.itemstring = ''
		removeObject(object)
		return false
	else
		return true
	end
end

local function isGood(object)
	-- only want items swooping up after players, not after chests!
	if not object:is_player() and object:get_luaentity() and object:get_luaentity().name == "__builtin:item" then
		return true
	else
		return false
	end
end

local function stop(object)
	movers[object] = nil
	-- no pickup, even though it's close, so
	-- stop moving towards the player
	object:set_velocity({x=0, y=0, z=0})
	object:set_acceleration({x=0, y=0, z=0})
	-- also we can walk on it and it can push pressure plates
	-- physical_state = false means "please make us physical again"
	local lua = object:get_luaentity()
	if lua then
		lua.physical_state = false
	end
end

local function pickupOrStop(object, inv, player, pickupRadius)
	local lua = object:get_luaentity()
	if object == nil or lua == nil or lua.itemstring == nil then
		return
	end
	if pickup(player, inv, object, pickupRadius) then
		stop(object)
	end
end

-- GMass... it's the player's mass if the player were a giant planetlike object
-- which things orbit around
-- in the following units:
-- if G = 6.67×10−11 then
-- GMass = 1 for 14,992,503,748 kilograms
local PLAYER_G_MASS = 1.7
-- the player is faaaaaaaat

local function moveTowards(object, player, pickupRadius, attractRadius)
	-- hacky check to see if objects are valid
	if not object or not object:get_yaw() then return end
	if not player or not player:is_player() then return end
	-- move it towards the player, then pick it up after a delay!
	local pos1 = player:get_pos()
	if pos1 == nil then return end
	local pos2 = object:get_pos()
	if pos2 == nil then return end
	pos1.y = pos1.y+0.5 -- head towards player's belt
	local direct = vector.subtract(pos1, pos2)
	local R = vector.length(direct)
	local v = object:get_velocity()
	local stopped = v.x == 0 and v.y == 0 and v.z == 0
	-- when direction(X) = direction(V) we passed the player
	-- so project V onto X. If same, passed. If not, approaching.
	-- projection = norm(X) * (length(V) * cos(theta))
	-- => length(V) * dot(V,X) / length(V) / length(X)
	-- = dot(V,X) / length(X)
	-- if X is normalized, length(X) == 1 so... dot product!
	-- sign(dot) > 0 = same direction sign(dot)< 0 = different
	direct = vector_fixedNormalize(direct)

	-- idea, set velocity not acceleration but set it
	-- not to velocity + acceleration, but to the projection of that
	-- onto the direction vector. object will always have velocity towards YOU

	if R > attractRadius then
		stop(object)
		return
	end
	if R < pickupRadius or (not stopped and vector.dot(v,direct) < 0) then
		pickupOrStop(object,player:get_inventory(),player,nil)
		return
	end
	-- Fg = G*M1*M2/R^2
	-- M1*A1 = G * M1 * M2 / R^2
	-- A1 = G * M2 / R ^2
	-- G = whatever it takes for stuff to orbit around the player
	-- and the weight of the player is ^^^
	-- A1 = C / R^2
	local A
	A = PLAYER_G_MASS / R^2
	A = math.max(A,2*PLAYER_G_MASS)
	object:set_acceleration(vector.multiply(direct,A))
end

if minetest.settings:get("enable_item_pickup") == "true" then
	local tickets = 0 -- XXX: oy vey
	local moveDelay = 0
	minetest.register_globalstep(function(dtime)
		-- it's much more efficient to just restart... no way to unregister_globalstep right?
		if not minetest.settings:get("enable_item_pickup") then return end
		moveDelay = moveDelay + dtime
		local pickupRadius = tonumber(minetest.settings:get("pickup_radius"))
		local attractRadius = tonumber(minetest.settings:get("attract_radius"))
		if not pickupRadius then pickupRadius = 0.5 end
		if not attractRadius then attractRadius = 3 end

		if moveDelay > 0.1 then
			moveDelay = 0
			-- correct your trajectory while moving
			for object,pair in pairs(movers) do
				local player = pair[1]
				moveTowards(object,player,pickupRadius,attractRadius)
			end
		end
		for _, player in ipairs(minetest.get_connected_players()) do
			if player:get_hp() > 0 or not minetest.settings:get_bool("enable_damage") then
				local playerPosition = player:get_pos()
				if playerPosition ~= nil then
					playerPosition.y = playerPosition.y + 0.5
					local inv = player:get_inventory()

					for _, object in ipairs(minetest.get_objects_inside_radius(playerPosition, 3)) do
						local lua = object:get_luaentity()
						if isGood(object) and lua and (lua.dropped_by ~= player:get_player_name() or lua.age > 3) and
							inv and
							inv:room_for_item("main", ItemStack(lua.itemstring))
						then
								local ticket = tickets
								movers[object] = {player,ticket}
								tickets = tickets + 1
								moveTowards(object, player, pickupRadius, attractRadius)
								-- make sure object doesn't push the player around!
								lua.physical_state = true
								lua.object:set_properties({
									physical = false,
									collide_with_objects = false,
									weight = 0
								})
								-- pleeease no immortal orbiting entities
								-- unless you want them to be >:)
								minetest.after(30, function(object)
									-- only if it's still moving
									-- but what if it started moving a second time?
									local pair = movers[object]
									if pair and pair[2] == ticket then
										stop(object)
									end
								end, object)
						end
					end
				end
			end
		end
	end)
end

if minetest.settings:get("enable_item_drops") == "true" then
	local old_handle_node_drops = minetest.handle_node_drops

	local function new_handle_node_drops(pos, drops, digger)
		if digger and digger.is_fake_player then
			return old_handle_node_drops(pos, drops, digger)
		end
		local inv
		-- the digger might be a node, like a constructor
		if minetest.settings:get_bool("creative_mode") and digger and digger:is_player() then
			inv = digger:get_inventory()
		end
		for _, item in ipairs(drops) do
			local count, name
			if type(item) == "string" then
				count = 1
				name = item
			else
				count = item:get_count()
				name = item:get_name()
			end
			-- Only drop the item if not in creative, or if the item is not in creative inventory
			if not inv or not inv:contains_item("main", ItemStack(name)) then
				for i=1, count do
					local obj = minetest.add_item(pos, item)
					if obj ~= nil then
						local x = math.random(1, 5)
						if math.random(1, 2) == 1 then
							x = -x
						end
						local z = math.random(1, 5)
						if math.random(1, 2) == 1 then
							z = -z
						end
						-- hurl it out into space at a random velocity
						-- (still falling though)
					obj:set_velocity({x=1/x, y=obj:get_velocity().y, z=1/z})
					end
				end
			end
		end
		-- the items have been dropped. Don't use builtin/item.lua or it could put the items
		-- into an inventory! (see quarry)
		-- return old_handle_node_drops(pos, drops, digger)
	end

	local function checkSetting(pos, drops, digger)
		if minetest.settings:get("enable_item_drops") == "true" then
			return new_handle_node_drops(pos, drops, digger)
		else
			return old_handle_node_drops(pos, drops, digger)
		end
	end
	minetest.handle_node_drops = checkSetting

end

function minetest.item_drop(itemstack, dropper, pos)
	if dropper.get_player_name then
		local v = dropper:get_look_dir()
		local p = {x=pos.x+v.x, y=pos.y+1.5+v.y, z=pos.z+v.z}
		local r
		if dropper:get_player_control().sneak then
			r = itemstack
			itemstack = itemstack:to_table()
			itemstack.count = 1
			itemstack = ItemStack(itemstack)
			r:take_item()
		else
			r = ItemStack("")
		end
		minetest.sound_play("item_drop", {
			pos=pos,
			gain = 1.0,
			max_hear_distance = 32,
		}, true)
		local obj = minetest.add_item(p, itemstack)
		if obj then
			v.x = v.x*2
			v.y = v.y*2 + 1
			v.z = v.z*2
			obj:set_velocity(v)
			obj:get_luaentity().dropped_by = dropper:get_player_name()
		end
		return r
	else
		minetest.add_item(pos, itemstack)
		return ItemStack("")
	end
end
