--[[

	Signs Bot
	=========

	Copyright (C) 2019-2024 Joachim Stolberg

	GPL v3
	See LICENSE.txt for more information

	Bot move commands

]]--

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

local lib = signs_bot.lib
local get_node_lvm = tubelib2.get_node_lvm


local function node_and_pos(pos)
	return get_node_lvm(pos), pos
end

-- Positions to check:
--   5 6
--  [R]1
--   3 2
--   4
function signs_bot.move_robot(mem)
	local param2 = mem.robot_param2
	local pos = mem.robot_pos
	local node1, pos1 = node_and_pos(lib.next_pos(pos, param2))
	local node2, pos2 = node_and_pos({x=pos1.x, y=pos1.y-1, z=pos1.z})
	local node3, pos3 = node_and_pos({x=pos.x, y=pos.y-1, z=pos.z})

	--
	-- One step forward (pos1)
	--
	if lib.check_pos(pos1, node1, node2, param2) or
			lib.check_pos(pos1, node1, node3, param2) then
		if node3.name == "signs_bot:robot_foot" then
			minetest.swap_node(pos3, mem.stored_node or {name = "air"})
			minetest.remove_node(pos)
		elseif node3.name == "signs_bot:robot_leg" then
			local node4, pos4 = node_and_pos({x=pos.x, y=pos.y-2, z=pos.z})
			if node4.name == "signs_bot:robot_foot" then
				minetest.swap_node(pos4, mem.stored_node or {name = "air"})
			end
			minetest.swap_node(pos3, {name = "air"})
			minetest.remove_node(pos)
		else
			minetest.swap_node(pos, mem.stored_node or {name = "air"})
		end
		mem.stored_node = node1
		minetest.set_node(pos1, {name="signs_bot:robot", param2=param2})
		minetest.sound_play('signs_bot_step', {pos = pos1})
		return pos1
	end

	--
	-- One step up (pos5)
	--
	local node6, pos6 = node_and_pos({x=pos1.x, y=pos1.y+1, z=pos1.z})
	if lib.check_pos(pos6, node6, node1, param2) then
		local node5, pos5 = node_and_pos({x=pos.x, y=pos.y+1, z=pos.z})
		if node5.name == "air" then
			if node3.name == "signs_bot:robot_leg" then
				return nil
			elseif node3.name == "signs_bot:robot_foot" then
				minetest.swap_node(pos3, {name="signs_bot:robot_leg"})
			else
				minetest.swap_node(pos, {name="signs_bot:robot_foot"})
			end
			minetest.set_node(pos5, {name="signs_bot:robot", param2=param2})
			minetest.sound_play('signs_bot_step', {pos = pos5})
			return pos5
		end
	end

	--
	-- One step down I (pos3)
	--
	local node4, pos4 = node_and_pos({x=pos.x, y=pos.y-2, z=pos.z})
	if lib.check_pos(pos3, node3, node4, param2) then  --
		minetest.remove_node(pos)
		minetest.set_node(pos3, {name="signs_bot:robot", param2=param2})
		minetest.sound_play('signs_bot_step', {pos = pos3})
		mem.stored_node = node3
		return pos3
	end
	--
	-- One step down II (pos3)
	--
	if node3.name == "signs_bot:robot_foot" or node3.name == "signs_bot:robot_leg" then
		minetest.remove_node(pos)
		minetest.set_node(pos3, {name="signs_bot:robot", param2=param2})
		minetest.sound_play('signs_bot_step', {pos = pos3})
		return pos3
	end
end


local function backward_robot(mem)
	local param2 = mem.robot_param2
	local pos = mem.robot_pos
	local node1, pos1 = node_and_pos(lib.next_pos(pos, (param2 + 2) % 4))
	local node2, pos2 = node_and_pos({x=pos1.x, y=pos1.y-1, z=pos1.z})
	local node3, pos3 = node_and_pos({x=pos.x, y=pos.y-1, z=pos.z})
	local node4, pos4 = node_and_pos({x=pos.x, y=pos.y-2, z=pos.z})
	local new_pos = nil

	if lib.check_pos(pos1, node1, node2, param2) then
		if node3.name == "signs_bot:robot_foot" or node3.name == "signs_bot:robot_leg" then
			minetest.remove_node(pos3)
			if node4.name == "signs_bot:robot_foot" then
				minetest.remove_node(pos4)
			end
		end
		minetest.swap_node(pos, mem.stored_node or {name = "air"})
		minetest.set_node(pos1, {name="signs_bot:robot", param2=param2})
		minetest.sound_play('signs_bot_step', {pos = pos1})
		mem.stored_node = node1
		return pos1
	end
