local select_item = {}

local S = minetest.get_translator("lzr_editor")

-- Cache for result of item filters
local player_filter_results = {}
local player_compares = {}
local player_maxpage = {}

select_item.reset_player_info = function(playername)
	player_filter_results[playername] = nil
	player_compares[playername] = nil
	player_maxpage[playername] = nil
end

local check_item = function(itemstring, filter)
	local itemdef = minetest.registered_items[itemstring]
	if itemstring == "" or itemstring == "unknown" or itemstring == "ignore" or itemdef == nil then
		return
	end
	if type(filter) == "function" then
		if filter(itemstring) ~= true then
			return false
		end
	end

	return true
end

local boundary_node_filter = function(itemstring)
	if itemstring == "air" or itemstring == "lzr_core:barrier" or itemstring == "lzr_core:rain_membrane" or itemstring == "lzr_core:water_source_barrier" then
		return true
	end
	local def = minetest.registered_nodes[itemstring]
	if not def then
		return false
	elseif (def.node_box and def.node_box.type ~= "regular") or (def.drawtype == "plantlike" or def.drawtype == "torchlike"
			or def.drawtype == "plantlike_rooted" or def.drawtype == "signlike") then
		return false
	elseif minetest.get_item_group(itemstring, "not_in_creative_inventory") ~= 0 or
		minetest.get_item_group(itemstring, "takable") ~= 0 or
		minetest.get_item_group(itemstring, "chest") ~= 0 or
		minetest.get_item_group(itemstring, "teleporter") ~= 0 or
		minetest.get_item_group(itemstring, "laser_destroys") ~= 0 or
		minetest.get_item_group(itemstring, "attached_node") ~= 0 or
		minetest.get_item_group(itemstring, "laser_block") ~= 0 or
		minetest.get_item_group(itemstring, "punchdig") ~= 0 or
		minetest.get_item_group(itemstring, "cracked") ~= 0 or
		minetest.get_item_group(itemstring, "spawner") ~= 0 or
		minetest.get_item_group(itemstring, "lever") ~= 0 or
		minetest.get_item_group(itemstring, "cobweb") ~= 0 then
		return false
	end
	return true
end

local boundary_node_compare = function(t1, t2)
	local t1d = minetest.registered_items[t1].description
	local t2d = minetest.registered_items[t2].description
	local group_order = {
		"barrier",
		"wood",
		"stone",
		"ground",
		"tree",
		"fruit",
		"leaves",
		"thatch",
		"cloth",
		"lightbox",
		"glass",
		"furniture",
		"crate",
		"barrel",
		"water",
	}

	if (t1d ~= "" and t2d == "") then
		return false
	elseif (t1d == "" and t2d ~= "") then
		return true
	elseif (t1 == "air" and t2 ~= "air") then
		return false
	elseif (t1 ~= "air" and t2 == "air") then
		return true
	elseif (t1 ~= "lzr_core:barrier" and t2 == "lzr_core:barrier") then
		return false
	elseif (t1 == "lzr_core:barrier" and t2 ~= "lzr_core:barrier") then
		return true
	elseif (t1 ~= "lzr_core:rain_membrane" and t2 == "lzr_core:rain_membrane") then
		return false
	elseif (t1 == "lzr_core:rain_membrane" and t2 ~= "lzr_core:rain_membrane") then
		return true
	elseif (t1 ~= "lzr_core:water_source_barrier" and t2 == "lzr_core:water_source_barrier") then
		return false
	elseif (t1 == "lzr_core:water_source_barrier" and t2 ~= "lzr_core:water_source_barrier") then
		return true
	end
	for g=1, #group_order do
		local groupname = group_order[g]
		local t1g = minetest.get_item_group(t1, groupname)
		local t2g = minetest.get_item_group(t2, groupname)
		if (t1g == 0 and t2g ~= 0) then
			return false
		elseif (t1g ~= 0 and t2g == 0) then
			return true
		end
	end
	return t1 < t2
end

