local TRIGGER_MODE_SENDER = 0
local TRIGGER_MODE_RECEIVER = 1
local TRIGGER_MODE_SENDER_TYPE = 2
local TRIGGER_MODE_RECEIVER_TYPE = 3
local TRIGGER_MODE_RESET = 4
local TRIGGER_MODE_INFO = 5
local TRIGGER_MODE_MAX = 5

local S = minetest.get_translator("lzr_laser")

local trigger_tool_crosshairs = {}
local selected_senders = {}

minetest.register_tool("lzr_laser:block_state_toggler", {
	description = S("Block State Toggler"),
	_tt_help = S("Turns blocks on or off"),
	inventory_image = "lzr_laser_emitter_toggler.png",
	groups = { editor_tool = 1 },
	on_use = function(itemstack, user, pointed_thing)
		if pointed_thing.type ~= "node" then
			return itemstack
		end
		local uname = user:get_player_name()
		local state = lzr_gamestate.get_state()
		if state ~= lzr_gamestate.EDITOR and state ~= lzr_gamestate.DEV then
			minetest.chat_send_player(uname, S("This tool only works in the level editor or development mode."))
			return itemstack
		end
		local pos = pointed_thing.under
		local node = minetest.get_node(pos)
		local def = minetest.registered_nodes[node.name]
		if def._lzr_on_toggle then
			local actions = def._lzr_on_toggle(pos, node)
			if actions then
				local nodeupdate = false
				-- The actions here need to be modelled after physics.lua
				for a=1, #actions do
					local action = actions[a]
					if action.type == "set_node" then
						minetest.set_node(pos, action.node)
						nodeupdate = true
					elseif action.type == "swap_node" then
						minetest.swap_node(pos, action.node)
						nodeupdate = true
					elseif action.type == "sound_play" then
						minetest.sound_play(action.sound, {pos=pos}, true)
					elseif action.type == "add_particlespawner" then
						minetest.add_particlespawner(action.particlespawner)
					end
				end
				if nodeupdate then
					lzr_laser.full_laser_update()
				end
			end
		end
		return itemstack
	end,
})

minetest.register_tool("lzr_laser:color_changer", {
	description = S("Color Changer"),
	_tt_help = S("Changes block color"),
	inventory_image = "lzr_laser_color_changer.png",
	groups = { editor_tool = 1 },
	on_use = function(itemstack, user, pointed_thing)
		if pointed_thing.type ~= "node" then
			return itemstack
		end
		local state = lzr_gamestate.get_state()
		local uname = user:get_player_name()
		if state ~= lzr_gamestate.EDITOR and state ~= lzr_gamestate.DEV then
			minetest.chat_send_player(uname, S("This tool only works in the level editor or development mode."))
			return itemstack
		end
		local pos = pointed_thing.under
		local node = minetest.get_node(pos)
		local def = minetest.registered_nodes[node.name]
		if def._lzr_next_color then
			node.name = def._lzr_next_color
			minetest.swap_node(pos, node)
			minetest.sound_play({name="lzr_laser_change_color", gain=0.4}, {pos=pos}, true)
			local minpos, maxpos = lzr_world.get_level_bounds()
			if state == lzr_gamestate.EDITOR then
				lzr_laser.full_laser_update(minpos, maxpos)
			end
		end
		return itemstack
	end,
	on_place = function(itemstack, user, pointed_thing)
		if pointed_thing.type ~= "node" then
			return itemstack
		end
		local state = lzr_gamestate.get_state()
		local uname = user:get_player_name()
		if state ~= lzr_gamestate.EDITOR and state ~= lzr_gamestate.DEV then
			minetest.chat_send_player(uname, S("This tool only works in the level editor or development mode."))
			return itemstack
		end
		local pos = pointed_thing.under
		local node = minetest.get_node(pos)
		local def = minetest.registered_nodes[node.name]
		if def._lzr_prev_color then
			node.name = def._lzr_prev_color
			minetest.swap_node(pos, node)
			minetest.sound_play({name="lzr_laser_change_color", gain=0.4}, {pos=pos}, true)
			local minpos, maxpos = lzr_world.get_level_bounds()
			if state == lzr_gamestate.EDITOR then
				lzr_laser.full_laser_update(minpos, maxpos)
			end
		end
		return itemstack
	end,
})

