-- LUALOCALS < ---------------------------------------------------------
local ItemStack, addgroups, get_mod_api, minetest, pairs, piredo_map,
      piredo_player, posrel, startswith, string, vector
    = ItemStack, addgroups, get_mod_api, minetest, pairs, piredo_map,
      piredo_player, posrel, startswith, string, vector
local string_format, string_gsub, string_sub
    = string.format, string.gsub, string.sub
-- LUALOCALS > ---------------------------------------------------------

local api = get_mod_api()

-- on_apply_itemgroup -> either left or right click
-- on_whack_itemgroup -> left click
-- on_give_itemgroup -> right-click

local function tryinvoke(pointed, node, stack, user, prefix)
	node = node or minetest.get_node_or_nil(pointed.under)
	local def = node and minetest.registered_nodes[node.name]
	for k, v in pairs(def or {}) do
		if startswith(k, prefix) and minetest.get_item_group(
			stack:get_name(), string_sub(k, #prefix + 1)) > 0 then
			minetest.log("action", string_format(
					"player applies %q to %q at %s",
					k, node.name,
					minetest.pos_to_string(pointed.under)))
			return v(pointed.under, node, stack, user, pointed)
		end
	end
	return false
end

local useitem = api.ratelimit(function(stack, user, pointed)
		piredo_player.player_hand_movement(user)
		if not pointed.under then return end

		local node = minetest.get_node_or_nil(pointed.under)

		local invoked = tryinvoke(pointed, node, stack, user, "on_whack_")
		if invoked ~= false then return invoked end
		invoked = tryinvoke(pointed, node, stack, user, "on_apply_")
		if invoked ~= false then return invoked end

		api.ratelimitskip()

		local def = node and minetest.registered_nodes[node.name]
		local cb = def and def.on_punch
		if cb then
			minetest.log("action", string_format("player punches %q with %q at %s",
					node.name, stack:get_name(), minetest.pos_to_string(pointed.under)))
			cb(pointed.under, node, user, pointed)
			return
		end

		minetest.log("action", string_format("player swings %q at %q at %s",
				stack:get_name(), node.name, minetest.pos_to_string(pointed.under)))
	end)

local function checkbarriers(pos, player)
	local start = player:get_pos()
	start.y = start.y + player:get_properties().eye_height
	local diff = vector.subtract(pos, start)
	local len = vector.length(diff)
	if len < 0.5 then return end
	local dir = vector.multiply(diff, 1 / len)
	local prev = start
	for i = 0, len, 0.1 do
		local p = vector.round(vector.add(start, vector.multiply(dir, i)))
		if not vector.equals(prev, p) then
			local node = minetest.get_node(p)
			local def = minetest.registered_nodes[node.name]
			if def and def.walkable and def.drawtype == "airlike"
			and not def.collision_box then
				return true
			end
		end
	end
end

local pickup = api.ratelimit(function(pos, node, player)
		-- Cannot pickup "through" invisible barriers
		-- (fix lake crown maze puzzle)
		if checkbarriers(pos, player) then
			api.ratelimitskip()
			return
		end

		if not piredo_map.get_current_room() then
			api.ratelimitskip()
			api.digsound(pos, node)
			return
		end

		local inv = player:get_inventory()
		local stack = ItemStack(node.name)
		stack:get_meta():from_table(minetest.get_meta(pos):to_table())
		if not inv:room_for_item("main", stack) then
			api.ratelimitskip()
			return
		end
		player:get_inventory():add_item("main", stack)
		minetest.remove_node(pos)
		api.pickupeffect(pos, node)
	end)

addgroups("piredo_terrain:xdecor__table", "solid_top")
addgroups("piredo_terrain:luxury_decor__bright_wall_wooden_shelf", "solid_top")
addgroups("piredo_terrain:luxury_decor__dark_wall_wooden_shelf", "solid_top")
addgroups("piredo_terrain:luxury_decor__simple_wooden_table", "solid_top")

local solid_drawtypes = {
	normal = true,
	glasslike = true,
	glasslike_framed = true,
	glasslike_framed_optional = true,
	allfaces = true,
	allfaces_optional = true
}
local function placement(stack, placer, pointed, ...)
	piredo_player.player_hand_movement(placer)

	local node = minetest.get_node(pointed.under)
	local invoked = tryinvoke(pointed, node, stack, placer, "on_give_")
	if invoked ~= false then return invoked end
	invoked = tryinvoke(pointed, node, stack, placer, "on_apply_")
	if invoked ~= false then return invoked end

	-- Cannot drop items in first couple of rooms (would lose access)
	-- or in adjoining passages (too easy to lose track)
	local room, roompos, roommax = piredo_map.get_current_room()
	if not room
	or pointed.above.x < roompos.x or pointed.above.z < roompos.z
	or pointed.above.x > roommax.x or pointed.above.z > roommax.z
	then return end

	-- Need a valid node placement location.
	if not pointed or pointed.type ~= "node" then return end

	-- Torchlike items handle wall placement fine, but everything
	-- else is assumed to be affected by gravity, so place on
	-- the surface below.
	local idef = stack:get_definition()
	if not (idef and idef.drawtype == "torchlike") then
		pointed.under = posrel(pointed.above)(0, -1, 0)
	end

	-- Cannot place items "floating", must be on top of a
	-- walkable item, which also means not another collectable.
	-- This prevents goofy hovering items.
	local bnode = minetest.get_node(pointed.under)
	if minetest.get_item_group(bnode.name, "solid_top") < 1
	or (pointed.above.y - pointed.under.y < 1) then
		local def = minetest.registered_nodes[bnode.name]
		if not (def and def.walkable and def.pointable
			and solid_drawtypes[def.drawtype]) then return end
	end

	-- Disallow placing of "signlike" flat items above
	-- the player's eye height because it might make
	-- them too easy to lose.
	if placer and idef and idef.drawtype == "signlike" then
		local pos = placer:get_pos()
		if pointed.above.y > pos.y + placer:get_properties().eye_height
		then return end
	end

	-- Cannot drop an item "through" invisible barriers
	if checkbarriers(pointed.above, placer) then
		api.ratelimitskip()
		return
	end

	return minetest.item_place(stack, placer, pointed, ...)
end

for name, olddef in pairs(minetest.registered_items) do
	if olddef.groups and olddef.groups.collectable
	and olddef.groups.collectable > 0 then
		local basename = string_gsub(name, ".*:", "")
		local itemkey = string_gsub(string_gsub(basename, ".*__", ""),
			"_?block", "")
		local groups = {}
		for k, v in pairs(olddef.groups or {}) do groups[k] = v end
		groups[itemkey] = 1
		minetest.override_item(name, {
				on_punch = pickup,
				on_drop = function() end,
				on_use = olddef.on_use or useitem,
				on_place = placement,
				after_place_node = function(pos, _, stack)
					minetest.get_meta(pos):from_table(stack:get_meta():to_table())
				end,
				node_placement_prediction = "air",
				walkable = olddef.drawtype == "nodebox" or false,
				pointable = true,
				paramtype = "light",
				groups = groups
			})
	end
end