local get_items = function(filter, compare)
	local it = {}
	for itemstring, itemdef in pairs(minetest.registered_items) do
		if check_item(itemstring, filter) then
			table.insert(it, {itemstring=itemstring, itemdef=itemdef})
		end
	end
	local internal_compare
	if not compare then
		-- Default sorting: Move description-less items and
		-- items with not_in_creative_inventory=1 to the end, then sort by itemstring.
		internal_compare = function(t1, t2)
			local t1d = minetest.registered_items[t1.itemstring].description
			local t2d = minetest.registered_items[t2.itemstring].description
			local t1g = minetest.get_item_group(t1.itemstring, "not_in_creative_inventory")
			local t2g = minetest.get_item_group(t2.itemstring, "not_in_creative_inventory")
			if (t1d == "" and t2d ~= "") then
				return false
			elseif (t1d ~= "" and t2d == "") then
				return true
			end
			if (t1g == 1 and t2g == 0) then
				return false
			elseif (t1g == 0 and t2g == 1) then
				return true
			end
			return t1.itemstring < t2.itemstring
		end
	else
		internal_compare = function(t1, t2)
			return compare(t1.itemstring, t2.itemstring)
		end
	end
	table.sort(it, internal_compare)
	return it
end

local xsize_norm = 12
local ysize_norm = 9

-- Opens the item selection dialog for player with the given filter function at page.
-- The dialog has unique identifier dialogname.
-- Returns: Number of items it displays.
select_item.show_dialog_page = function(playername, dialogname, page)
	if page > 1 then
		if not player_maxpage[playername] then
			minetest.log("error", "[lzr_editor] player_maxpage not set!")
			return 0
		end
		if page > player_maxpage[playername] then
			page = player_maxpage[playername]
		end
	end
	local items
	local filter = boundary_node_filter
	local compare = boundary_node_compare
	if player_filter_results[playername] == nil then
		items = get_items(filter, compare)
		player_filter_results[playername] = items
	end
	items = player_filter_results[playername]
	local xsize, ysize, total_pages
	if #items < xsize_norm * ysize_norm then
		xsize = xsize_norm
		ysize = math.ceil(#items / xsize)
		total_pages = 1
	else
		xsize = xsize_norm
		ysize = ysize_norm
		total_pages = math.ceil(#items / (xsize * ysize))
	end
	local bg = ""
	if #items == 0 then
		local form = "size[6,2]"..
				bg ..
				"label[0,0;"..minetest.formspec_escape(S("There are no nodes to choose from.")).."]"..
				"button_exit[0,1;2,1;cancel;"..minetest.formspec_escape(S("OK")).."]"
		minetest.show_formspec(playername, "lzr_editor:select_item_page1", form)
		return #items
	end
	local form = "size["..xsize..","..(ysize+1).."]" .. bg
	local x = 0
	local y = 0.5
	if page == nil then page = 1 end
	local start = 1 + (page-1) * xsize * ysize
	player_maxpage[playername] = total_pages
	form = form .. "label[0,0;"..minetest.formspec_escape(S("Select a boundary node:")).."]"
	for i=start, #items do
		local itemstring = items[i].itemstring
		local itemdef = items[i].itemdef
		local name = "item_"..itemstring
		form = form .. "item_image_button["..x..","..y..";1,1;"..itemstring..";"..name..";]"
		if itemdef.description == nil or itemdef.description == "" then
			form = form .. "tooltip["..name..";"..itemstring.."]"
		end

		x = x + 1
		if x >= xsize then
			x = 0
			y = y + 1
			if y >= ysize then
				break
			end
		end
	end
	local ynav = (ysize + 0.5)
	if total_pages > 1 then
		form = form .. "button[0,"..ynav..";1,1;previous;<]"
		form = form .. "button[1,"..ynav..";1,1;next;>]"
		form = form .. "label[2,"..ynav..";"..minetest.formspec_escape(S("Page @1/@2", page, total_pages)).."]"
	end
	form = form .. "button_exit["..(xsize-2)..","..ynav..";2,1;cancel;"..minetest.formspec_escape(S("Cancel")).."]"
	minetest.show_formspec(playername, "lzr_editor:select_item_page"..page.."%%"..dialogname, form)
	return #items
end

select_item.show_dialog = function(playername, dialogname)
	select_item.show_dialog_page(playername, dialogname, 1)
end

minetest.register_on_leaveplayer(function(player)
	select_item.reset_player_info(player:get_player_name())
end)

return select_item