minetest.register_tool("lzr_laser:screw_changer", {
	description = S("Screw Changer"),
	_tt_help = S("Cycles through screw types for blocks"),
	inventory_image = "lzr_laser_screw_changer.png",
	groups = { editor_tool = 1 },
	pointabilities = {
		nodes = {
			["group:skull"] = true,
		},
	},
	on_use = function(itemstack, user, pointed_thing)
		if pointed_thing.type ~= "node" then
			return itemstack
		end
		local state = lzr_gamestate.get_state()
		local uname = user:get_player_name()
		if state ~= lzr_gamestate.EDITOR and state ~= lzr_gamestate.DEV then
			minetest.chat_send_player(uname, S("This tool only works in the level editor or development mode."))
			return itemstack
		end
		local pos = pointed_thing.under
		local node = minetest.get_node(pos)
		if minetest.get_item_group(node.name, "laser_block") == 0 then
			return itemstack
		end
		local def = minetest.registered_nodes[node.name]
		local next_node
		-- Takable node
		if minetest.get_item_group(node.name, "takable") ~= 0 then
			if def._lzr_fixed then
				next_node = def._lzr_fixed
			end
		-- Rotatable node
		elseif minetest.get_item_group(node.name, "rotatable") == 1 then
			if def._lzr_takable then
				next_node = def._lzr_takable
			elseif def._lzr_fixed then
				next_node = def._lzr_fixed
			end
		-- Fixed node
		else
			if def._lzr_rotatable then
				next_node = def._lzr_rotatable
			elseif def._lzr_takable then
				next_node = def._lzr_takable
			end
		end
		if next_node then
			node.name = next_node
			minetest.swap_node(pos, node)
			minetest.sound_play({name="lzr_laser_change_screw", gain=0.4}, {pos=pos}, true)
		end
		return itemstack
	end,
	on_place = function(itemstack, user, pointed_thing)
		if pointed_thing.type ~= "node" then
			return itemstack
		end
		local state = lzr_gamestate.get_state()
		local uname = user:get_player_name()
		if state ~= lzr_gamestate.EDITOR and state ~= lzr_gamestate.DEV then
			minetest.chat_send_player(uname, S("This tool only works in the level editor or development mode."))
			return itemstack
		end
		local pos = pointed_thing.under
		local node = minetest.get_node(pos)
		if minetest.get_item_group(node.name, "laser_block") == 0 then
			return itemstack
		end
		local def = minetest.registered_nodes[node.name]
		local next_node
		-- Takable node
		if minetest.get_item_group(node.name, "takable") ~= 0 then
			if def._lzr_rotatable then
				next_node = def._lzr_rotatable
			elseif def._lzr_fixed then
				next_node = def._lzr_fixed
			end
		-- Rotatable node
		elseif minetest.get_item_group(node.name, "rotatable") == 1 then
			if def._lzr_fixed then
				next_node = def._lzr_fixed
			elseif def._lzr_takable then
				next_node = def._lzr_takable
			end
		-- Fixed node
		else
			if def._lzr_takable then
				next_node = def._lzr_takable
			elseif def._lzr_rotatable then
				next_node = def._lzr_rotatable
			end
		end
		if next_node then
			node.name = next_node
			minetest.swap_node(pos, node)
			minetest.sound_play({name="lzr_laser_change_screw", gain=0.4}, {pos=pos}, true)
		end
		return itemstack
	end,
})



local trigger_tool_modeinfo = {
	[TRIGGER_MODE_SENDER] = {
		img = "lzr_laser_trigger_tool_sender.png",
		name = S("Sender Mode"),
		description = S("Punch node to select a sender to add receivers to in Receiver Mode"),
	},
	[TRIGGER_MODE_RECEIVER] = {
		img = "lzr_laser_trigger_tool_receiver.png",
		name = S("Receiver Mode"),
		description = S("Punch node to add a node as a receiver to the list of receivers of the selected sender from Sender Mode"),
	},
	[TRIGGER_MODE_SENDER_TYPE] = {
		img = "lzr_laser_trigger_tool_sender_type.png",
		name = S("Sender Type Mode"),
		description = S("Punch sender node to change its sender type"),
	},
	[TRIGGER_MODE_RECEIVER_TYPE] = {
		img = "lzr_laser_trigger_tool_receiver_type.png",
		name = S("Receiver Type Mode"),
		description = S("Punch receiver node to change its receiver type"),
	},
	[TRIGGER_MODE_RESET] = {
		img = "lzr_laser_trigger_tool_reset.png",
		name = S("Reset Mode"),
		description = S("Punch sender or receiver to remove all its trigger information"),
	},
	[TRIGGER_MODE_INFO] = {
		img = "lzr_laser_trigger_tool_info.png",
		name = S("Info Mode"),
		description = S("Punch node to expose its current trigger relations").."\n"..S("Sneak-punch node to show trigger relations permanently"),
	},
}

