--[[

	Signs Bot
	=========

	Copyright (C) 2019-2021 Joachim Stolberg

	GPL v3
	See LICENSE.txt for more information

	Bot sign commands and nodes

]]--

-- for lazy programmers
local M = minetest.get_meta

-- Load support for I18n.
local S = signs_bot.S

local lib = signs_bot.lib

local sCmnds = ""
local lCmnds = {}
local tCmndIdx = {}

minetest.after(2, function()
	for idx,cmnd in ipairs(signs_bot.get_commands()) do
		cmnd = minetest.formspec_escape(cmnd)
		lCmnds[#lCmnds+1] = cmnd
		tCmndIdx[cmnd] = idx
	end
	sCmnds = table.concat(lCmnds, ",")
end)


local function formspec1(meta)
	local cmnd = meta:get_string("signs_bot_cmnd")
	local name = meta:get_string("sign_name")
	local err_msg = meta:get_string("err_msg")
	cmnd = minetest.formspec_escape(cmnd)
	name = minetest.formspec_escape(name)
	return "size[9,8]"..
	default.gui_bg..
	default.gui_bg_img..
	default.gui_slots..
	"style_type[textarea,table;font=mono]"..
	"tabheader[0,0;tab;"..S("Commands,Help")..";1;;true]"..
	"field[0.3,0.5;9,1;name;"..S("Sign name:")..";"..name.."]"..
	"textarea[0.3,1.2;9,7.2;cmnd;;"..cmnd.."]"..
	"label[0.3,7.5;"..err_msg.."]"..
	"button_exit[5,7.5;2,1;cancel;"..S("Cancel").."]"..
	"button[7,7.5;2,1;check;"..S("Check").."]"
end

local function formspec2(pos, text)
	return "size[9,8]"..
	default.gui_bg..
	default.gui_bg_img..
	default.gui_slots..
	"style_type[textarea,table;font=mono]"..
	"tabheader[0,0;tab;"..S("Commands,Help")..";2;;true]"..
	"table[0.1,0;8.6,4;command;"..sCmnds..";"..pos.."]"..
	"textarea[0.3,4.5;9,3.5;help;"..S("Help")..":;"..text.."]"..
	"button[3,7.5;3,1;copy;"..S("Copy Cmnd").."]"
end

local function add_arrow(text, line_num)
	local tbl = {}
	for idx,line in ipairs(string.split(text, "\n", true)) do
		if idx == line_num and not string.find(line, '--<<== error') then
			tbl[#tbl+1] = line.."  --<<== error"
		else
			tbl[#tbl+1] = line
		end
	end
	return table.concat(tbl, "\n")
end

local function check_syntax(pos, meta, text)
	local res,err_msg, line_num = signs_bot.check_commands(pos, text)
	meta:set_int("err_code", res and 0 or 1) -- zero means OK
	meta:set_string("err_msg", err_msg)
	if line_num > 0 then
		meta:set_string("signs_bot_cmnd", add_arrow(text, line_num))
	end
end

local function append_line(pos, meta, line)
	line = line and line:trim() or ""
	local text = meta:get_string("signs_bot_cmnd").."\n"..line
	meta:set_string("signs_bot_cmnd", text)
	meta:set_int("err_code", 1) -- zero means OK
	meta:set_string("err_msg", S("please check the added line(s)"))
end

local function check_and_store(pos, meta, fields)
	meta:set_string("sign_name", fields.name)
	meta:set_string("signs_bot_cmnd", fields.cmnd)
	check_syntax(pos, meta, fields.cmnd)
	meta:set_string("formspec", formspec1(meta))
	meta:set_string("infotext", meta:get_string("sign_name"))
end

minetest.register_node("signs_bot:sign_cmnd", {
	description = S('Sign "command"'),
	drawtype = "nodebox",
	inventory_image = "signs_bot_sign_cmnd.png",
	node_box = {
		type = "fixed",
		fixed = {
			{ -1/16, -8/16, -1/16,   1/16, 4/16, 1/16},
			{ -6/16, -5/16, -2/16,   6/16, 3/16, -1/16},
		},
	},
	paramtype2 = "facedir",
	tiles = {
		"default_wood.png",
		"default_wood.png",
		"default_wood.png",
		"default_wood.png",
		"default_wood.png",
		"default_wood.png^signs_bot_sign_cmnd.png",
	},
	after_place_node = function(pos, placer, itemstack)
		local imeta = itemstack:get_meta()
		local nmeta = minetest.get_meta(pos)
		if imeta:get_string("description") ~= ""  then
			nmeta:set_string("signs_bot_cmnd", imeta:get_string("cmnd"))
			nmeta:set_string("sign_name", imeta:get_string("description"))
			nmeta:set_string("err_msg", imeta:get_string("err_msg"))
			nmeta:set_int("err_code", imeta:get_int("err_code"))
		else
			nmeta:set_string("sign_name", S('Sign "command"'))
			nmeta:set_string("signs_bot_cmnd", S("-- enter or copy commands from help page"))
			nmeta:set_int("err_code", 0)
		end
		nmeta:set_string("infotext", nmeta:get_string("sign_name"))
		nmeta:set_string("formspec", formspec1(nmeta))
	end,

	on_receive_fields = function(pos, formname, fields, player)
		if minetest.is_protected(pos, player:get_player_name()) then
			return
		end
		local meta = minetest.get_meta(pos)
		if fields.check then
			check_and_store(pos, meta, fields)
		elseif fields.key_enter_field then
			check_and_store(pos, meta, fields)
		elseif fields.copy then
			append_line(pos, meta, lCmnds[meta:get_int("help_pos")])
		elseif fields.tab == "1" then
			meta:set_string("formspec", formspec1(meta))
		elseif fields.tab == "2" then
			check_and_store(pos, meta, fields)
			local pos = meta:get_int("help_pos")
			local cmnd = lCmnds[pos] or ""
			meta:set_string("formspec", formspec2(pos, signs_bot.get_help_text(cmnd)))
		elseif fields.command then
			local evt = minetest.explode_table_event(fields.command)
			if evt.type == "DCL" then
				append_line(pos, meta, lCmnds[tonumber(evt.row)])
			elseif evt.type == "CHG" then
				local pos = tonumber(evt.row)
				meta:set_int("help_pos", pos)
				local cmnd = lCmnds[pos] or ""
				meta:set_string("formspec", formspec2(pos, signs_bot.get_help_text(cmnd)))
			end
		end
	end,

	after_dig_node = lib.after_dig_sign_node,
	paramtype = "light",
	use_texture_alpha = signs_bot.CLIP,
	sunlight_propagates = true,
	is_ground_content = false,
	drop = "",
	groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, sign_bot_sign = 1},
	sounds = default.node_sound_wood_defaults(),
})


-- Get one sign from the robot signs inventory
local function get_inv_sign(base_pos, slot)
	local inv = minetest.get_inventory({type="node", pos=base_pos})
	local stack = inv:get_stack("sign", slot)
	local taken = stack:take_item(1)
	if taken:get_count() == 1 then
		inv:set_stack("sign", slot, stack)
		return taken
	end
end

-- Put one sign into the robot signs inventory
local function put_inv_sign(base_pos, slot, item)
	local inv = minetest.get_inventory({type="node", pos=base_pos})
	local stack = inv:get_stack("sign", slot)
	local leftovers = stack:add_item(item)
	if leftovers:get_count() == 0 then
		inv:set_stack("sign", slot, stack)
		return true
	end
	return false
end

local function place_sign(base_pos, robot_pos, param2, slot)
	local pos1 = lib.dest_pos(robot_pos, param2, {0})
	if lib.not_protected(base_pos, pos1) then
		if lib.is_air_like(pos1) then
			local sign = get_inv_sign(base_pos, slot)
			if sign then
				lib.place_sign(pos1, sign, param2)
				return signs_bot.DONE
			else
				return signs_bot.ERROR, S("Error: Signs inventory empty")
			end
		end
	end
	return signs_bot.ERROR, S("Error: Position protected or occupied")
end

signs_bot.register_botcommand("place_sign", {
	mod = "sign",
	params = "<slot>",
	num_param = 1,
	description = S("Place a sign in front of the robot\ntaken from the signs inventory\n"..
		"<slot> is the inventory slot (1..6)"),
	check = function(slot)
		slot = tonumber(slot) or 1
		return slot and slot > 0 and slot < 7
	end,
	cmnd = function(base_pos, mem, slot)
		slot = tonumber(slot) or 1
		return place_sign(base_pos, mem.robot_pos, mem.robot_param2, slot)
	end,
})

local function place_sign_behind(base_pos, robot_pos, param2, slot)
	local pos1 = lib.dest_pos(robot_pos, param2, {2})
	if lib.not_protected(base_pos, pos1) then
		if lib.is_air_like(pos1) then
			local sign = get_inv_sign(base_pos, slot)
			if sign then
				lib.place_sign(pos1, sign, param2)
				return signs_bot.DONE
			else
				return signs_bot.ERROR, S("Error: Signs inventory empty")
			end
		end
	end
	return signs_bot.ERROR, S("Error: Position protected or occupied")
end

signs_bot.register_botcommand("place_sign_behind", {
	mod = "sign",
	params = "<slot>",
	num_param = 1,
	description = S("Place a sign behind the robot\ntaken from the signs inventory\n"..
		"<slot> is the inventory slot (1..6)"),
	check = function(slot)
		slot = tonumber(slot) or 1
		return slot and slot > 0 and slot < 7
	end,
	cmnd = function(base_pos, mem, slot)
		slot = tonumber(slot) or 1
		return place_sign_behind(base_pos, mem.robot_pos, mem.robot_param2, slot)
	end,
})

local function dig_sign(base_pos, robot_pos, param2, slot)
	local pos1 = lib.dest_pos(robot_pos, param2, {0})
	local meta =  M(pos1)
	local cmnd = meta:get_string("signs_bot_cmnd")
	local err_code = meta:get_int("err_code")
	local name = meta:get_string("sign_name")
	if cmnd == "" then
		return signs_bot.ERROR, S("Error: No sign available")
	end
	if lib.not_protected(base_pos, pos1) then
		local node = tubelib2.get_node_lvm(pos1)
		local sign = ItemStack(node.name)
		local meta = sign:get_meta()
		meta:set_string("description", name)
		meta:set_string("cmnd", cmnd)
		meta:set_int("err_code", err_code)
		minetest.remove_node(pos1)
		if not put_inv_sign(base_pos, slot, sign) then
			signs_bot.lib.drop_items(robot_pos, sign)
			return signs_bot.ERROR, S("Error: Signs inventory slot is occupied")
		end
		return signs_bot.DONE
	end
	return signs_bot.ERROR, S("Error: Position is protected")
end

signs_bot.register_botcommand("dig_sign", {
	mod = "sign",
	params = "<slot>",
	num_param = 1,
	description = S("Dig the sign in front of the robot\n"..
		"and add it to the signs inventory.\n"..
		"<slot> is the inventory slot (1..6)"),
	check = function(slot)
		slot = tonumber(slot) or 1
		return slot and slot > 0 and slot < 7
	end,
	cmnd = function(base_pos, mem, slot)
		slot = tonumber(slot) or 1
		return dig_sign(base_pos, mem.robot_pos, mem.robot_param2, slot)
	end,
})

local function trash_sign(base_pos, robot_pos, param2, slot)
	local pos1 = lib.dest_pos(robot_pos, param2, {0})
	local cmnd = M(pos1):get_string("signs_bot_cmnd")
	if cmnd == "" then
		return signs_bot.ERROR, S("Error: No sign available")
	end
	if lib.not_protected(base_pos, pos1) then
		local node = tubelib2.get_node_lvm(pos1)
		local sign = ItemStack("signs_bot:sign_cmnd")
		minetest.remove_node(pos1)
		local leftover = signs_bot.bot_inv_put_item(base_pos, slot, sign)
		if leftover and leftover:get_count() > 0 then
			signs_bot.lib.drop_items(robot_pos, leftover)
		end
		return signs_bot.DONE
	end
	return signs_bot.ERROR, S("Error: Position is protected")
end

signs_bot.register_botcommand("trash_sign", {
	mod = "sign",
	params = "<slot>",
	num_param = 1,
	description = S("Dig the sign in front of the robot\n"..
		"and add the cleared sign to\nthe item iventory.\n"..
		"<slot> is the inventory slot (1..8)"),
	check = function(slot)
		slot = tonumber(slot) or 1
		return slot and slot > 0 and slot < 9
	end,
	cmnd = function(base_pos, mem, slot)
		slot = tonumber(slot) or 1
		return trash_sign(base_pos, mem.robot_pos, mem.robot_param2, slot)
	end,
})


minetest.register_craft({
	output = "signs_bot:sign_cmnd 4",
	recipe = {
		{"group:wood", "default:stick", "group:wood"},
		{"dye:yellow", "default:stick", "dye:yellow"},
		{"", "dye:black", ""}
	}
})

if minetest.get_modpath("doc") then
	doc.add_entry("signs_bot", "sign_cmnd", {
		name = S("Sign 'command'"),
		data = {
			item = "signs_bot:sign_cmnd",
			text = table.concat({
				S("The 'command' sign can be programmed by the player."),
				S("Place the sign in front of you and use the node menu to program your sequence of bot commands."),
				S("The menu has an edit field for your commands and a help page with all available commands."),
				S("The help page has a copy button to simplify the programming."),
			}, "\n")
		},
	})
end

