-- [ʞ] sw.lua
--  ~ lexi hale <lexi@hale.su>
--  🄯 EUPL v1.2
--  ? 

-------------------------------
-- basic suit nano abilities --
-------------------------------
local function shredder(prop)
	local function fabToItemsAndCharges(fab)
		local elt
		if fab then
			elt = fab:elementalize()
		else
			elt = {}
		end
		local items,charges = {},{}
		if elt.element then
			for k,v in pairs(elt.element) do
				local forms =  starlit.world.material.element.db[k].form
				if forms.brick then
					local st = ItemStack {
						name = forms.brick;
						count = math.floor(v);
					}
					table.insert(items, st)
				else -- gas, liquid
					table.insert(charges, {id = k, mass = v})
				end
			end
		end
		return items, charges
	end

	return function(user, ctx)
		local function cleanup()
			user.action.prog.shred = nil
			if user.action.sfx.shred then
				minetest.sound_fade(user.action.sfx.shred, 1, 0)
				user.action.sfx.shred = nil
			end
			if user.action.fx.shred then
				user.action.fx.shred.abort()
			end
		end

		if user.action.tgt.type ~= 'node' then return end
		local what = user.action.tgt.under
		if what == nil or user.entity:get_pos():distance(what) > prop.range then
			cleanup()
			return false
		end
		local shredTime = 1.0
		local soundPitch = 1.0 -- TODO
		local pdraw = prop.powerDraw or 0

		if minetest.is_protected(what, user.entity:get_player_name()) then return end
		local node = minetest.get_node(what)
		local nd = minetest.registered_nodes[node.name]
		local elt, fab, vary
		if nd._starlit then
			fab = nd._starlit.recover or nd._starlit.fab
			vary = nd._starlit.recover_vary
		end
		if fab then
			if fab.flag then
				if fab.flag.unshreddable then
					cleanup()
					return false
					-- TODO error beep
				end
			end
			shredTime = fab.time and fab.time.shred or shredTime -- FIXME
			if fab.cost and fab.cost.shredPower then
				pdraw = pdraw * fab.cost.shredPower
			end
		end
		local maxW = user:getSuit():maxPowerUse()
		if maxW < pdraw then
			shredTime = shredTime * (pdraw/maxW)
			pdraw = maxW
		end
		if ctx.how.state == 'prog' then
			local pdx = pdraw * ctx.how.delta
			local p = user:suitDrawCurrent(pdx, ctx.how.delta, {kind='nano',label='Shredder'}, pdx)
			if p < pdx then
				cleanup()
				return false
			elseif not user.action.prog.shred then
				cleanup() -- kill danglers
				-- begin
				user.action.prog.shred = 0
				user.action.sfx.shred = minetest.sound_play('starlit-nano-shred', {
					object = user.entity;
					max_hear_distance = prop.range*2;
					loop = true;
					pitch = soundPitch;
				})
				user.action.fx.shred = starlit.fx.nano.shred(user, what, prop, shredTime, node)
			else
				user.action.prog.shred = user.action.prog.shred + ctx.how.delta or 0
			end
			--print('shred progress: ', user.action.prog.shred)
			if user.action.prog.shred >= shredTime then
				minetest.remove_node(what)
				minetest.check_for_falling(what)
				--print('shred complete')
				user:suitSound 'starlit-success'
				if fab then
					local vf = fab
					if vary then
						local rng = (starlit.world.seedbank+0xa891f62)[minetest.hash_node_position(what)]
						vf = vf + vary(rng, {})
					end
					local items, charges = fabToItemsAndCharges(vf)
					for i, it in ipairs(items) do user:give(it) end
					-- TODO give gasses, liquids
				end
				cleanup()
			end
		elseif ctx.how.state == 'halt' then
			cleanup()
		end
		return true
	end
end

starlit.item.sw.link('starlit_electronics:shred', {
	name = 'NanoShred';
	kind = 'suitPower', powerKind = 'active';
	desc = 'An open-source program used in its various forks and iterations all across human-inhabited space and beyond. Rumored to contain fragments of code stolen from the nanoware of the Greater Races by an elusive infoterrorist.';
	size = 500e3;
	cost = {
		cycles = 100e6;
		ram = 500e6;
	};
	run = shredder{range=3, powerDraw=200};
})