end

signs_bot.register_botcommand("backward", {
	mod = "move",
	params = "",
	num_param = 0,
	description = S("Move the robot one step back"),
	cmnd = function(base_pos, mem)
		local new_pos = backward_robot(mem)
		if new_pos then  -- not blocked?
			mem.robot_pos = new_pos
		end
		return signs_bot.DONE
	end,
})

local function turn_robot(pos, param2, dir)
	if dir == "R" then
		param2 = (param2 + 1) % 4
	else
		param2 = (param2 + 3) % 4
	end
	minetest.swap_node(pos, {name="signs_bot:robot", param2=param2})
	minetest.sound_play('signs_bot_step', {pos = pos, gain = 0.6})
	return param2
end

signs_bot.register_botcommand("turn_left", {
	mod = "move",
	params = "",
	num_param = 0,
	description = S("Turn the robot to the left"),
	cmnd = function(base_pos, mem)
		mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "L")
		return signs_bot.DONE
	end,
})

signs_bot.register_botcommand("turn_right", {
	mod = "move",
	params = "",
	num_param = 0,
	description = S("Turn the robot to the right"),
	cmnd = function(base_pos, mem)
		mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R")
		return signs_bot.DONE
	end,
})

signs_bot.register_botcommand("turn_around", {
	mod = "move",
	params = "",
	num_param = 0,
	description = S("Turn the robot around"),
	cmnd = function(base_pos, mem)
		mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R")
		mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R")
		return signs_bot.DONE
	end,
})


-- Positions to check:
--   1
--  [R]
--   2
local function robot_up(pos, param2)
	local node1, pos1 = node_and_pos({x=pos.x, y=pos.y+1, z=pos.z})
	local node2, pos2 = node_and_pos({x=pos.x, y=pos.y-1, z=pos.z})
	if lib.check_pos(pos1, node1, node2, param2) then
		if node2.name == "signs_bot:robot_leg" then
			return nil
		elseif node2.name == "signs_bot:robot_foot" then
			minetest.swap_node(pos, {name="signs_bot:robot_leg"})
		else
			minetest.swap_node(pos, {name="signs_bot:robot_foot"})
		end
		minetest.set_node(pos1, {name="signs_bot:robot", param2=param2})
		minetest.sound_play('signs_bot_step', {pos = pos1})
		return pos1
	end
	return nil
end

signs_bot.register_botcommand("move_up", {
	mod = "move",
	params = "",
	num_param = 0,
	description = S("Move the robot upwards"),
	cmnd = function(base_pos, mem)
		local new_pos = robot_up(mem.robot_pos, mem.robot_param2)
		if new_pos then  -- not blocked?
			mem.robot_pos = new_pos
		end
		return signs_bot.DONE
	end,
})

-- Positions to check:
--  [R]
--   1
--   2
--   3
local function robot_down(pos, param2)
	local node1, pos1 = node_and_pos({x=pos.x, y=pos.y-1, z=pos.z})
	local node2, pos2 = node_and_pos({x=pos.x, y=pos.y-2, z=pos.z})
	local node3, pos3 = node_and_pos({x=pos.x, y=pos.y-3, z=pos.z})
	if lib.check_pos(pos1, node1, node2, param2)
	or (node1.name == "air" and lib.check_pos(pos2, node2, node3, param2))
	or (node1.name == "signs_bot:robot_leg" or node1.name == "signs_bot:robot_foot") then
		minetest.remove_node(pos)
		minetest.set_node(pos1, {name="signs_bot:robot", param2=param2})
		minetest.sound_play('signs_bot_step', {pos = pos1})
		return pos1
	end
	return nil
