local S = minetest.get_translator("mesecons_commandblock")
local F = minetest.formspec_escape

local function construct(pos)
	local meta = minetest.get_meta(pos)

	meta:set_string("commands", "")
	meta:set_string("commander", "")
end

local function after_place(pos, placer)
	if placer then
		local meta = minetest.get_meta(pos)
		meta:set_string("commander", placer:get_player_name())
	end
end

local function resolve_commands(commands, pos)
	local players = minetest.get_connected_players()

	local meta = minetest.get_meta(pos)
	local commander = meta:get_string("commander")

	-- A non-printable character used while replacing “@@”.
	local SUBSTITUTE_CHARACTER = '\26' -- ASCII SUB

	-- No players online: remove all commands containing
	-- problematic placeholders.
	if #players == 0 then
		commands = commands:gsub("[^\r\n]+", function (line)
			line = line:gsub("@@", SUBSTITUTE_CHARACTER)
			if line:find("@n") then return "" end
			if line:find("@p") then return "" end
			if line:find("@f") then return "" end
			if line:find("@r") then return "" end
			line = line:gsub("@c", commander)
			line = line:gsub(SUBSTITUTE_CHARACTER, "@")
			return line
		end)
		return commands
	end

	local nearest, farthest = nil, nil
	local min_distance, max_distance = math.huge, -1
	for index, player in pairs(players) do
		local distance = vector.distance(pos, player:get_pos())
		if distance < min_distance then
			min_distance = distance
			nearest = player:get_player_name()
		end
		if distance > max_distance then
			max_distance = distance
			farthest = player:get_player_name()
		end
	end
	local random = players[math.random(#players)]:get_player_name()
	commands = commands:gsub("@@", SUBSTITUTE_CHARACTER)
	commands = commands:gsub("@p", nearest)
	commands = commands:gsub("@n", nearest)
	commands = commands:gsub("@f", farthest)
	commands = commands:gsub("@r", random)
	commands = commands:gsub("@c", commander)
	commands = commands:gsub(SUBSTITUTE_CHARACTER, "@")
	return commands
end

local function check_commands(commands, player_name)
	for _, command in pairs(commands:split("\n")) do
		local pos = command:find(" ")
		local cmd, param = command, ""
		if pos then
			cmd = command:sub(1, pos - 1)
		end
		local cmddef = minetest.chatcommands[cmd]
		if not cmddef then
			-- Invalid chat command
			local msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands.", cmd)
			if string.sub(cmd, 1, 1) == "/" then
				msg = S("Error: The command “@1” does not exist; your command block has not been changed. Use the “help” chat command for a list of available commands. Hint: Try to remove the leading slash.", cmd)
			end
			return false, minetest.colorize("#FF0000", msg)
		end
		if player_name then
			local player_privs = minetest.get_player_privs(player_name)

			for cmd_priv, _ in pairs(cmddef.privs) do
				if player_privs[cmd_priv] ~= true then
					local msg = S("Error: You have insufficient privileges to use the command “@1” (missing privilege: @2)! The command block has not been changed.", cmd, cmd_priv)
					return false, minetest.colorize("#FF0000", msg)
				end
			end
		end
	end
	return true
end

local function commandblock_action_on(pos, node)
	if node.name ~= "mesecons_commandblock:commandblock_off" then
		return
	end

	minetest.swap_node(pos, {name = "mesecons_commandblock:commandblock_on"})

	local meta = minetest.get_meta(pos)

	local commands = resolve_commands(meta:get_string("commands"), pos)
	for _, command in pairs(commands:split("\n")) do
		local cpos = command:find(" ")
		local cmd, param = command, ""
		if cpos then
			cmd = command:sub(1, cpos - 1)
			param = command:sub(cpos + 1)
		end
		local cmddef = minetest.chatcommands[cmd]
		if not cmddef then
			-- Invalid chat command
			return
		end
		-- Execute command in the name of commander
		local commander = meta:get_string("commander")
		cmddef.func(commander, param)
	end
end

local function commandblock_action_off(pos, node)
	if node.name == "mesecons_commandblock:commandblock_on" then
		minetest.swap_node(pos, {name = "mesecons_commandblock:commandblock_off"})
	end
end

local on_rightclick = function(pos, node, player, itemstack, pointed_thing)
	local can_edit = true
	-- Only allow write access in Creative Mode
	if not minetest.settings:get_bool("creative_mode") then
		can_edit = false
	end
	local pname = player:get_player_name()
	if minetest.is_protected(pos, pname) then
		can_edit = false
	end
	local privs = minetest.get_player_privs(pname)
	if not privs.maphack then
		can_edit = false
	end

	local meta = minetest.get_meta(pos)
	local commands = meta:get_string("commands")
	if not commands then
		commands = ""
	end
	local commander = meta:get_string("commander")
	local commanderstr
	if commander == "" or commander == nil then
		commanderstr = S("Error: No commander! Block must be replaced.")
	else
		commanderstr = S("Commander: @1", commander)
	end
	local textrea_name, submit, textarea
	-- If editing is not allowed, only allow read-only access.
	-- Player can still view the contents of the command block.
	if can_edit then
		textarea_name = "commands"
		submit = "button_exit[3.3,4.5;2,1;submit;"..F(S("Submit")).."]"
	else
		textarea_name = ""
		submit = ""
	end
	if not can_edit and commands == "" then
		textarea = "label[0.5,0.5;"..F(S("No commands.")).."]"
	else
		textarea = "textarea[0.5,0.5;8.5,4;"..textarea_name..";"..F(S("Commands:"))..";"..F(commands).."]"
	end
	local formspec = "invsize[9,5;]" ..
	textarea ..
	submit ..
	"image_button[8,4.5;1,1;doc_button_icon_lores.png;doc;]" ..
	"tooltip[doc;"..F(S("Help")).."]" ..
	"label[0,4;"..F(commanderstr).."]"
	minetest.show_formspec(pname, "commandblock_"..pos.x.."_"..pos.y.."_"..pos.z, formspec)
end

local on_place = function(itemstack, placer, pointed_thing)
	if pointed_thing.type ~= "node" then
		return itemstack
	end

	-- Use pointed node's on_rightclick function first, if present
	local node = minetest.get_node(pointed_thing.under)
	if placer and not placer:get_player_control().sneak then
		if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
			return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
		end
	end

	local privs = minetest.get_player_privs(placer:get_player_name())
	if not privs.maphack then
		minetest.chat_send_player(placer:get_player_name(), S("Placement denied. You need the “maphack” privilege to place command blocks."))
		return itemstack
	end

	return minetest.item_place_node(itemstack, placer, pointed_thing)
end

minetest.register_node("mesecons_commandblock:commandblock_off", {
	description = S("Command Block"),

	_doc_items_longdesc =
S("Command blocks are mighty redstone components which are able to alter reality itself. In other words, they cause the server to execute server commands when they are supplied with redstone power."),
	_doc_items_usagehelp =
S("Everyone can activate a command block and look at its commands, but not everyone can edit and place them.").."\n\n"..

S("To view the commands in a command block, use it. To activate the command block, just supply it with redstone power. This will execute the commands once. To execute the commands again, turn the redstone power off and on again.")..
"\n\n"..

S("To be able to place a command block and change the commands, you need to be in Creative Mode and must have the “maphack” privilege. A new command block does not have any commands and does nothing. Use the command block (in Creative Mode!) to edit its commands. Read the help entry “Advanced topics > Server Commands” to understand how commands work. Each line contains a single command. You enter them like you would in the console, but without the leading slash. The commands will be executed from top to bottom.").."\n\n"..

S("All commands will be executed on behalf of the player who placed the command block, as if the player typed in the commands. This player is said to be the “commander” of the block.").."\n\n"..

S("Command blocks support placeholders, insert one of these placeholders and they will be replaced by some other text:").."\n"..
S("• “@@c”: commander of this command block").."\n"..
S("• “@@n” or “@@p”: nearest player from the command block").."\n"..
S("• “@@f” farthest player from the command block").."\n"..
S("• “@@r”: random player currently in the world").."\n"..
S("• “@@@@”: literal “@@” sign").."\n\n"..

S("Example 1:\n    time 12000\nSets the game clock to 12:00").."\n\n"..

S("Example 2:\n    give @@n mcl_core:apple 5\nGives the nearest player 5 apples"),

	tiles = {{name="jeija_commandblock_off.png", animation={type="vertical_frames", aspect_w=32, aspect_h=32, length=2}}},
	groups = {creative_breakable=1, mesecon_effector_off=1},
	drop = "",
	on_blast = function() end,
	on_construct = construct,
	is_ground_content = false,
	on_place = on_place,
	after_place_node = after_place,
	on_rightclick = on_rightclick,
	sounds = mcl_sounds.node_sound_stone_defaults(),
	mesecons = {effector = {
		action_on = commandblock_action_on,
		rules = mesecon.rules.alldirs,
	}},
	_mcl_blast_resistance = 18000000,
	_mcl_hardness = -1,
})

minetest.register_node("mesecons_commandblock:commandblock_on", {
	tiles = {{name="jeija_commandblock_off.png", animation={type="vertical_frames", aspect_w=32, aspect_h=32, length=2}}},
	groups = {creative_breakable=1, mesecon_effector_on=1, not_in_creative_inventory=1},
	drop = "",
	on_blast = function() end,
	on_construct = construct,
	is_ground_content = false,
	on_place = on_place,
	after_place_node = after_place,
	on_rightclick = on_rightclick,
	sounds = mcl_sounds.node_sound_stone_defaults(),
	mesecons = {effector = {
		action_off = commandblock_action_off,
		rules = mesecon.rules.alldirs,
	}},
	_mcl_blast_resistance = 18000000,
	_mcl_hardness = -1,
})

minetest.register_on_player_receive_fields(function(player, formname, fields)
	if string.sub(formname, 1, 13) == "commandblock_" then
		if (not fields.submit and not fields.key_enter and not fields.doc) or (not fields.commands) then
			return
		end
		local privs = minetest.get_player_privs(player:get_player_name())
		if not privs.maphack then
			minetest.chat_send_player(player:get_player_name(), S("Access denied. You need the “maphack” privilege to edit command blocks."))
			return
		end

		if fields.doc and minetest.get_modpath("doc") then
			doc.show_entry(player:get_player_name(), "nodes", "mesecons_commandblock:commandblock_off", true)
			return
		end
		local index, _, x, y, z = string.find(formname, "commandblock_(-?%d+)_(-?%d+)_(-?%d+)")
		if index ~= nil and x ~= nil and y ~= nil and z ~= nil then
			local pos = {x=tonumber(x), y=tonumber(y), z=tonumber(z)}
			local meta = minetest.get_meta(pos)
			if not minetest.settings:get_bool("creative_mode") then
				minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! You can only change the command block in Creative Mode!"))
				return
			end
			local check, error_message = check_commands(fields.commands, player:get_player_name())
			if check == false then
				-- Command block rejected
				minetest.chat_send_player(player:get_player_name(), error_message)
				return
			else
				meta:set_string("commands", fields.commands)
			end
		else
			minetest.chat_send_player(player:get_player_name(), S("Editing the command block has failed! The command block is gone."))
		end
	end
end)

-- Add entry alias for the Help
if minetest.get_modpath("doc") then
	doc.add_entry_alias("nodes", "mesecons_commandblock:commandblock_off", "nodes", "mesecons_commandblock:commandblock_on")
end