local function matterCompiler(desc)
	return {
		name = desc.name;
		kind = 'suitPower', powerKind = 'direct';
		desc = desc.desc;
		size = desc.size;
		cost = desc.cost;
		ui = 'starlit:compile-matter-component';
		bgProc = function(user, ctx, interval, runState)
			if runState.flags.compiled == true then return false end
			-- only so many nanides to go around
			runState.flags.compiled = true

			local conf = ctx.file.body.conf
			local job_t = starlit.store.compilerJob
			local job, jobSlot
			for i, v in ipairs(conf) do
				if v.key == 'job' then
					job = job_t.dec(v.value)
					jobSlot = i
					goto found
				end
			end

			::notfound:: do
				return
			end

			::found::
			local scm = starlit.item.sw.db[job.schematic]
			local stages = {
				{id = 'cyclesLeft', decr=ctx.comp.cycles};
				{id = 'powerLeft', decr=function(current)
					return ctx.drawCurrent(current, interval, 'compile')
				end};
				{id = 'timeLeft', decr=interval};
				{id = 'numinaLeft', decr=0}; -- FIXME
			}

			for i,v in ipairs(stages) do
				if job[v.id] > 0 then
					local decr
					if type(v.decr) == 'function'
						then decr = v.decr(job[v.id])
						else decr = v.decr
					end
					job[v.id] = math.max(0, job[v.id] - decr)
					goto incomplete
				end
			end

			::complete:: do
				table.remove(conf, jobSlot)
				user:give(scm.output)
				user:alarm(-2, 'item')
				goto done
			end

			::incomplete:: do
				conf[jobSlot].value = job_t.enc(job)
			end

			::done::
			ctx.saveConf()
		end;
	}
end

starlit.item.sw.link('starlit_electronics:compile_commune', matterCompiler {
	name = 'Compile Matter';
	desc = "A basic suit matter compiler program. It's rather slow, but it's been ruthlessly optimized for size- and memory-efficiency by some of the Commune's most fanatic coders, to the point where every Commune nanosuit can come with the program preinstalled.";
	size = 700e6;
	cost = {
		cycles = 4e9;
		ram = .3e9;
	};
})

starlit.item.sw.link('starlit_electronics:compile_block_commune', {
	name = 'Compile Block';
	kind = 'suitPower', powerKind = 'active';
	desc = "An advanced suit matter compiler program, capable of printing complete devices and structure parts directly into the world.";
	size = 500e6;
	cost = {
		cycles = 8e9;
		ram = 1e9;
	};
	ui = 'starlit:compile-matter-block';
	run = function(user, ctx)
	end;
})

starlit.item.sw.link('starlit_electronics:compile_imperial', matterCompiler {
	name = 'Genesis Deluxe';
	desc = "House Bascundir has long dominated the matter compiler market in the Crystal Sea. Their firmware is excessively complex due to mountains of specialized edge-case handling, but the end result is certainly speedier than the competitors'.";
	size = 2e9;
	cost = {
		cycles = 1e9;
		ram = 1.5e9;
	};
})

