--[[

	===============================================
	Smartline SaferLua Controller Programming Tools
	by Micu (c) 2018

	This file contains:
	* Memory Copier for SaferLua Controller
	* Memory Programmer for SaferLua Controller

	Notice: SaferLua Controller uses node formspec
	so please switch to 'outp' or 'help' tab for
	upload data to all 3 meta strings (must not be
	an active tab)

	License: LGPLv2.1+
	===============================================

]]--


slprogtools = {}

--[[
	---------
	Variables
	---------
]]--

-- default programmer fields
local def_marker = "--@CONF@"
local def_this_var = "THIS"
local def_dev_array = "Machines"

-- colors
local msg_W = minetest.get_color_escape_sequence("#FFFFFF")
local msg_Y = minetest.get_color_escape_sequence("#FFFF00")
local msg_C = minetest.get_color_escape_sequence("#00FFFF")
local msg_M = minetest.get_color_escape_sequence("#FF00FF")
local msg_R = minetest.get_color_escape_sequence("#FFBFBF")

-- help
local help_text = [[SaferLua Controller Memory Programmer

MANUAL

NOTICE: Before accessing SaferLua Controller,
make sure active tab on target controller is
set to either 'outp' or 'help'. Attempt to
write text to active tab silently fails.

This gadget is designed to assist player in
managing SaferLua Controllers and to save
time in deployment and programming of these
useful computers.

Left click on a controller connects to the
computer and displays programmer GUI.
Left click on any Tubelib-compatible machine
stores its number (4-digit unique ID) in
memory. Right click performs quick erase
of this memory area.

Functions (tabs):

* info
  Basic information about connected controller.

* memory
  Memory Copier mode. Allows to transfer 'init',
  'func', 'loop' and 'notes' sections between
  device and connected SaferLua Controller.
  Checkboxes allow to select on which memory
  areas device operates.

* program
  Programmer mode. In many scenarios, SaferLua
  Controller manages Tubelib machines. This mode
  allows to fill a specially marked area in 'init'
  section of controller with collected machine
  numbers organized in array. It also creates
  variable with controller's own number.

  Example:

    Device has collected 3 machine numbers: 0101,
    0202 and 0303. It is connected to controller
    0555. Init section looks like below:
    -- some code
    a = 5
    -- default marker below
    --@CONF@
    -- some other code
    b = 0

    After pressing Rewrite button, init code of
    controller changes to:
    -- some code
    a = 5
    -- default marker below
    THIS = "0555" 
    Machines = Array( "0101", "0202", "0303" )
    -- some other code
    b = 0

* security
  Allows to protect main memory from read or write
  operations (or even both)

* help
  This window.

]]

--[[
	---------------
	Sound functions
	---------------
]]--

local function play_beep_ok(player_name)
	minetest.sound_play("slprogtools_beep_ok", {
		to_player = player_name,
		gain = 0.8,
	})
end
local function play_beep_err(player_name)
	minetest.sound_play("slprogtools_beep_err", {
		to_player = player_name,
		gain = 0.5,
	})
end
local function play_click(player_name)
	minetest.sound_play("slprogtools_click", {
		to_player = player_name,
		gain = 0.6,
	})
end

--[[
	-------
	Helpers
	-------
]]--

-- count Tubelib numbers in comma-separated list
local function get_num_count(list)
	return math.floor((string.len(list or "") + 1) / 5)
end

--[[
	---------------
	Device formspec
	---------------
]]--

