
-- key: group name, value: table with key: item name, value: group value
aom_materials._groups = {}
-- key: full node name, value: table with key: item name, value: item definition
aom_materials._shapes = {}
-- key: full node name, value: true
aom_materials._has_materials = {}

-- collect all materials and keep track for easier lookup
core.register_on_mods_loaded(function()
	for iname, idef in pairs(core.registered_items) do
		for gname, gval in pairs(idef.groups or {}) do
			if string.sub(gname, 1, 9) == "material_" then
				if not aom_materials._groups[gname] then aom_materials._groups[gname] = {} end
				aom_materials._groups[gname][iname] = gval
				aom_materials._has_materials[iname] = true
			end
		end
		if idef._full_node_name then
			if not aom_materials._shapes[idef._full_node_name] then
				aom_materials._shapes[idef._full_node_name] = {}
			end
			aom_materials._shapes[idef._full_node_name][iname] = idef
		end
	end
end)

-- returns a list of item names (usually nodes) that have common materials with `stack`
function aom_materials.get_items_of_same_material(stack, full_node_shapes_only)
	local ret = {}
	local sdef = stack:get_definition()
	for gname, gval in pairs(sdef.groups or {}) do
		if string.sub(gname, 1, 9) == "material_" then
			for iname, val in pairs(aom_materials._groups[gname]) do
				local idef = core.registered_items[iname]
				if not (full_node_shapes_only and idef._full_node_name and (idef._full_node_name ~= iname)) then
					table.insert(ret, iname)
				end
			end
		end
	end
	return ret
end

-- returns a list of node names that are shape variations of eachother
function aom_materials.get_nodes_of_same_shape(stack)
	local ret = {}
	local idef = stack:get_definition()
	for gname, gval in pairs(idef.groups or {}) do
		if string.sub(gname, 1, 9) == "material_" then
			for iname, val in pairs(aom_materials._groups[gname]) do
				table.insert(ret, iname)
			end
		end
	end
	return ret
end

-- true if any `material_[name]` group is shared between stacks
function aom_materials.is_same_material(stack, other)
	other = ItemStack(other)
	local idef = stack:get_definition()
	for gname, gval in pairs(idef.groups or {}) do
		if string.sub(gname, 1, 9) == "material_" then
			if core.get_item_group(other:get_name(), gname) > 0 then
				return true
			end
		end
	end
	return false
end

-- true if e.g. cobble stair + cobble slab
function aom_materials.is_shape_of_node(stack, other)
	local idef = stack:get_definition()
	local other_def = other:get_definition()
	return (idef._full_node_name ~= nil) and (idef._full_node_name == other_def._full_node_name)
end

-- uses `count` items, based on the `itemstack` if enough of its materal is present, returns true if enough, false if not enough material
-- priority is same name, shape of same node, same material
function aom_materials.use_material_by_itemstack(inventory, itemstack, count, flags)
	flags = flags or {}
	local iname = itemstack:get_name()
	local lists = (
		(flags.list_name == nil) and inventory:get_lists() or
		{[flags.list_name] = inventory:get_list(flags.list_name) or {}}
	)
	local used_count = 0
	local changed_lists = {}
	if (not flags.no_name_check) then
		for list_name, list in pairs(lists) do
			for i, stack in ipairs(list) do
				itemstack = ItemStack(itemstack)
				stack = ItemStack(stack)
				if used_count >= count then break end
				if (stack:get_name() == itemstack:get_name()) then
					local stack_count = math.min(stack:get_count(), count - used_count)
					used_count = used_count + stack_count
					stack:set_count(stack:get_count() - stack_count)
					changed_lists[list_name] = list
				end
			end
		end
	end
	-- go through again for shapes if could not find enough items of same name
	if (used_count < count) and (not flags.no_shape_check)
	and (aom_materials._shapes[iname] ~= nil) then
		for list_name, list in pairs(lists) do
			for i, stack in ipairs(list) do
				if used_count >= count then break end
				if aom_materials.is_shape_of_node(stack, itemstack) then
					local stack_count = math.min(stack:get_count(), count - used_count)
					used_count = used_count + stack_count
					stack:set_count(stack:get_count() - stack_count)
					changed_lists[list_name] = list
				end
			end
		end
	end
	-- go through again if could not find enough items of same name
	if (used_count < count) and (not flags.no_material_check)
	and (aom_materials._has_materials[iname] == true) then
		for list_name, list in pairs(lists) do
			for i, stack in ipairs(list) do
				if used_count >= count then break end
				if aom_materials.is_same_material(stack, itemstack) then
					local stack_count = math.min(stack:get_count(), count - used_count)
					used_count = used_count + stack_count
					stack:set_count(stack:get_count() - stack_count)
					changed_lists[list_name] = list
				end
			end
		end
	end
	-- don't modify anything if is dry run
	if flags.is_dry_run then
		return used_count >= count
	end
	-- actually use items
	for list_name, list in pairs(changed_lists) do
		inventory:set_list(list_name, list)
	end
	return used_count >= count
