--[[ The item list dialog can only be displayed from within a node formspec.
To open the item list dialog, add a do_file :

local show_item_list = dofile(
	minetest.get_modpath(minetest.get_current_modname())..'/itemlist.lua')

...

-- From your on_receive_field, call :
show_item_list(player, title, item_list, pos_context)

player: player name or object
title: title to be displayed at the top of the dialog
item_list: a list of item names
pos_context: position of the context node (node that openned the formspec)
]]--

-- Formspec style

local style={
	linesize = 0.9, -- Line height
	colsize = 4,    -- Column width
	lines = 8,      -- Number of lines of items displayed
	cols = 3,       -- Number of columns of items displayed
}

local modname = minetest.get_current_modname()

local contexts = {}

local function get_player_name(player)
	if type(player) == 'string' then
		return player
	end
	if minetest.is_player(player) then
		return player:get_player_name()
	end
	minetest.log('warning',	'['..modname..'] get_player_name could not identify player.')
end

minetest.register_on_leaveplayer(function(player)
	local playername = get_player_name(player)
	if playername then
		contexts[playername] = nil
	end
end)

local function get_context(player)
	local playername = get_player_name(player)
	if playername then
		if not contexts[playername] then
			contexts[playername] = { playername = playername }
		end
		return contexts[playername]
	end
end

-- Show node formspec functions
local function show_node_formspec(playername, pos)
	local meta = minetest.get_meta(pos)

	-- Decontextualize formspec
	local fs = meta:get_string('formspec')

	-- Change context and currrent_name references to nodemeta references
	local nodemeta = string.format("nodemeta:%i,%i,%i", pos.x, pos.y ,pos.z)
	fs = fs:gsub("current_name", nodemeta)
	fs = fs:gsub("context", nodemeta)

	-- Change all ${} to their corresponding metadata values
	local s, e
	repeat
		s, e = fs:find('%${.*}')
		if s and e then
			fs = fs:sub(1, s-1)..
				minetest.formspec_escape(meta:get_string(fs:sub(s+2,e-1)))..
				fs:sub(e+1)
		end
	until s == nil

	-- Find node on_receive_fields
	local ndef = minetest.registered_nodes[minetest.get_node(pos).name]

	local context = get_context(playername)
	context.node_pos = pos

	if ndef and ndef.on_receive_fields then
		context.on_receive_fields = ndef.on_receive_fields
	end

	-- Show formspec
	minetest.show_formspec(playername, modname..':context_formspec', fs)
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname == modname..':context_formspec' then
		local context = get_context(player)
		if context == nil then
			return
		end

		if context.on_receive_fields then
			context.on_receive_fields(context.node_pos, '', fields, player)
		end
	end
end)

-- Specific functions

local function get_nb_of_pages(context)
	return math.ceil((#context.items + 1) / (style.lines * style.cols))
end

local function item_list_prepare(item_list)
	local list = {}
	for _, name in ipairs(item_list) do
		local ndef = minetest.registered_items[name]
		if ndef then
			list[#list+1] = ndef
		end
	end
	table.sort(list, function(a,b) return a.description < b.description end)
	return list
end

local function show_item_list_formspec(player)
	local context = get_context(player)
	if context == nil then
		return
	end

	local parts = {}
	parts[#parts + 1] = string.format(
		"size[%f,%f]%s%s%slabel[1,0;%s]button_exit[%f,%f;2,1;close;Close]",
		(style.colsize * style.cols), (style.linesize * style.lines + 1.3),
		default.gui_bg, default.gui_bg_img, default.gui_slots, context.title,
		(style.colsize * style.cols - 2), (style.linesize * style.lines + 0.7))

	local nb_of_pages = get_nb_of_pages(context)
	local right = style.colsize * style.cols

	if nb_of_pages > 1 then
		parts[#parts + 1] = string.format("label[%f,0;Page %d of %d]",
			(right - 4), context.page, nb_of_pages)
	end

	if context.page > 1 then
		parts[#parts + 1] = string.format("button[%f,-0.2;1,1;prev;<]", (right - 2))
	end

	if context.page < nb_of_pages then
		parts[#parts + 1] = string.format("button[%f,-0.2;1,1;next;>]", (right - 1))
	end

	local index = (style.lines * style.cols) * (context.page - 1)

	for col = 1, style.cols do
		local x = (col - 1) * style.colsize
		for line = 1, style.lines do
			index = index + 1
			local y = line * style.linesize
			if context.items[index] then
				parts[#parts + 1] = string.format(
					"item_image[%f,%f;1,1;%s]label[%f,%f;%s]",
					x, y - 0.2, context.items[index].name, x + 1, y,
					(context.items[index].description or context.items[index].name))
			end
		end
	end
	minetest.show_formspec(
		context.playername, modname..':item_list', table.concat(parts))
end

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname ~= modname..':item_list' then
		return
	end

	local context = get_context(player)
	if context == nil then
		return
	end

	if fields.next and context.page < get_nb_of_pages(context) then
		context.page = context.page + 1
		show_item_list_formspec(context.playername)
	end
	if fields.prev and context.page > 1 then
		context.page = context.page - 1
		show_item_list_formspec(context.playername)
	end
	if fields.quit == 'true' then
		if context.node_pos then
			-- Using after to avoid the "double close" bug
			minetest.after(0, show_node_formspec, get_player_name(player),
				context.node_pos)
		end
	end
end)

-- Only exposed function - Entry point

local function show_item_list(player, title, item_list, pos_context)
	local context = get_context(player)
	context.items = item_list_prepare(item_list)
	context.title = title
	context.node_pos = pos_context
	context.page = 1
	show_item_list_formspec(player)
end

return show_item_list