local function formspec_copier(tool, tubelib_id)
	-- function requires all metadata to be accessible
	-- (or initialized elsewhere)
	local tool_def = tool:get_definition()
	local desc = tool_def.description
	local tool_meta = tool:get_meta()
	local init_flag = tool_meta:get_int("init_flag") ~= 0
	local func_flag = tool_meta:get_int("func_flag") ~= 0
	local loop_flag = tool_meta:get_int("loop_flag") ~= 0
	local note_flag = tool_meta:get_int("note_flag") ~= 0
	local ro_flag = tool_meta:get_int("ro_flag") ~= 0
	local label = tool_meta:get_string("description") or ""
	local init_len = string.len(tool_meta:get_string("init_code"))
	local func_len = string.len(tool_meta:get_string("func_code"))
	local loop_len = string.len(tool_meta:get_string("loop_code"))
	local note_len = string.len(tool_meta:get_string("note_text"))
	return "size[7,5.25,true]"..
	"position[0.5,0.25]" ..
	"no_prepend[]" ..
	"label[1.5,0;" .. minetest.colorize("#FFFF00", desc) .. "]" ..
	"field[0.75,1.25;4.5,0.5;text_label;;" .. label .. "]" ..
	"label[0.5,1.75;Connected to controller " .. msg_C ..
		tubelib_id .. msg_W .. "]" ..
	"checkbox[0.5,2.25;init_flag;include " .. msg_M .. "init()" .. msg_W ..
		" code (size: " .. tostring(init_len) .. ");" ..
		tostring(init_flag) .. "]" ..
	"checkbox[0.5,2.75;func_flag;include " .. msg_M .. "func" .. msg_W ..
		" code (size: " .. tostring(func_len) .. ");" ..
		tostring(func_flag) .. "]" ..
	"checkbox[0.5,3.25;loop_flag;include " .. msg_M .. "loop()" .. msg_W ..
		" code (size: " .. tostring(loop_len) .. ");" ..
		tostring(loop_flag) .. "]" ..
	"checkbox[0.5,3.75;note_flag;include " .. msg_M .. "notes" .. msg_W ..
		" text (size: " .. tostring(note_len) .. ");" ..
		tostring(note_flag) .. "]" ..
	"checkbox[0.5,4.25;ro_flag;read only (archive mode);" ..
		tostring(ro_flag) .. "]" ..
	"button_exit[5,0.75;1.5,1;label;Label]" ..
	"button_exit[5,2;1.5,1;clear;Clear]" ..
	"button_exit[5,2.75;1.5,1;download;Download]" ..
	"button_exit[5,3.5;1.5,1;upload;Upload]" ..
	"button_exit[5,4.25;1.5,1;exit;Exit]"
end

local function formspec_programmer_info(tool, slc_meta)
	local slc_id = slc_meta:get_string("number")
	local slc_state = tubelib.StateStrings[slc_meta:get_int("state")]
	local slc_init_len = string.len(slc_meta:get_string("init"))
	local slc_func_len = string.len(slc_meta:get_string("func"))
	local slc_loop_len = string.len(slc_meta:get_string("loop"))
	local slc_note_len = string.len(slc_meta:get_string("notes"))
	return "tabheader[0,1.75;tab;info,memory,program,security,help;1;false;false]" ..
	"label[0,1.75;Connected to controller " .. msg_C .. slc_id ..
		msg_W .. " (" .. msg_C .. slc_state .. msg_W .. ")]" ..
	"label[0,2.5;Code size of " .. msg_M .. "init()" .. msg_W ..
		" section is " .. msg_C .. tostring(slc_init_len) .. msg_W ..
		" bytes]" ..
	"label[0,3;Code size of " .. msg_M .. "func" .. msg_W ..
		" section is " .. msg_C .. tostring(slc_func_len) .. msg_W ..
		" bytes]" ..
	"label[0,3.5;Code size of " .. msg_M .. "loop()" .. msg_W ..
		" section is " .. msg_C .. tostring(slc_loop_len) .. msg_W ..
		" bytes]" ..
	"label[0,4;Text size of " .. msg_M .. "notes" .. msg_W ..
		" area is " .. msg_C .. tostring(slc_note_len) .. msg_W ..
		" bytes]" ..
	"button_exit[5,4.75;1.5,0;exit;Exit]"
end