end

-- adds this itemstack to the inventory as a generic material/shape and returns leftover itemstack
-- priority is same name, shape of same node, same material
function aom_materials.add_material_by_itemstack(inventory, itemstack, flags)
	flags = flags or {}
	itemstack = ItemStack(itemstack)
	local iname = itemstack:get_name()
	if itemstack:get_count() <= 0 then return itemstack end
	local old_stack = ItemStack(itemstack)
	local lists = (
		(flags.list_name == nil) and inventory:get_lists() or
		{[flags.list_name] = inventory:get_list(flags.list_name) or {}}
	)
	local changed_lists = {}
	if (not flags.no_name_check) then
		for list_name, list in pairs(lists) do
			for i, stack in ipairs(list) do
				stack = ItemStack(stack)
				if itemstack:get_count() <= 0 then break end
				if (stack:get_name() == itemstack:get_name()) then
					itemstack = stack:add_item(itemstack)
					list[i] = stack
					changed_lists[list_name] = list
				end
			end
		end
	end
	-- go through again for shapes if could not find enough items of same name
	if (itemstack:get_count() > 0) and (not flags.no_shape_check)
	and (aom_materials._shapes[iname] ~= nil) then
		for list_name, list in pairs(lists) do
			for i, stack in ipairs(list) do
				if aom_materials.is_shape_of_node(stack, itemstack) then
					itemstack:set_name(stack:get_name())
					itemstack = stack:add_item(itemstack)
					if itemstack:get_count() > 0 then
						itemstack:set_name(old_stack:get_name())
					end
					changed_lists[list_name] = list
				end
			end
		end
	end
	-- go through again if could not find enough items of same name
	if (itemstack:get_count() > 0) and (not flags.no_material_check)
	and (aom_materials._has_materials[iname] == true) then
		for list_name, list in pairs(lists) do
			for i, stack in ipairs(list) do
				if aom_materials.is_same_material(stack, itemstack) then
					itemstack:set_name(stack:get_name())
					itemstack = stack:add_item(itemstack)
					if itemstack:get_count() > 0 then
						itemstack:set_name(old_stack:get_name())
					end
					changed_lists[list_name] = list
				end
			end
		end
	end
	-- actually modify inventory
	for list_name, list in pairs(changed_lists) do
		inventory:set_list(list_name, list)
	end
	if itemstack:get_count() > 0 then
		itemstack = inventory:add_item(flags.list_name or "main", itemstack)
	end
	return itemstack
end


--[[
-- for testing purposes
core.register_chatcommand("g", {
    params = "",
    description = "",
    privs = {},
    func = function(name, param)
		local player = core.get_player_by_name(name)
		if not player then return end
		local count = tonumber(param) or 1
		core.log(tostring(count))
		local inventory = player:get_inventory()
		if count > 0 then
			aom_materials.add_material_by_itemstack(inventory, ItemStack("aom_stone:stone " .. count), {list_name = "main"})
		else
			aom_materials.use_material_by_itemstack(inventory, ItemStack("aom_stone:stone"), math.abs(count), {list_name = "main"})
		end
    end
})
--]]