local function construct_trigger_tool_description(mode)
	local MODE_COLOR = "#fff020"
	local HELP_COLOR = "#ffd0d0"
	--~ Tool in level editor to change triggers
	return S("Trigger Tool").."\n"..
		minetest.colorize(MODE_COLOR, trigger_tool_modeinfo[mode].name).."\n"..
		minetest.colorize(HELP_COLOR, trigger_tool_modeinfo[mode].description).."\n"..
		minetest.colorize(HELP_COLOR, S("Place to change mode"))
end

local function add_sender_crosshair(player, pos)
	local id = player:hud_add({
		type = "image_waypoint",
		text = "lzr_laser_particle_crosshair.png^[opacity:200",
		offset = { x = 0, y = 0 },
		scale = { x = 8, y = 8 },
		z_index = -295,
		world_pos = pos,
	})
	if id then
		trigger_tool_crosshairs[player:get_player_name()] = {
			time = 0,
			id = id,
		}
	end
end

minetest.register_tool("lzr_laser:trigger_tool", {
	description = construct_trigger_tool_description(TRIGGER_MODE_SENDER),
	short_description = S("Trigger Tool"),
	-- This tool handles its tooltip extension itself
	_tt_ignore = true,
	inventory_image = "lzr_laser_trigger_tool_sender.png",
	groups = { editor_tool = 1 },
	on_use = function(itemstack, user, pointed_thing)
		local imeta = itemstack:get_meta()
		local mode = imeta:get_int("mode")
		if pointed_thing.type ~= "node" then
			if mode == TRIGGER_MODE_INFO then
				lzr_laser.hide_trigger_info(user)
			end
			return itemstack
		end
		local uname = user:get_player_name()
		if lzr_gamestate.get_state() ~= lzr_gamestate.EDITOR then
			minetest.chat_send_player(uname, S("This tool only works in the level editor."))
			return itemstack
		end
		local pos = pointed_thing.under
		local minpos, maxpos = lzr_world.get_level_bounds()
		if not vector.in_area(pos, minpos, maxpos) then
			minetest.chat_send_player(uname, S("This node is outside the level area."))
			if mode == TRIGGER_MODE_INFO then
				lzr_laser.hide_trigger_info(user)
			end
			return itemstack
		end

		local node = minetest.get_node(pos)
		if minetest.get_item_group(node.name, "sender") == 0 and minetest.get_item_group(node.name, "receiver") == 0 then
			minetest.chat_send_player(uname, S("This node is neither a sender nor a receiver."))
			if mode == TRIGGER_MODE_INFO then
				lzr_laser.hide_trigger_info(user)
			end
			return itemstack
		end
		local nmeta = minetest.get_meta(pos)
		if mode == TRIGGER_MODE_SENDER then
			if minetest.get_item_group(node.name, "sender") == 0 then
				minetest.chat_send_player(uname, S("This node isn’t a sender."))
				return itemstack
			end
			local meta = minetest.get_meta(pos)
			local trigger_id = meta:get_string("trigger_id")
			if not lzr_triggers.trigger_exists(trigger_id) then
				minetest.chat_send_player(uname, S("ERROR: This node wasn’t initialized as a trigger!"))
				return itemstack
			end
			selected_senders[uname] = trigger_id

			-- Show crosshair at position
			if trigger_tool_crosshairs[uname] then
				user:hud_change(trigger_tool_crosshairs[uname].id, "world_pos", pos)
			else
				add_sender_crosshair(user, pos)
			end

			minetest.chat_send_player(uname, S("Now setting receivers for sender: @1", trigger_id))
		elseif mode == TRIGGER_MODE_RECEIVER then
			if minetest.get_item_group(node.name, "receiver") == 0 then
				minetest.chat_send_player(uname, S("This node isn’t a receiver."))
				return itemstack
			end

			local current_sender_str = selected_senders[uname] or ""
			local current_sender_pos = minetest.string_to_pos(current_sender_str)
			if not current_sender_pos then
				minetest.chat_send_player(uname, S("Select a sender in Sender Mode first!"))
				return itemstack
			end
			local meta = minetest.get_meta(current_sender_pos)
			local sender_id = meta:get_string("trigger_id")
			if not lzr_triggers.trigger_exists(sender_id) then
				minetest.chat_send_player(uname, S("The selected sender no longer exists."))
				selected_senders[uname] = nil
				if trigger_tool_crosshairs[uname] then
					user:hud_remove(trigger_tool_crosshairs[uname].id)
					trigger_tool_crosshairs[uname] = nil
				end
				return itemstack
			end
			local receiver_id = minetest.pos_to_string(pos)
			local added = lzr_triggers.add_signal(sender_id, receiver_id)
			if added then
				minetest.chat_send_player(uname, S("Added signal from sender @1 to receiver @2.", sender_id, receiver_id))
			else
				minetest.chat_send_player(uname, S("This receiver was already added to the receiver list of the sender at @1.", sender_id))
			end
			lzr_laser.show_trigger_info(sender_id, user, true)
		elseif mode == TRIGGER_MODE_SENDER_TYPE then
			if minetest.get_item_group(node.name, "sender") == 0 then
				minetest.chat_send_player(uname, S("This node isn’t a sender."))
				return itemstack
			end
			local meta = minetest.get_meta(pos)
			local trigger_id = meta:get_string("trigger_id")
			if not lzr_triggers.trigger_exists(trigger_id) then
				minetest.chat_send_player(uname, S("ERROR: This node wasn’t initialized as a trigger!"))
				return itemstack
			end
			local trigger = lzr_triggers.get_trigger(trigger_id)
			local sender_type = trigger.sender_type or 0
			sender_type = sender_type + 1
			if sender_type > lzr_triggers.MAX_SENDER_TYPE then
				sender_type = 0
			end
			lzr_triggers.set_trigger_sender_type(trigger_id, sender_type)
			--~ @1: short sender type name, @2: long sender type description
			local signal_description = S("@1 (@2)", lzr_triggers.SENDER_TYPE_NAMES[sender_type], lzr_triggers.SENDER_TYPE_DESCRIPTIONS[sender_type])
			if not signal_description then
				signal_description = tostring(sender_type)
			end
			lzr_laser.show_trigger_info(trigger_id, user, true)
			minetest.chat_send_player(uname, S("Sender type of sender @1 changed to @2.", trigger_id, signal_description))
		elseif mode == TRIGGER_MODE_RECEIVER_TYPE then
			if minetest.get_item_group(node.name, "receiver") == 0 then
				minetest.chat_send_player(uname, S("This node isn’t a receiver."))
				return itemstack
			end
			local meta = minetest.get_meta(pos)
			local trigger_id = meta:get_string("trigger_id")
			if not lzr_triggers.trigger_exists(trigger_id) then
				minetest.chat_send_player(uname, S("ERROR: This node wasn’t initialized as a trigger!"))
				return itemstack
			end
			local trigger = lzr_triggers.get_trigger(trigger_id)
			local receiver_type = trigger.receiver_type or 0
			receiver_type = receiver_type + 1
			if receiver_type > lzr_triggers.MAX_RECEIVER_TYPE then
				receiver_type = 0
			end
			lzr_triggers.set_trigger_receiver_type(trigger_id, receiver_type)
			--~ @1: short receiver type name, @2: long receiver type description
			local receiver_type_description = S("@1 (@2)", lzr_triggers.RECEIVER_TYPE_NAMES[receiver_type], lzr_triggers.RECEIVER_TYPE_DESCRIPTIONS[receiver_type])
			if not receiver_type_description then
				receiver_type_description = tostring(receiver_type)
			end
			lzr_laser.show_trigger_info(trigger_id, user, true)
			minetest.chat_send_player(uname, S("Receiver type of receiver @1 changed to @2.", trigger_id, receiver_type_description))
		elseif mode == TRIGGER_MODE_RESET then
			local meta = minetest.get_meta(pos)
			local trigger_id = meta:get_string("trigger_id")
			if not lzr_triggers.trigger_exists(trigger_id) then
				minetest.chat_send_player(uname, S("ERROR: This node wasn’t initialized as a trigger!"))
				return itemstack
			end
			lzr_triggers.set_signals(trigger_id, {})
			lzr_triggers.set_trigger_sender_type(trigger_id, 0)
			lzr_triggers.set_trigger_receiver_type(trigger_id, 0)
			--~ Node was reset at @1.
			minetest.chat_send_player(uname, S("Reset node at @1.", minetest.pos_to_string(pos)))
		elseif mode == TRIGGER_MODE_INFO then
			local meta = minetest.get_meta(pos)
			local trigger_id = meta:get_string("trigger_id")
			if not lzr_triggers.trigger_exists(trigger_id) then
				minetest.chat_send_player(uname, S("ERROR: This node wasn’t initialized as a trigger!"))
				lzr_laser.hide_trigger_info(user)
				return itemstack
			end
			local send_to, receive_from, send_to_str, receive_from_str
			local is_sender = minetest.get_item_group(node.name, "sender") ~= 0
			local is_receiver = minetest.get_item_group(node.name, "receiver") ~= 0
			if is_sender then
				send_to = lzr_triggers.get_receivers(trigger_id)
				if #send_to > 0 then
					local send_to_concat = {}
					for s=1, #send_to do
						table.insert(send_to_concat, send_to[s])
					end
					--~ list separator
					send_to_str = table.concat(send_to_concat, S(", "))
				end
			end
			if is_receiver then
				receive_from = lzr_triggers.get_senders(trigger_id)
				if #receive_from > 0 then
					local receive_from_concat = {}
					for r=1, #receive_from do
						table.insert(receive_from_concat, receive_from[r])
					end
					receive_from_str = table.concat(receive_from_concat, S(", "))
				end
			end
			if send_to_str then
				minetest.chat_send_player(uname, S("This node sends to: @1", send_to_str))
			end
			if receive_from_str then
				minetest.chat_send_player(uname, S("This node receives from: @1", receive_from_str))
			end
			if is_sender and is_receiver and not send_to_str and not receive_from_str then
				minetest.chat_send_player(uname, S("This node is a sender and receiver. It neither sends nor receives signals."))
				lzr_laser.hide_trigger_info(user)
				return itemstack
			elseif is_receiver and not receive_from_str then
				minetest.chat_send_player(uname, S("This node is a receiver. It does not receive signals."))
				lzr_laser.hide_trigger_info(user)
				return itemstack
			elseif is_sender and not send_to_str then
				minetest.chat_send_player(uname, S("This node is a sender. It does not send signals."))
				lzr_laser.hide_trigger_info(user)
				return itemstack
			end
			-- Show trigger HUD overlay.
			-- If sneak is held down, is shown permanently, otherwise, temporarily.
			local ctrl = user:get_player_control()
			local show_permanent = ctrl.sneak == true
			lzr_laser.show_trigger_info(trigger_id, user, true, show_permanent)
		end
		return itemstack
	end,
	on_place = function(itemstack, user, pointed_thing)
		local imeta = itemstack:get_meta()
		local mode = imeta:get_int("mode")
		mode = (mode + 1) % (TRIGGER_MODE_MAX+1)
		imeta:set_int("mode", mode)

		imeta:set_string("description", construct_trigger_tool_description(mode))
		imeta:set_string("inventory_image", trigger_tool_modeinfo[mode].img)
		local modename = trigger_tool_modeinfo[mode].name
		local uname = user:get_player_name()
		--~ Trigger tool was set to the new mode @1
		minetest.chat_send_player(uname, S("Tool set to @1!", modename))
		return itemstack
	end,
})