local function formspec_programmer_mem(tool, slc_meta)
	local tool_meta = tool:get_meta()
	local init_flag = tool_meta:get_int("init_flag") ~= 0
	local func_flag = tool_meta:get_int("func_flag") ~= 0
	local loop_flag = tool_meta:get_int("loop_flag") ~= 0
	local note_flag = tool_meta:get_int("note_flag") ~= 0
	local init_len = string.len(tool_meta:get_string("init_code"))
	local func_len = string.len(tool_meta:get_string("func_code"))
	local loop_len = string.len(tool_meta:get_string("loop_code"))
	local note_len = string.len(tool_meta:get_string("note_text"))
	return "tabheader[0,1.75;tab;info,memory,program,security,help;2;false;false]" ..
	"checkbox[0,2;init_flag;include " .. msg_M .. "init()" .. msg_W ..
		" code (" .. msg_C .. tostring(init_len) .. msg_W ..
		" bytes);" .. tostring(init_flag) .. "]" ..
	"checkbox[0,2.5;func_flag;include " .. msg_M .. "func" .. msg_W ..
		" code (" .. msg_C .. tostring(func_len) .. msg_W ..
		" bytes);" .. tostring(func_flag) .. "]" ..
	"checkbox[0,3;loop_flag;include " .. msg_M .. "loop()" .. msg_W ..
		" code (" .. msg_C .. tostring(loop_len) .. msg_W ..
		" bytes);" .. tostring(loop_flag) .. "]" ..
	"checkbox[0,3.5;note_flag;include " .. msg_M .. "notes" .. msg_W ..
		" text (" .. msg_C .. tostring(note_len) .. msg_W ..
		" bytes);" .. tostring(note_flag) .. "]" ..
	"button_exit[5,2.25;1.5,0;clear;Clear]" ..
	"button_exit[5,3;1.5,0;download;Download]" ..
	"button_exit[5,3.75;1.5,0;upload;Upload]" ..
	"button_exit[5,4.75;1.5,0;exit;Exit]"
end

local function formspec_programmer_prog(tool, slc_meta)
	local tool_meta = tool:get_meta()
	local prog_mark = tool_meta:get_string("prog_mark")
	local this_var = tool_meta:get_string("this_var")
	local dev_array = tool_meta:get_string("dev_array")
	local numlen = get_num_count(tool_meta:get_string("number_list"))
	return "tabheader[0,1.75;tab;info,memory,program,security,help;3;false;false]" ..
	"label[0,1.75;Machine numbers in memory: " .. msg_C ..
		tostring(numlen) .. msg_W .. "]" ..
	"label[0,2.75;Substitution marker in init()]" ..
	"field[3.4,3.3;1.75,0;marker_text;;" .. prog_mark .. "]" ..
	"field_close_on_enter[marker_text;false]" ..
	"label[0,3.5;Controller number variable]" ..
	"field[3.4,4.05;1.75,0;slc_var;;" .. this_var .. "]" ..
	"field_close_on_enter[slc_var;false]" ..
	"label[0,4.25;Machine numbers array]" ..
	"field[3.4,4.8;1.75,0;dev_array;;" .. dev_array .. "]" ..
	"field_close_on_enter[dev_array;false]" ..
	"button_exit[5,2.5;1.5,0;erase;Erase]" ..
	"button_exit[5,3.25;1.5,0;rewrite;Rewrite]" ..
	"button_exit[5,4.75;1.5,0;exit;Exit]"
end

local function formspec_programmer_sec(tool, slc_meta)
	local tool_meta = tool:get_meta()
	local ro_flag = tool_meta:get_int("ro_flag") ~= 0
	local wo_flag = tool_meta:get_int("wo_flag") ~= 0
	return "tabheader[0,1.75;tab;info,memory,program,security,help;4;false;false]" ..
	"checkbox[0.5,2;ro_flag;write protection (disable download and clear);" ..
		tostring(ro_flag) .. "]" ..
	"checkbox[0.5,2.5;wo_flag;read protection (disable upload);" ..
		tostring(wo_flag) .. "]" ..
	"button_exit[5,4.75;1.5,0;exit;Exit]"
end

local function formspec_programmer_help(tool, slc_meta)
	return "tabheader[0,1.75;tab;info,memory,program,security,help;5;false;false]" ..
	"textarea[0.25,1.75;7,2.75;help;;" .. help_text .. "]" ..
	"button_exit[5,4.75;1.5,0;exit;Exit]"
end