do local J = starlit.store.compilerJob
	starlit.item.sw.link('starlit_electronics:driver_compiler_commune', {
		name = 'Matter Compiler';
		kind = 'driver';
		desc = "A driver for a standalone matter compiler, suitable for building larger components than your suit alone can handle.";
		size = 850e3;
		cost = {
			cycles = 400e6;
			ram = .2e9;
		};
		ui = 'starlit:device-compile-matter-component';
		run = function(user, ctx)
		end;
		--[[
		bgProc = function(user, ctx, interval, runState)
			if runState.flags.compiled == true then return false end
			-- only so many nanides to go around
			runState.flags.compiled = true
			local time = minetest.get_gametime()
			local cyclesLeft = ctx.comp.cycles * interval

			for id, e in ipairs(ctx.file.body.conf) do
				if e.key == 'job' then
					local t = J.dec(e.value)
					local remove = false
					local r = starlit.item.sw.db[t.schematic]
					if not r then -- bad schematic
						remove = true
					else
						local ccost = ctx.sw.cost.cycles + r.cost.cycles
						local tcost = ccost / cyclesLeft
						t.progress = t.progress + (1/tcost)*interval
						cyclesLeft = cyclesLeft - ccost*interval
						if t.progress >= 1 then
							-- complete
							remove = true
							local i = starlit.item.mk(r.output, {
								how = 'print';
								user = user; -- for suit
								compiler = {
									node = ctx.compiler; -- for device
									sw = ctx.sw;
									install = ctx.fd;
								};
								schematic = r;
							})
							ctx.giveItem(i)
						end
					end
					if remove then
						table.remove(ctx.file.body.conf, id)
					else
						e.value = J.enc(t)
					end
					if not cyclesLeft > 0 then break end
				end
			end
			ctx.saveConf()
		end;]]
	})
end

local function pasv_heal(effect, energy, lvl, pgmId)
	return function(user, ctx, interval, runState)
		if runState.flags.healed == true then return false end
		-- competing nanosurgical programs?? VERY bad idea
		runState.flags.healed = true

		local amt, f = user:effectiveStat 'health'
		local st = user:getSuit():powerState()
		if (st == 'on' and f < lvl) or (st == 'powerSave' and f < math.min(lvl,0.25)) then
			local maxPower = energy*interval
			local p = user:suitDrawCurrent(maxPower, interval, {
				id = 'heal';
				src = 'suitPower';
				pgmId = pgmId;
				healAmount = effect;
			})
			if p > 0 then
				local heal = (p/maxPower) * ctx.speed * effect*interval
				--user:statDelta('health', math.max(1, heal))
				starlit.fx.nano.heal(user, {{player=user.entity}}, heal, 1)
				return true
			end
		end
		return false -- program did not run
	end;
end

starlit.item.sw.link('starlit_electronics:nanomed', {
	name = 'NanoMed';
	kind = 'suitPower', powerKind = 'passive';
	desc = 'Repair of the body is a Commune specialty, and their environment suits all come equipped with highly sophisticated nanomedicine suites, able to repair even the most grievous of wounds given sufficient energy input and time.';
	size = 2e9;
	cost = {
		cycles = 400e6;
		ram = 3e9;
	};
	run = pasv_heal(2, 20, 1);
})

starlit.item.sw.link('starlit_electronics:autodoc_deluxe', {
	name = 'AutoDoc Deluxe';
	kind = 'suitPower', powerKind = 'passive';
	desc = "A flagship offering of the Excellence Unyielding nanoware division, AutoDoc Deluxe has been the top-rated nanocare package in the Celestial Shores Province for six centuries and counting. Every chip includes our comprehensive database of illnesses, prosyn schematics, and organ repair techniques, with free over-the-ether updates guaranteed for ten solariads from date of purchase! When professional medical care just isn't an option, 9/10 doctors recommend Excellence Unyielding AutoDoc Deluxe! The remaining doctor was bribed by our competitors.";
	size = 1e9;
	cost = {
		cycles = 700e6;
		ram = 1e9;
	};
	run = pasv_heal(4, 50, .7);
})

starlit.item.sw.link('starlit_electronics:battle_buddy_extreme', {
	name = 'BattleBuddy XTREME';
	kind = 'suitPower', powerKind = 'passive';
	desc = "Who needs a unit medic when you've got BattleBuddy XTREME Edition! BattleBuddy XTREME Edition is fully loaded with emergency response protocols for wounds of every caliber, and is GUARANTEED* to keep you alive as long as you can still crawl to safety. BattleBuddy XTREME is not intended for civilian use. By using BattleBuddy XTREME, you commit to unbind House Vacsatar, its subcontractors, and cadet houses from all liability for product failure, intracellular mutilation, transcription drift, runaway prion cascades, or military defeat.\n*Guarantees not legally binding.";
	size = 4e9;
	cost = {
		cycles = 2000e6;
		ram = 8e9;
	};
	run = pasv_heal(4, 50, .7);
})

