-- in theory, minetest groups are supposed to allow us to
-- give consistent, cross-mod classes to items, and easily
-- detect whether items fit into a particular class. unfortunately,
-- they don't really work for this purpose because we often
-- need to attach additional data to items that are outside
-- of our control (and the default mod's authors are amazingly
-- lax in grouping items; for instance, diamonds and mese
-- crystals aren't even part of a 'gem' or 'crystal' group!)
-- this module allows us to consistently classify items, and
-- easily maintain complex hierarchies of subclasses. whether
-- an item belongs to a class can be determined by checking
-- its groups, consulting compat tables, calling a custom
-- predicate function (possibly to check for a _sorcery
-- defprop), or recursing through a list of subclasses.
-- this also means that matters of identity are all controlled
-- from a central location.
sorcery.itemclass = {
	classes = {
		-- gem/crystalline and metal/metallic differentiate
		-- between crafting materials (i.e. gems or ingots
		-- themselves) and items crafted from those materials.
		-- the former includes only crafting materials, the
		-- latter includes both.
		gem = {
			compat = 'gems';
			finagle = function(g) return {data = sorcery.data.gems[g.id]} end;
			groups = { 'gem', 'crystal'; };
			predicate = function(name)
				if minetest.get_item_group(name, 'sorcery_gem') ~= 0 
				or minetest.get_item_group(name, 'sorcery_shard') ~= 0 then
					return minetest.registered_items[name]._sorcery.material;
				end
			end;
		};
		crystalline = {
			subclass = {'gem'};
			predicate = function(name)
				local mat = sorcery.matreg.lookup[name]
				if mat and mat.gem then return mat end
			end;
		};
		grindable = {
			compat = 'grindables';
			subclass = {'metallic'};
			conform = {
				metallic = function(m)
					if m and m.data and m.data.parts and m.data.parts.powder then
						return {
							hardness = m.data.hardness;
							grindcost = 1;
							grindvalue = m.value or 1;
							powder = m.data.parts.powder;
						}
					end
				end;
			};
			predicate = function(name)
				local def = minetest.registered_items[name]._sorcery
				if not def then return nil end
				def = def.material
				if def and def.grindvalue then
					return {
						hardness = def.hardness or def.data.hardness;
						grindcost = def.grindcost or 1;
						grindvalue = def.grindvalue or def.value;
						powder = def.powder or def.data.parts.powder;
					}
				end
			end;
		};
		metal = {
			predicate = function(name)
				-- metallookup is a table of 'primary' metal
				-- items, like ingots, fragments, and powders
				return sorcery.data.metallookup[name]
			end;
		};
		metallic = {
			subclass = {'metal'};
			predicate = function(name)
				-- matreg is a registry binding crafted items,
				-- like armors and tools, to the material they
				-- are made out of. it's necessary because not
				-- all items we want to interact with have
				-- definitions under our control
				local mat = sorcery.matreg.lookup[name]
				if mat and mat.metal then return mat end
				local prop = minetest.registered_items[name]._sorcery
				if prop and prop.material and prop.material.metal then
					return prop.material
				end
			end;
		};
		material = {
			subclass = {'metallic','crystalline'};
		};
		ore = {
			groups = { 'ore' };
			compat = 'ore';
			predicate = function(name)
				-- maybe revise this at some point once sorcery is extricated
				-- from instant_ores and we have more control over the items
				-- we generate
				local orepfx = "stone_with_" -- }:<
				local barename = string.sub(name, string.find(name, ':') + 1)
				if string.sub(barename,1,string.len(orepfx)) == orepfx then
					local iname = string.sub(barename,string.len(orepfx) + 1)
					if sorcery.data.metals[iname] then
						return { metal = true, id = iname }
					elseif sorcery.data.gems[iname] then
						return { gem = true, id = iname }
					end
				end
			end;
		};
		fuel = {
			groups = {'fuel','flammable'};
			predicate = function(name)
				local c,di = minetest.get_craft_result {
					method = 'fuel';
					width = 1;
					items = { ItemStack(name) };
				}
				if c.time and c.time > 0 then
					return {
						burntime = c.time;
						leftover = di and di[1];
					}
				end
			end;
		};
		vessel = {
			compat = 'vessels';
			finagle = { type = 'vessel'; charge = 3; hold = 'liquid' };
			predicate = function(name)
				local item = minetest.registered_items[name]
				if item and item._sorcery and item._sorcery.container then
					local ct = item._sorcery.container
					if ct.type == 'vessel' then return sorcery.lib.tbl.merge({
						charge = 3;
					}, ct) end
				end
			end;
		};
		box = {
			compat = 'boxes';
			predicate = function(name)
				local item = minetest.registered_items[name]
				if item._sorcery and item._sorcery.container then
					local ct = item._sorcery.container
					if ct.type == 'box' then return sorcery.lib.tbl.merge({
						charge = 8;
					}, ct) end
				end
			end;
		};
		bucket = {
			compat = 'buckets';
			finagle = { type = 'bucket', hold = 'liquid', charge = 3 * 3, empty = 'bucket:bucket_empty'};

			predicate = function(name)
				local item = minetest.registered_items[name]
				if item._sorcery and item._sorcery.container then
					local ct = item._sorcery.container
					if ct.type == 'bucket' then return sorcery.lib.tbl.merge({
						charge = 3 * 3;
					}, ct) end
				end
			end;
		};
		container = {
			compat = 'containers';
			subclass = {'vessel', 'box', 'bucket'}
		};
	};
	get = function(name,class)
		local c = sorcery.itemclass.classes[class]
		local o
		if not c then return false end
		if type(name) ~= 'string' then name = name:get_name() end

		if c.predicate then 
			o = c.predicate(name)
			if o then return o end
		end

		if c.compat then
			o = sorcery.data.compat[c.compat][name]
			if o then
				if c.finagle then
					if type(c.finagle) == 'function' then
						return sorcery.lib.tbl.merge(o, c.finagle(o))
					elseif type(o) == 'table' then
						return sorcery.lib.tbl.merge(o, c.finagle)
					end
				end
				return o
			end
		end

		if c.subclass then
			for _,s in pairs(c.subclass) do
				o = sorcery.itemclass.get(name,s)
				if o then
					if c.conform and c.conform[s] then
						return c.conform[s](o)
					else return o end
				end
			end
		end

		if c.groups then
			for _,g in pairs(c.groups) do
				o = minetest.get_item_group(name,g) 
				if o > 0 then return { kind = o } end
			end
			o = nil
		end

		return false
	end;
}