local function formspec_programmer(tool, slc_meta, tab_idx)
	-- function requires all metadata to be accessible
	-- (or initialized elsewhere)
	local tool_def = tool:get_definition()
	local label = tool_def.description
	local formspec_header = "size[7,5.25,true]" ..
		"position[0.5,0.25]" ..  "no_prepend[]" ..
		"label[1.25,0;" .. minetest.colorize("#FFFF00", label) .. "]"
	if not tab_idx or tab_idx == 1 then
		-- info
		return formspec_header ..
			formspec_programmer_info(tool, slc_meta)
	elseif tab_idx == 2 then
		-- memory
		return formspec_header ..
			formspec_programmer_mem(tool, slc_meta)
	elseif tab_idx == 3 then
		-- program
		return formspec_header ..
			formspec_programmer_prog(tool, slc_meta)
	elseif tab_idx == 4 then
		-- security
		return formspec_header ..
			formspec_programmer_sec(tool, slc_meta)
	elseif tab_idx == 5 then
		-- help
		return formspec_header ..
			formspec_programmer_help(tool, slc_meta)
	end
end

--[[
	---------------
	Device metadata
	---------------
]]--

local function init_metadata_copier(player, tool)
	-- write correct metadata only if not present
	local tool_meta = tool:get_meta()
	local tool_upd = false
	local metatable = tool_meta:to_table()
	if not metatable.fields["ro_flag"] then
		tool_meta:set_int("ro_flag", 0)
		tool_upd = true
	end
	if not metatable.fields["init_flag"] then
		tool_meta:set_int("init_flag", 1)
		tool_upd = true
	end
	if not metatable.fields["func_flag"] then
		tool_meta:set_int("func_flag", 1)
		tool_upd = true
	end
	if not metatable.fields["loop_flag"] then
		tool_meta:set_int("loop_flag", 1)
		tool_upd = true
	end
	if not metatable.fields["note_flag"] then
		tool_meta:set_int("note_flag", 1)
		tool_upd = true
	end
	if not metatable.fields["init_code"] then
		tool_meta:set_string("init_code", "")
		tool_upd = true
	end
	if not metatable.fields["func_code"] then
		tool_meta:set_string("func_code", "")
		tool_upd = true
	end
	if not metatable.fields["loop_code"] then
		tool_meta:set_string("loop_code", "")
		tool_upd = true
	end
	if not metatable.fields["note_text"] then
		tool_meta:set_string("note_text", "")
		tool_upd = true
	end
	-- all done
	if tool_upd then player:set_wielded_item(tool) end
	return tool_upd
end

local function init_metadata_programmer(player, tool)
	-- write correct metadata only if not present
	local tool_meta = tool:get_meta()
	local tool_upd = false
	local metatable = tool_meta:to_table()
	if not metatable.fields["wo_flag"] then
		tool_meta:set_int("wo_flag", 0)
		tool_upd = true
	end
	if not metatable.fields["prog_mark"] then
		tool_meta:set_string("prog_mark", def_marker)
		tool_upd = true
	end
	if not metatable.fields["this_var"] then
		tool_meta:set_string("this_var", def_this_var)
		tool_upd = true
	end
	if not metatable.fields["dev_array"] then
		tool_meta:set_string("dev_array", def_dev_array)
		tool_upd = true
	end
	if not metatable.fields["number_list"] then
		tool_meta:set_string("number_list", "")
		tool_upd = true
	end
	if tool_upd then player:set_wielded_item(tool) end
	-- copier metadata is part of device
	return init_metadata_copier(player, tool) or tool_upd
end

--[[
	-----------
	Main Window
	-----------
]]--

