local lib = starlit.mod.lib
local B = {}
starlit.mod.building = B

B.path = {}
-- this maps stage IDs to tables of the following form
--[[ {
	part = {
		['starlit_building:pipe'] = 'myMod:stage3';
	};
	tool = {
		['starlit:screwdriver'] = 'myMod:otherThing_stage1';
		['starlit:saw'] = function(node, tool)
			minetest.replace_node(node, {name='myMod:stage1'})
			minetest.drop_item(node, 'starlit_building:pipe')
		end;
		['myMod:laserWrench'] = {
			allow = function(node, tool) ... end;
			handle = function(node, tool) ... end;
		};
	};
} ]]
-- it should only be written by special accessor functions!

B.begin = {part = {}, tool = {}}
-- this maps parts/tools to the new nodes they create
--[[
	part = {
		['starlit_building:concrete'] = 'starlit_building:stage_foundation';
		-- or function(...)
	};
	tool = {
		-- for consistency's sake -- can't quite imagine a valid use
		-- for this but im sure it'll bite me eventually if i leave it out
	};
]]

B.stage = lib.registry.mk 'starlit_building:stage'
-- a stage consists of a node definition and maps from possible materials
-- / tool usages to succeeding stages in the build tree
--[[
	starlit.mod.building.stage.link(id, {
		tex = { ... };
		box = { ... }; -- nodebox def, or
		mesh = '...'; -- mesh name
		begin = {
			part = {...};
			tool = {...};
		}
		build = {
			part = {
				['starlit_building:insulation'] = 'starlit_building:foundation_with_insulation';
			};
			tool = {
				['starlit_building:screwdriver'] = {id = 'air', drop = 'starlit_building:foundation'};
			};
		};
	})
]]

B.part = lib.registry.mk 'starlit_building:part'
-- a part is implemented as a special craftitem with the proper callbacks
-- to index the registries and place/replace nodes by reference to the
-- build tree.
--[[
	starlit.mod.building.part.link(id, {
		name = ''; -- display name
		desc = ''; -- display desc
		img = ''; -- display image
		mass = 0;
		fab = {}; -- (optional) auto-gen schematic
		rarity = 0;
	})
]]
B.part.foreach('starlit:partGen', {}, function(id, e)
	local props = {}
	if e.mass then
		table.insert(props, {title='Mass', desc=lib.math.siUI('g',e.mass), affinity='info'})
	end
	local rev, scmID
	if e.fab then
		scmID = string.format('%s_schematic', id)
		rev = {
			sw = scmID;
			complexity = e.complexity or 1;
		}
	end
	minetest.register_craftitem(id, {
		short_description = e.name;
		description = starlit.ui.tooltip {
			title = e.name;
			desc = e.desc;
			props = props;
		};
		stack_max = e.max or (e.mass and math.min(math.max(math.floor(500 / e.mass), 1), 500)) or 10;
		inventory_image = e.img;
		on_place = function(stack, luser, point)
			local node = minetest.get_node(point.under)
			local function tryBuild()
				local p = B.path[node.name]
				if not p then return nil end
				if (not p.part) or (not p.part[id]) then return nil end
				local n = p.part[id]
				local obj
				if type(n) == 'function' then
					obj = n(stack, node, point)
					if obj == nil then return nil end
				else
					obj = ItemStack(n)
					stack:take_item(1)
				end
				local pname = obj:get_name()
				local stg = B.stage.db[pname]
				node.name = pname
				minetest.swap_node(point.under, node)
				-- TODO make a noise
				if stg.onBuild then
					stg.onBuild(point.under, luser, stack)
				end
				return stack
			end

			local function tryBegin()
				local p = B.begin.part[stack:get_name()]
				if not p then return nil end
				if type(p) == 'function' then
					p = p(stack, node, point)
					if p == nil then return nil end
				else
					stack:take_item(1)
				end
				minetest.rotate_and_place(ItemStack(p), luser, point, true)
				-- TODO make a noise
				return stack
			end

			return tryBuild() or tryBegin() or stack
		end;
		_starlit = {
			mass = e.mass;
			reverseEngineer = rev;
			recover = e.recover or e.fab;
		};
	})
	if e.fab then
		starlit.item.sw.link(scmID, {
			kind = 'schematic';
			name = string.format('%s Schematic', e.name);
			size = e.size or 32e6;
			input = e.fab;
			output = id;
			cost = e.cost or {
				cycles = 4e9;
				ram = 1e9;
			};
			rarity = e.rarity or 0;
		})
	end

end)

function B.pathLink(from, kind, what, to)
	if not B.path[from] then
		B.path[from] = {part={}, tool={}}
	end
	local k = B.path[from][kind]
	assert(k[what] == nil, 'attempted to overwrite an existing build path')
	k[what] = to
end

function B.pathFind(from, kind, what)
	if not B.path[from] then return nil end
	return B.path[from][kind][what]
end

B.stage.foreach('starlit:stageGen', {}, function(id, e)
	local grp = e.groups and table.copy(e.groups) or {}
	grp.stage = 1
	local meta = {
		stage = id;
		recover = e.recover;
	}
	for k,v in pairs(e.meta or {}) do meta[k] = v end
	minetest.register_node(id, {
		description = 'Construction';
		drawtype = (e.box  and 'nodebox')
		        or (e.mesh and 'mesh')
		        or 'regular';
		paramtype  = e.paramtype or (e.box or e.mesh or e.light) and 'light' or nil;
		paramtype2 = e.paramtype2 or 'none';
		tiles = e.tex;
		node_box = e.box;
		mesh = e.mesh;
		groups = grp;
		_starlit = meta;
	})
	if e.begin then
		for _, kind in ipairs {'part', 'tool'} do
			for i, v in ipairs(e.begin[kind] or {}) do
				assert(B.begin[kind][v] == nil, 'attempted to overwrite buildpath beginning')
				B.begin[kind][v] = id
			end
		end
	end
	if e.build then
		for _, kind in ipairs {'part', 'tool'} do
			for what, to in pairs(e.build[kind] or {}) do
				B.pathLink(id, kind, what, to)
			end
		end
	end
end)


starlit.include 'parts'
starlit.include 'stages/arch'