
-- Table to define order of type sorting
local itemtypes = {
	node = 1,
	tool = 2,
	craft = 3,
	none = 4
}

-- Not so good compatibility workaround for older than 5.3 servers
local get_translated_string = minetest.get_translated_string or function(_, name) return name end

function technic.chests.sort_inv(inv, mode)
	local list = inv:get_list("main")
	if not list then return end
	local unique_items = {}
	if mode ~= 4 then
		local items = {}
		for _,stack in pairs(list) do
			if not stack:is_empty() then
				local name = stack:get_name()
				local wear = stack:get_wear()
				local meta = stack:get_meta():get_string("")
				local count = stack:get_count()
				local def = minetest.registered_items[name]
				local itemtype = (def and itemtypes[def.type]) and def.type or "none"
				local key = string.format("%s %05d %s", name, wear, meta)
				if not items[key] then
					items[key] = {
						stacks = {stack},
						wear = wear,
						count = count,
						itemtype = itemtype,
						key = key,
					}
				else
					items[key].count = items[key].count + count
					table.insert(items[key].stacks, stack)
				end
			end
		end
		for k,v in pairs(items) do
			table.insert(unique_items, v)
		end
	end
	if mode == 1 then
		-- Quantity
		table.sort(unique_items, function(a, b)
			if a.count ~= b.count then
				return a.count > b.count
			end
			return a.key < b.key
		end)
	elseif mode == 2 then
		-- Type
		table.sort(unique_items, function(a, b)
			if itemtypes[a.itemtype] ~= itemtypes[b.itemtype] then
				return itemtypes[a.itemtype] < itemtypes[b.itemtype]
			end
			return a.key < b.key
		end)
	elseif mode == 3 then
		-- Wear
		table.sort(unique_items, function(a, b)
			if a.itemtype == "tool" and b.itemtype == "tool" then
				if a.wear ~= b.wear then
					return a.wear < b.wear
				end
				return a.key < b.key
			elseif a.itemtype == "tool" or b.itemtype == "tool" then
				return a.itemtype == "tool"
			end
			return a.key < b.key
		end)
	elseif mode == 4 then
		-- Natural
		do -- Collect item stacks for sorting
			local function name(stack)
				return stack:get_meta():get("infotext")
					or stack:get_description()
					or stack:get_name()
			end
			local lookup = {}
			for _,stack in pairs(list) do
				local key = get_translated_string('', name(stack))
				if not lookup[key] then
					table.insert(unique_items, {
						stacks = {stack},
						key = key,
					})
					lookup[key] = #unique_items
				else
					table.insert(unique_items[lookup[key]].stacks, stack)
				end
			end
		end
		local function padnum(value)
			local dec, n = string.match(value, "(%.?)0*(.+)")
			return #dec > 0 and ("%.12f"):format(value) or ("%s%03d%s"):format(dec, #n, n)
		end
		table.sort(unique_items, function(a, b)
			local sort_a = ("%s%3d"):format(tostring(a.key):gsub("%.?%d+", padnum), #b.key)
			local sort_b = ("%s%3d"):format(tostring(b.key):gsub("%.?%d+", padnum), #a.key)
			return sort_a < sort_b
		end)
	else
		-- Item
		table.sort(unique_items, function(a, b)
			return a.key < b.key
		end)
	end
	inv:set_list("main", {})
	for _,item in ipairs(unique_items) do
		for _,stack in ipairs(item.stacks) do
			inv:add_item("main", stack)
		end
	end
end

function technic.chests.get_inv_items(inv)
	local list = inv:get_list("main")
	if not list then return {} end
	local keys = {}
	for _,stack in pairs(list) do
		if not stack:is_empty() then
			keys[stack:get_name()] = true
		end
	end
	local items = {}
	for k,_ in pairs(keys) do
		items[#items + 1] = k
	end
	return items
end

function technic.chests.move_inv(from_inv, to_inv, filter)
	local list = from_inv:get_list("main")
	if not list then return {} end
	local moved_items = {}
	for i,stack in ipairs(list) do
		if not stack:is_empty() then
			local move_stack = false
			local name = stack:get_name()
			if name == filter or not filter then
				move_stack = true
			elseif type(filter) == "table" then
				for _,k in pairs(filter) do
					if name == k then
						move_stack = true
						break
					end
				end
			end
			if move_stack then
				local leftover = to_inv:add_item("main", stack)
				if not leftover:is_empty() then
					from_inv:set_stack("main", i, leftover)
					stack:set_count(stack:get_count() - leftover:get_count())
				else
					from_inv:set_stack("main", i, "")
				end
				table.insert(moved_items, stack:to_table())
			end
		end
	end
	return moved_items
end

function technic.chests.log_inv_change(pos, name, change, items)
	local spos = minetest.pos_to_string(pos)
	if change == "move" then
		minetest.log("action", name.." moves "..items.." in chest at "..spos)
	elseif change == "put" then
		minetest.log("action", name.." puts "..items.." into chest at "..spos)
	elseif change == "take" then
		minetest.log("action", name.." takes "..items.." from chest at "..spos)
	end
end

function technic.chests.log_fast_move(pos, name, change, items)
	local spos = minetest.pos_to_string(pos)
	local itemlist = {}
	for _, stack in ipairs(items) do
		table.insert(itemlist, stack.name.." "..stack.count)
	end
	if change == "put" then
		minetest.log("action", string.format("%s puts items into chest at %s: %s",
			name, spos, table.concat(itemlist, ", ")))
	elseif change == "take" then
		minetest.log("action", string.format("%s takes items from chest at %s: %s",
			name, spos, table.concat(itemlist, ", ")))
	end
end
