local fake_inventory = {}
local identifier = "fakelib:inventory"
local check, secure_table = ...

-- API functions
----------------------------------------

function fakelib.is_inventory(x)
	if type(x) == "userdata" and x.get_lists then
		return true
	elseif type(x) == "table" and getmetatable(x) == identifier then
		return true
	end
	return false
end

function fakelib.create_inventory(sizes)
	local lists = {}
	if type(sizes) == "table" then
		for listname, size in pairs(sizes) do
			if type(listname) == "string" and type(size) == "number" and size > 0 then
				local list = {}
				for i = 1, size do
					list[i] = ItemStack()
				end
				lists[listname] = list
			end
		end
	end
	return secure_table({ lists = lists }, fake_inventory, identifier)
end

-- Helper functions
----------------------------------------

local function copy_list(list)
	local copy = {}
	for i = 1, #list do
		copy[i] = ItemStack(list[i])
	end
	return copy
end

local function stack_matches(a, b, match_meta)
	if a:get_name() ~= b:get_name() then
		return false
	end
	if match_meta then
		if a:get_wear() ~= b:get_wear() then
			return false
		end
		return a:get_meta():equals(b:get_meta())
	end
	return true
end

-- Inventory functions
----------------------------------------

function fake_inventory:is_empty(listname)
	check(1, listname, "string", "number")
	local list = self.lists[tostring(listname)]
	if not list or #list == 0 then
		return true
	end
	for _, stack in ipairs(list) do
		if not stack:is_empty() then
			return false
		end
	end
	return true
end

function fake_inventory:get_size(listname)
	check(1, listname, "string", "number")
	local list = self.lists[tostring(listname)]
	return list and #list or 0
end

function fake_inventory:set_size(listname, size)
	check(1, listname, "string", "number")
	check(2, size, "number")
	listname = tostring(listname)
	if size ~= size or size < 0 then
		return false
	end
	size = math.floor(size)
	if size == 0 then
		self.lists[listname] = nil
		return true
	end
	local list = self.lists[listname] or {}
	if #list < size then
		for i = #list + 1, size do
			list[i] = ItemStack()
		end
	elseif #list > size then
		for i = size + 1, #list do
			list[i] = nil
		end
	end
	self.lists[listname] = list
	return true
end

function fake_inventory:get_width(listname)
	check(1, listname, "string", "number")
	local list = self.lists[tostring(listname)]
	return list and list.width or 0
end

function fake_inventory:set_width(listname, width)
	check(1, listname, "string", "number")
	check(2, width, "number")
	local list = self.lists[tostring(listname)]
	if not list or width ~= width or width < 0 then
		return false
	end
	width = math.floor(width)
	list.width = width > 0 and width or nil
	return true
end

function fake_inventory:get_stack(listname, i)
	check(1, listname, "string", "number")
	check(2, i, "number")
	i = math.floor(i)
	local list = self.lists[tostring(listname)]
	if not list or not list[i] then
		return ItemStack()
	end
	return ItemStack(list[i])
end

function fake_inventory:set_stack(listname, i, stack)
	check(1, listname, "string", "number")
	check(2, i, "number")
	stack = ItemStack(stack)
	i = math.floor(i)
	local list = self.lists[tostring(listname)]
	if not list or not list[i] or stack:is_empty() then
		return false
	end
	list[i] = stack
	return true
end

function fake_inventory:get_list(listname)
	check(1, listname, "string", "number")
	local list = self.lists[tostring(listname)]
	return list and copy_list(list) or nil
end

function fake_inventory:set_list(listname, list)
	check(1, listname, "string", "number")
	listname = tostring(listname)
	if list == nil then
		self.lists[listname] = nil
		return
	end
	check(2, list, "table")
	local new_list, size = {}, 0
	for i, s in pairs(list) do
		check(4, i, "number")
		if i > size then
			size = i
		end
		new_list[i] = ItemStack(s)
	end
	for i = 1, size do
		if not new_list[i] then
			new_list[i] = ItemStack()
		end
	end
	self.lists[listname] = new_list
end

function fake_inventory:get_lists()
	local lists = {}
	for listname, list in pairs(self.lists) do
		lists[listname] = copy_list(list)
	end
	return lists
end

function fake_inventory:set_lists(lists)
	check(1, lists, "table")
	local new_lists = {}
	for listname, list in pairs(lists) do
		check(3, listname, "string", "number")
		check(3, list, "table")
		listname = tostring(listname)
		local new_list, size = {}, 0
		for i, s in pairs(list) do
			check(5, i, "number")
			if i > size then
				size = i
			end
			new_list[i] = ItemStack(s)
		end
		for i = 1, size do
			if not new_list[i] then
				new_list[i] = ItemStack()
			end
		end
		new_lists[listname] = new_list
	end
	self.lists = new_lists
end

function fake_inventory:add_item(listname, stack)
	check(1, listname, "string", "number")
	stack = ItemStack(stack)
	local list = self.lists[tostring(listname)]
	if not list or #list == 0 or stack:is_empty() then
		return stack
	end
	local empty = {}
	for _, s in ipairs(list) do
		if s:is_empty() then
			table.insert(empty, s)
		else
			stack = s:add_item(stack)
			if stack:is_empty() then
				return stack
			end
		end
	end
	for _, s in ipairs(empty) do
		stack = s:add_item(stack)
		if stack:is_empty() then
			return stack
		end
	end
	return stack
end

function fake_inventory:room_for_item(listname, stack)
	check(1, listname, "string", "number")
	stack = ItemStack(stack)
	local list = self.lists[tostring(listname)]
	if not list or #list == 0 or stack:is_empty() then
		return false
	end
	for _, s in ipairs(copy_list(list)) do
		stack = s:add_item(stack)
		if stack:is_empty() then
			return true
		end
	end
	return false
end

function fake_inventory:contains_item(listname, stack, match_meta)
	check(1, listname, "string", "number")
	stack = ItemStack(stack)
	local list = self.lists[tostring(listname)]
	if not list or stack:is_empty() or stack:is_empty() then
		return false
	end
	local count = stack:get_count()
	for _, s in ipairs(list) do
		if stack_matches(stack, s, match_meta) then
			count = count - s:get_count()
			if count <= 0 then
				return true
			end
		end
	end
	return false
end

function fake_inventory:remove_item(listname, stack)
	check(1, listname, "string", "number")
	stack = ItemStack(stack)
	local list = self.lists[tostring(listname)]
	if not list or #list == 0 or stack:is_empty() then
		return ItemStack()
	end
	local name, remaining, removed = stack:get_name(), stack:get_count()
	for i = #list, 1, -1 do
		local s = list[i]
		if s:get_name() == name then
			s = s:take_item(remaining)
			remaining = remaining - s:get_count()
			if not removed then
				removed = s
			else
				removed:set_count(removed:get_count() + s:get_count())
			end
			if remaining == 0 then
				break
			end
		end
	end
	return removed or ItemStack()
end

function fake_inventory.get_location()
	return { type = "undefined" }
end