end

signs_bot.register_botcommand("move_down", {
	mod = "move",
	params = "",
	num_param = 0,
	description = S("Move the robot down"),
	cmnd = function(base_pos, mem)
		local new_pos = robot_down(mem.robot_pos, mem.robot_param2)
		if new_pos then  -- not blocked?
			mem.robot_pos = new_pos
		end
		return signs_bot.DONE
	end,
})

signs_bot.register_botcommand("fall_down", {
	mod = "move",
	params = "",
	num_param = 0,
	description = S("Fall into a hole/chasm (up to 10 blocks)"),
	cmnd = function(base_pos, mem)
		if not mem.bot_falling then
			--Run a while loop that checks the 10 nodes below bot for a node with the walkable property, breaking the loop once it finds a walkable node.
			local fallcounter = 0
			local fallnode  = {walkable = false}
			while fallcounter <= 9 and fallnode.walkable == false do
				fallcounter = fallcounter + 1
				--Pulls the node name from the next position, then assigns the node definintion to the fallnode variable.
				fallnode = minetest.get_node_or_nil({x=mem.robot_pos.x, y=mem.robot_pos.y-fallcounter, z=mem.robot_pos.z})
				fallnode = minetest.registered_nodes[fallnode.name]				
			end
			--If the first nine nodes below the bot are not walkable, then it should assign the definintion to the 10th node. If it too is not walkable, then a "Too Deep" error is returned.
			if fallnode.walkable == false then
				return signs_bot.ERROR, "Too deep"
			end
			--Designates the node above the walkable node as the new location for the bot.
			local pos3 = {x=mem.robot_pos.x, y=mem.robot_pos.y-fallcounter+1, z=mem.robot_pos.z}
			--Turns the bot into a falling node.
			local sts, _ = minetest.spawn_falling_node(mem.robot_pos)
			--Stores the data for the node the bot will land in and replaces it with air. This way if the node the bot lands in is occupied by an unwalkable node, such as a rail or sign,
			--it will be repalced when the bot moves.
			mem.stored_node = get_node_lvm(pos3)
			minetest.swap_node(pos3, {name="air"})
			if sts then
				mem.bot_falling = 2
				mem.robot_pos = {x=pos3.x, y=pos3.y, z=pos3.z}
				return signs_bot.BUSY
			end
		else
			mem.bot_falling = mem.bot_falling - 1
			if mem.bot_falling <= 0 then
				mem.bot_falling = nil
				return signs_bot.DONE
			end
			return signs_bot.BUSY
		end
	end,
})

signs_bot.register_botcommand("pause", {
	mod = "move",
	params = "<sec>",
	num_param = 1,
	description = S("Stop the robot for <sec> seconds\n(1..9999)"),
	check = function(sec)
		sec = tonumber(sec) or 1
		return sec and sec > 0 and sec < 10000
	end,
	cmnd = function(base_pos, mem, sec)
		if not mem.steps then
			mem.steps = tonumber(sec) or 1
		end
		mem.steps = mem.steps - 1
		if mem.steps == 0 then
			mem.steps = nil
			return signs_bot.DONE
		end
		if mem.capa then
			mem.capa = mem.capa + 1
		end
		return signs_bot.BUSY
	end,
})

signs_bot.register_botcommand("stop", {
	mod = "move",
	params = "",
	num_param = 0,
	description = S("Stop the robot."),
	cmnd = function(base_pos, mem, slot)
		local param2 = mem.robot_param2
		local pos = mem.robot_pos
		local node1, pos1 = node_and_pos(lib.next_pos(pos, param2))
		if mem.capa then
			mem.capa = mem.capa + 2
		end
		if node1 and node1.name ~= "signs_bot:sign_stop" then
			return signs_bot.DONE
		end
		return signs_bot.BUSY
	end,
})

signs_bot.register_botcommand("turn_off", {
	mod = "move",
	params = "",
	num_param = 0,
	description = S("Turn the robot off\n"..
		"and put it back in the box."),
	cmnd = function(base_pos, mem)
		signs_bot.stop_robot(base_pos, mem)
		return signs_bot.TURN_OFF
	end,
})



