local u = sorcery.lib
local m = u.marshal
local tunepack, tuneunpack = m.transcoder {
	code = m.t.u16; -- disambiguate just in case
	partner_x = m.t.s32;
	partner_y = m.t.s32;
	partner_z = m.t.s32;
	-- note that the meaning of 'partner' is a bit confusing. attuning
	-- is a directional relationship; that is, every item can be tuned
	-- to only one item (its partner), but more items can be tuned to
	-- it. while it will in most cases be reciprocal, this will not
	-- always be the case (e.g. with portals)
}

sorcery.attunement = {
	nodeid = function(node)
		local meta = minetest.get_meta(node)
		local id
		if meta:contains('sorcery:identity') then
			id = meta:get_string('sorcery:identity')
		else
			id = sorcery.lib.str.rand(16)
			meta:set_string('sorcery:identity', id)
		end
		return id
	end;
	decode = function(raw)
		local obj = tuneunpack(u.str.meta_dearmor(raw,true))
		return {
			code = obj.code;
			partner = {
				x = obj.partner_x;
				y = obj.partner_y;
				z = obj.partner_z;
			};
		}
	end;
	encode = function(tune)
		return u.str.meta_armor(tunepack {
			code = tune.code;
			partner_x = tune.partner.x;
			partner_y = tune.partner.y;
			partner_z = tune.partner.z;
		}, true)
	end;
	get = function(node)
		local raw = minetest.get_meta(node):get_string('sorcery:attune')
		if not raw then return nil end
		return sorcery.attunement.decode(raw)
	end;
	verify = function(node)
		local tune = sorcery.attunement.get(node)
		if not tune then return nil end
		local ptnr = sorcery.attunement.get(tune.partner)
		if not ptnr then return nil end

		local nodename =minetest.get_node(node).name
		local ptnrname =minetest.get_node(tune.partner).name

		local ndef = minetest.registered_nodes[nodename]
		local pdef = minetest.registered_nodes[ptnrname]

		if not (ndef and ndef._sorcery and pdef and pdef._sorcery and
			ndef._sorcery.attune and pdef._sorcery.attune) then return nil end

		ndef = ndef._sorcery.attune pdef = pdef._sorcery.attune

		if ndef.reciprocal and (not vector.equals(ptnr.partner, node)) then return nil end

		if tune.code == ptnr.code then
			return tune, ptnr
		end
		return nil
	end;
	set = function(node,tune)
		local meta = minetest.get_meta(node)
		meta:set_string('sorcery:attune', sorcery.attunement.encode(tune))
		meta:mark_as_private('sorcery:attune')
	end;
	pair = function(reciprocal,src,dest)
		local d, code = sorcery.attunement.get(src)
		local gdef = function(n)
			local m = minetest.registered_nodes[minetest.get_node(n).name]
			if m and m._sorcery and m._sorcery.attune then
				return m._sorcery.attune
			end
			return nil
		end

		local dsrc, dtgt = gdef(src), gdef(tgt)
		if not (dsrc and dtgt) then return nil end

		if not (dsrc.source and dtgt.target) then return nil end
		if dtgt.accepts ~= dsrc.class then return nil end
		
		if reciprocal or not d then
			code = math.random(0,65536)
			sorcery.attunement.set(src, {
				code = code;
				partner = dest;
			})
		else code = d.code end
		sorcery.attunement.set(dest, {
			code = code;
			partner = src;
		})
	end;
}