local timer = 0
local CROSSHAIR_STEP = 0.5
local CROSSHAIR_TIME_MAX = 1.0
minetest.register_globalstep(function(dtime)
	timer = timer + dtime
	if timer < CROSSHAIR_STEP then
		return
	end
	timer = 0
	local players = minetest.get_connected_players()
	for p=1, #players do
		local player = players[p]
		local pname = player:get_player_name()
		local wield = player:get_wielded_item()
		local ttc = trigger_tool_crosshairs[pname]
		if wield:get_name() == "lzr_laser:trigger_tool" then
			if not ttc then
				local sid = selected_senders[pname]
				local spos = minetest.string_to_pos(sid)
				if spos then
					add_sender_crosshair(player, spos)
				end
			end
		else
			if ttc then
				ttc.time = ttc.time + dtime + CROSSHAIR_STEP
				if ttc.time > CROSSHAIR_TIME_MAX then
					player:hud_remove(ttc.id)
					trigger_tool_crosshairs[pname] = nil
				end
			end
		end

	end
end)
minetest.register_on_leaveplayer(function(player)
	trigger_tool_crosshairs[player:get_player_name()] = nil
	selected_senders[player:get_player_name()] = nil
end)

-- The Block State Toggler replaces the Emitter Toggler
minetest.register_alias("lzr_laser:emit_toggler", "lzr_laser:block_state_toggler")