-- formspec callback
minetest.register_on_player_receive_fields(function(player, formname, fields)
	if formname ~= "slprogtools:slc_memory_copier" and
	   formname ~= "slprogtools:slc_memory_programmer" then
		return false
	end
	if not fields then return true end
	local player_name = player:get_player_name()
	local tool = player:get_wielded_item()
	local tool_def = tool:get_definition()
	local label = tool_def._label
	local tool_meta = tool:get_meta()
	local slc_pos = minetest.deserialize(
		tool_meta:get_string("lua_pos"))
	local slc_meta = minetest.get_meta(slc_pos)
	local slc_id = slc_meta:get_string("number")
	local tool_upd = false
	local play_snd = true
	if fields.tab then
		minetest.show_formspec(player_name, tool_def.name,
			tool_def._formspec(tool, slc_meta, tonumber(fields.tab)))
		return true
	end
	if fields.clear or fields.download or fields.upload
		or fields.erase or fields.rewrite
		or fields.label or fields.key_enter_field
		or fields.exit or fields.quit then
		play_snd = false
	end
	if fields.init_flag then
		tool_meta:set_int("init_flag", fields.init_flag == "true" and 1 or 0)
		if play_snd then play_click(player_name) end
		tool_upd = true
	end
	if fields.func_flag then
		tool_meta:set_int("func_flag", fields.func_flag == "true" and 1 or 0)
		if play_snd then play_click(player_name) end
		tool_upd = true
	end
	if fields.loop_flag then
		tool_meta:set_int("loop_flag", fields.loop_flag == "true" and 1 or 0)
		if play_snd then play_click(player_name) end
		tool_upd = true
	end
	if fields.note_flag then
		tool_meta:set_int("note_flag", fields.note_flag == "true" and 1 or 0)
		if play_snd then play_click(player_name) end
		tool_upd = true
	end
	if fields.ro_flag then
		tool_meta:set_int("ro_flag", fields.ro_flag == "true" and 1 or 0)
		if play_snd then play_click(player_name) end
		tool_upd = true
	end
	if fields.wo_flag then
		tool_meta:set_int("wo_flag", fields.wo_flag == "true" and 1 or 0)
		if play_snd then play_click(player_name) end
		tool_upd = true
	end
	if fields.marker_text then
		local prog_mark = fields.marker_text:trim()
		if prog_mark == "" then prog_mark = def_marker end
		tool_meta:set_string("prog_mark", prog_mark)
		if play_snd then play_click(player_name) end
		tool_upd = true
	end
	if fields.slc_var then
		local this_var = fields.slc_var:trim()
		if this_var == "" then this_var = def_this_var end
		tool_meta:set_string("this_var", this_var)
		if play_snd then play_click(player_name) end
		tool_upd = true
	end
	if fields.dev_array then
		local dev_array = fields.dev_array:trim()
		if dev_array == "" then dev_array = def_dev_array end
		tool_meta:set_string("dev_array", dev_array)
		if play_snd then play_click(player_name) end
		tool_upd = true
	end
	if tool_upd then
		player:set_wielded_item(tool)  -- update data for buttons
		tool_upd = false
	end
	if fields.clear or fields.download or fields.upload then
		-- copier window or memory tab buttons
		-- (this must work also for copier which has no WO flag)
		local ro_flag = tool_meta:get_int("ro_flag") == 1
		local wo_flag = tool_meta:get_int("wo_flag") == 1
		if (fields.clear or fields.download) and ro_flag then
			play_beep_err(player_name)
			minetest.chat_send_player(player_name,
				msg_Y .. "[" .. label .. "] " .. msg_R ..
				"Device memory is write-protected!")
			return true
		elseif fields.upload and wo_flag then
			play_beep_err(player_name)
			minetest.chat_send_player(player_name,
				msg_Y .. "[" .. label .. "] " .. msg_R ..
				"Device memory is read-protected!")
			return true
		end
		local init_flag = tool_meta:get_int("init_flag") ~= 0
		local func_flag = tool_meta:get_int("func_flag") ~= 0
		local loop_flag = tool_meta:get_int("loop_flag") ~= 0
		local note_flag = tool_meta:get_int("note_flag") ~= 0
		local clr_list = {}
		if fields.clear then
			if init_flag then
				tool_meta:set_string("init_code", "")
				clr_list[#clr_list + 1] =  "init"
				tool_upd = true
			end
			if func_flag then
				tool_meta:set_string("func_code", "")
				clr_list[#clr_list + 1] = "func"
				tool_upd = true
			end
			if loop_flag then
				tool_meta:set_string("loop_code", "")
				clr_list[#clr_list + 1] = "loop"
				tool_upd = true
			end
			if note_flag then
				tool_meta:set_string("note_text", "")
				clr_list[#clr_list + 1] = "notes"
				tool_upd = true
			end
			play_click(player_name)
			minetest.chat_send_player(player_name,
				msg_Y .. "[" .. label .. "] " .. msg_W ..
				"Memory cleared (" ..
				table.concat(clr_list, ", ") .. ")")
		elseif fields.download then
			local init_len, func_len, loop_len, note_len = -1, -1, -1, -1
			if init_flag then
				tool_meta:set_string("init_code",
					slc_meta:get_string("init"))
				init_len = string.len(
					tool_meta:get_string("init_code"))
				tool_upd = true
			end
			if func_flag then
				tool_meta:set_string("func_code",
					slc_meta:get_string("func"))
				func_len = string.len(
					tool_meta:get_string("func_code"))
				tool_upd = true
			end
			if loop_flag then
				tool_meta:set_string("loop_code",
					slc_meta:get_string("loop"))
				loop_len = string.len(
					tool_meta:get_string("loop_code"))
				tool_upd = true
			end
			if note_flag then
				tool_meta:set_string("note_text",
					slc_meta:get_string("notes"))
				note_len = string.len(
					tool_meta:get_string("note_text"))
				tool_upd = true
			end
			play_beep_ok(player_name)
			minetest.chat_send_player(player_name,
				msg_Y .. "[" .. label .. "] " .. msg_W ..
				"Download from " .. msg_C .. slc_id .. msg_W ..
				" completed (init " .. msg_C ..
				(init_len < 0 and "skipped" or tostring(init_len) ..
				msg_W .. " bytes") .. msg_W .. ", func " .. msg_C ..
				(func_len < 0 and "skipped" or tostring(func_len) ..
				msg_W .. " bytes") .. msg_W .. ", loop " .. msg_C ..
				(loop_len < 0 and "skipped" or tostring(loop_len) ..
				msg_W .. " bytes") .. msg_W .. ", notes " .. msg_C ..
				(note_len < 0 and "skipped" or tostring(note_len) ..
				msg_W .. " bytes)"))
		elseif fields.upload then
			local init_len, func_len, loop_len, note_len = -1, -1, -1, -1
			if slc_meta:get_int("state") ~= tubelib.STOPPED then
				play_beep_err(player_name)
				minetest.chat_send_player(player_name,
					msg_Y .. "[" .. label .. "] " .. msg_R ..
					"Cannot upload to running system!")
				return true
			end
			if init_flag then
				slc_meta:set_string("init",
					tool_meta:get_string("init_code"))
				init_len = string.len(
					slc_meta:get_string("init"))
			end
			if func_flag then
				slc_meta:set_string("func",
					tool_meta:get_string("func_code"))
				func_len = string.len(
					slc_meta:get_string("func"))
			end
			if loop_flag then
				slc_meta:set_string("loop",
					tool_meta:get_string("loop_code"))
				loop_len = string.len(
					slc_meta:get_string("loop"))
			end
			if note_flag then
				slc_meta:set_string("notes",
					tool_meta:get_string("note_text"))
				note_len = string.len(
					slc_meta:get_string("notes"))
			end
			play_beep_ok(player_name)
			minetest.chat_send_player(player_name,
				msg_Y .. "[" .. label .. "] " .. msg_W ..
				"Upload to " .. msg_C .. slc_id .. msg_W ..
				" completed (init " .. msg_C ..
				(init_len < 0 and "skipped" or tostring(init_len) ..
				msg_W .. " bytes") .. msg_W .. ", func " .. msg_C ..
				(func_len < 0 and "skipped" or tostring(func_len) ..
				msg_W .. " bytes") .. msg_W .. ", loop " .. msg_C ..
				(loop_len < 0 and "skipped" or tostring(loop_len) ..
				msg_W .. " bytes") .. msg_W .. ", notes " .. msg_C ..
				(note_len < 0 and "skipped" or tostring(note_len) ..
				msg_W .. " bytes)"))
		end
	elseif fields.erase or fields.rewrite then
		-- programmer tab buttons
		if fields.erase then
			tool_meta:set_string("number_list", "")
			tool_upd = true
			play_click(player_name)
			minetest.chat_send_player(player_name,
				msg_Y .. "[" .. label .. "] " .. msg_W ..
				"All machine numbers erased from memory")
		elseif fields.rewrite then
			if slc_meta:get_int("state") ~= tubelib.STOPPED then
				play_beep_err(player_name)
				minetest.chat_send_player(player_name,
					msg_Y .. "[" .. label .. "] " .. msg_R ..
					"Cannot rewrite code of running system!")
				return true
			end
			local prog_mark = tool_meta:get_string("prog_mark")
			local this_var = tool_meta:get_string("this_var")
			local dev_array = tool_meta:get_string("dev_array")
			local numlist = tool_meta:get_string("number_list") or ""
			local newcode = this_var .. " = \"" ..
				slc_id .. "\"\n" ..dev_array .. " = Array( \"" ..
				string.gsub(numlist, ",", "\", \"") ..
				"\" )"
			local init = slc_meta:get_string("init")
			local newinit, replnum = string.gsub(init, prog_mark,
				newcode, 1)
			slc_meta:set_string("init", newinit)
			if replnum > 0 then
				play_beep_ok(player_name)
				minetest.chat_send_player(player_name,
					msg_Y .. "[" .. label .. "] " ..
					msg_W .. "Machine numbers injected into " ..
					msg_C .. slc_id .. msg_W .. " init code")
			else
				play_beep_err(player_name)
				minetest.chat_send_player(player_name,
					msg_Y .. "[" .. label .. "] " .. msg_R ..
					"No marker in code for machine numbers")
				return true
			end
		end
	elseif fields.label or fields.key_enter_field == "text_label" then
		-- copier label
		local new_txtlbl = fields.text_label:trim()
		tool_meta:set_string("description", new_txtlbl)
		play_click(player_name)
		minetest.chat_send_player(player_name,
			msg_Y .. "[" .. label .. "] " ..
			msg_W .. "Text label " ..
			(new_txtlbl == "" and "reset" or "set"))
		tool_upd = true
	elseif fields.exit or fields.quit then
		play_click(player_name)
	end
	if tool_upd then player:set_wielded_item(tool) end
	return true
end)

-- show formspec
local function show_menu_copier(item, player, pointed_thing)
	if pointed_thing.type ~= "node" then return nil end
	local player_name = player:get_player_name()
	local tool_def = item:get_definition()
	local label = tool_def._label
	if minetest.get_node(pointed_thing.under).name ~=
		"sl_controller:controller" then
		play_beep_err(player_name)
		return nil
	end
	local slc_meta = minetest.get_meta(pointed_thing.under)
	if slc_meta:get_string("owner") ~= player_name then
		play_beep_err(player_name)
		minetest.chat_send_player(player_name,
			msg_Y .. "[" .. label .. "] " ..
			msg_R ..  "Access denied - not owner!")
		return nil
	end
	tool_def._init_metadata(player, item)
	local tool_meta = item:get_meta()
	tool_meta:set_string("lua_pos",  -- for event function to get meta
		minetest.serialize(pointed_thing.under))
	player:set_wielded_item(item)
	local tubelib_id = slc_meta:get_string("number")
	play_beep_ok(player_name)
	minetest.show_formspec(player_name, tool_def.name,
		tool_def._formspec(item, tubelib_id))
	return item  -- we change metadata so on_use must receive updated item
end

local function on_use_programmer(item, player, pointed_thing)
	if pointed_thing.type ~= "node" then return nil end
	local player_name = player:get_player_name()
	local tool_def = item:get_definition()
	local label = tool_def._label
	local pt_id = tubelib.get_node_number(pointed_thing.under)
	if not pt_id then
		play_beep_err(player_name)
		minetest.chat_send_player(player_name,
			msg_Y .. "[" .. label .. "] " ..
			msg_R ..  "Incompatible logic - unable to read ID!")
		return nil
	end
	local pt_meta = minetest.get_meta(pointed_thing.under)
	local owner = pt_meta:get_string("owner") or ""
	if owner ~= "" and owner ~= player_name then
		play_beep_err(player_name)
		minetest.chat_send_player(player_name,
			msg_Y .. "[" .. label .. "] " ..
			msg_R ..  "Access denied - not owner!")
		return nil
	end
	tool_def._init_metadata(player, item)
	local tool_meta = item:get_meta()
	if minetest.get_node(pointed_thing.under).name ==
		"sl_controller:controller" then
		-- pointing at SaferLua Controller
		tool_meta:set_string("lua_pos",  -- for event function
		minetest.serialize(pointed_thing.under))
		player:set_wielded_item(item)
		play_beep_ok(player_name)
		minetest.show_formspec(player_name, tool_def.name,
			tool_def._formspec(item, pt_meta, 1))
	else
		-- pointing at Techpack device
		local numlist = tool_meta:get_string("number_list") or ""
		if string.find(numlist, pt_id) then
			play_beep_err(player_name)
			minetest.chat_send_player(player_name,
				msg_Y .. "[" .. label .. "] " .. msg_W ..
				"Number " .. msg_C .. pt_id .. msg_W ..
				" already in memory")
		else
			numlist = numlist == "" and pt_id or (numlist .. "," .. pt_id)
			tool_meta:set_string("number_list", numlist)
			play_beep_ok(player_name)
			minetest.chat_send_player(player_name,
				msg_Y .. "[" .. label .. "] " .. msg_W ..
				"Machine with number " .. msg_C .. pt_id ..
				msg_W .. " stored at position " .. msg_M ..
				tostring(get_num_count(numlist)))
		end
	end
	return item  -- we change metadata so on_use must receive updated item
end

--[[
	-----------------
	Tool registration
	-----------------
]]--

minetest.register_tool("slprogtools:slc_memory_copier", {
	description = "SaferLua Controller Memory Copier",
	wield_image = "slprogtools_memory_copier_hand.png",
	wield_scale = { x = 1, y = 1, z = 1 },
	inventory_image = "slprogtools_memory_copier_inv.png",
	stack_max = 1,
	range = 4,
	_label = "SLCMemoryCopier",
	_init_metadata = init_metadata_copier,
	_formspec = formspec_copier,
	on_use = show_menu_copier,
})

minetest.register_tool("slprogtools:slc_memory_programmer", {
	description = "SaferLua Controller Memory Programmer",
	wield_image = "slprogtools_memory_programmer_hand.png",
	wield_scale = { x = 1, y = 1, z = 1 },
	inventory_image = "slprogtools_memory_programmer_inv.png",
	stack_max = 1,
	range = 4,
	_label = "SLCMemoryProgrammer",
	_init_metadata = init_metadata_programmer,
	_formspec = formspec_programmer,
	on_use = on_use_programmer,
	on_secondary_use = function(item, user, pointed_thing)
		-- quick erase machine codes
		local player_name = user:get_player_name()
		local label = item:get_definition()._label
		local tool_meta = item:get_meta()
		tool_meta:set_string("number_list", "")
		play_click(player_name)
		minetest.chat_send_player(player_name,
			msg_Y .. "[" .. label .. "] " .. msg_W ..
			"All machine numbers erased from memory")
		return item
	end
})


--[[
	--------
	Crafting
	--------
]]--

local metal_comp = "default:gold_ingot"
if minetest.global_exists("moreores") then
	metal_comp = "moreores:silver_ingot"
end

minetest.register_craft({
	output = "slprogtools:slc_memory_copier",
	type = "shaped",
	recipe = {
		{ "", "default:steel_ingot", "" },
		{ metal_comp, "tubelib:wlanchip", "default:copper_ingot" },
		{ "", "dye:blue", "" },
	},
})

minetest.register_craft({
	output = "slprogtools:slc_memory_programmer",
	type = "shapeless",
	recipe = { "slprogtools:slc_memory_copier", "default:mese_crystal", "tubelib_addons2:programmer" },
})
