-- LUALOCALS < ---------------------------------------------------------
local minetest, nodecore, pairs, type
    = minetest, nodecore, pairs, type
-- LUALOCALS > ---------------------------------------------------------

local match_skip = {
	name = true,
	param2 = true,
	param = true,
	groups = true,
	stack = true,
	count = true,
	excess = true,
	wear = true,
	stacked = true,
	any = true,
	empty = true,
	stackany = true
}

function nodecore.match(thing, crit)
	if not thing then return end

	if type(crit) == "string" then crit = {name = crit} end

	if crit.any then
		for _, v in pairs(crit.any) do
			local found = nodecore.match(thing, v)
			if found then return found end
		end
		return
	end

	-- Allow matches on stacks inside any node, not only
	-- bare stack nodes.
	if crit.stackany then
		local subcrit = nodecore.underride({}, crit)
		subcrit.stackany = nil
		if nodecore.match(thing, subcrit) then return thing end
	end

	thing.count = thing.count or 1

	thing = nodecore.underride({}, thing)
	if thing.stack then
		thing.name = thing.stack:get_name()
		thing.count = thing.stack:get_count()
		thing.wear = thing.stack:get_wear()
		thing.stacked = true
	end
	if not thing.name then
		thing = nodecore.underride(thing, minetest.get_node(thing))
	end
	local def = minetest.registered_items[thing.name]
	if crit.stackany or (not thing.stacked) and def and def.groups
	and def.groups.is_stack_only then
		local stack = thing.x and thing.y and thing.z and nodecore.stack_get(thing)
		if stack and not stack:is_empty() then
			thing.name = stack:get_name()
			def = minetest.registered_items[thing.name]
			thing.count = stack:get_count()
			thing.wear = stack:get_wear()
		end
		thing.stacked = true
	end
	if crit.stacked and not thing.stacked then return end
	if crit.stacked == false and thing.stacked then return end

	thing.name = thing.name and minetest.registered_aliases[thing.name] or thing.name

	if crit.name and thing.name ~= crit.name then return end
	if crit.param2 and thing.param2 ~= crit.param2 then return end
	if crit.param and thing.param ~= crit.param then return end
	if crit.count and thing.count < crit.count then return end
	if crit.count and (not crit.excess) and thing.count > crit.count then return end
	if crit.count == nil and thing.count ~= 1 then return end
	if crit.wear then
		if crit.wear < 1 then crit.wear = crit.wear * 65535 end
		if thing.wear and (thing.wear > crit.wear) then return end
	end

	if crit.groups then
		if (not def) or (not def.groups) then return end
		for k, v in pairs(crit.groups) do
			if v == true then
				if (not def.groups[k]) or def.groups[k] == 0 then return end
			elseif v == false then
				if def.groups[k] and def.groups[k] ~= 0 then return end
			else
				if def.groups[k] ~= v then return end
			end
		end
	end
	for k, v in pairs(crit) do
		if not match_skip[k] then
			if not def or def[k] ~= v then return end
		end
	end

	-- Never match on a thing that also has a stack inside it, e.g. crafts on
	-- shelfs/forms that are full.
	if crit.empty and not (thing.stacked or nodecore.stack_get(thing)
		:is_empty()) then return end

	return thing
end
