--[[ Trigger logic.
This file handles the triggers of the level.
All trigger data is stored completely separately from
the world.

To actually make use of the trigger system, nodes need
to store a trigger_id in their node metadata, which
can be obtained by lzr_triggers.add_trigger.
]]

lzr_triggers = {}

local S = minetest.get_translator("lzr_triggers")

-- Abbreviated sender types and receiver types have
-- a separate textdomain to avoid collisions.
local SA = minetest.get_translator("_lzr_triggers_abbreviations")

--[[ A 'trigger' is a node that is able to send
and/or receive signals. Triggers have an unique trigger_id,
a location (either in the world or in the player inventory),
and a sender type (which specifies how receivers react to
the signal).

The trigger_id is an unique identifier for each trigger
and must be constructed from minetest.pos_to_string(pos),
where <pos> is the *original* position of the trigger node
that the node spawned in.

The initial location of every trigger MUST be a position.

A minimal trigger is created with lzr_triggers.add_triggers, which
must be eventually be followed by lzr_triggers.set_trigger_location.
]]
local triggers = {
	--[[
		[trigger_id_1] = {
			location = <position> or "player",
			sender_type = <number>,
			receiver_type = <number>,
		},
		-- [trigger_id_2] = ...
		-- ...
	]]
}

-- This table contains the signals. A signal is a 1:n relation
-- between a sender and its receivers.
-- Each sender may send to any number of receivers.
-- The signal list should only be constructed after the level
-- was built.
local signals = {
	--[[
		[trigger_id_of_sender_1] = { <list_of_trigger_ids_that_sender_1_sends_to> },
		[trigger_id_of_sender_2] = { <list_of_trigger_ids_that_sender_2_sends_to> },
		-- ...
	]]
}

--[[ SENDER TYPES ]]

-- List of all supported sender types.
-- When a sender gets activated or deactivated,
-- it may send a signal depending on the state
-- change. Senders can send an ON, OFF or TOGGLE
-- signal which turns on, off or toggles the
-- receiver, respectively.

-- Example: SENDER_TYPE_SYNC sends an ON signal
-- when the sender is activated and an OFF
-- signal when it is deactivated.

-- activate → ON; deactivate → OFF (default)
lzr_triggers.SENDER_TYPE_SYNC = 0
-- activate → OFF; deactivate → ON
lzr_triggers.SENDER_TYPE_SYNC_INV = 1

-- activate → TOGGLE; deactivate → TOGGLE
lzr_triggers.SENDER_TYPE_TOGGLE = 2
-- activate → ON; deactivate → ON
lzr_triggers.SENDER_TYPE_TOGGLE_ON = 3
-- activate → OFF; deactivate → OFF
lzr_triggers.SENDER_TYPE_TOGGLE_OFF = 4

-- activate → TOGGLE; deactivate → (no signal)
lzr_triggers.SENDER_TYPE_ACTIVATE_TOGGLE = 5
-- activate → ON; deactivate → (no signal)
lzr_triggers.SENDER_TYPE_ACTIVATE_ON = 6
-- activate → OFF; deactivate → (no signal)
lzr_triggers.SENDER_TYPE_ACTIVATE_OFF = 7

-- activate → (no signal); deactivate → TOGGLE
lzr_triggers.SENDER_TYPE_DEACTIVATE_TOGGLE = 8
-- activate → (no signal); deactivate → ON
lzr_triggers.SENDER_TYPE_DEACTIVATE_ON = 9
-- activate → (no signal); deactivate → OFF
lzr_triggers.SENDER_TYPE_DEACTIVATE_OFF = 10

-- Maximum sender type ID (for iterations)
lzr_triggers.MAX_SENDER_TYPE = 10

-- Icons for each sender type
lzr_triggers.SENDER_TYPE_ICONS = {
	[lzr_triggers.SENDER_TYPE_ACTIVATE_OFF] = "lzr_triggers_icon_sender_type_activate_off.png",
	[lzr_triggers.SENDER_TYPE_ACTIVATE_ON] = "lzr_triggers_icon_sender_type_activate_on.png",
	[lzr_triggers.SENDER_TYPE_ACTIVATE_TOGGLE] = "lzr_triggers_icon_sender_type_activate_toggle.png",
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_OFF] = "lzr_triggers_icon_sender_type_deactivate_off.png",
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_ON] = "lzr_triggers_icon_sender_type_deactivate_on.png",
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_TOGGLE] = "lzr_triggers_icon_sender_type_deactivate_toggle.png",
	[lzr_triggers.SENDER_TYPE_SYNC] = "lzr_triggers_icon_sender_type_sync.png",
	[lzr_triggers.SENDER_TYPE_SYNC_INV] = "lzr_triggers_icon_sender_type_sync_inv.png",
	[lzr_triggers.SENDER_TYPE_TOGGLE] = "lzr_triggers_icon_sender_type_toggle.png",
	[lzr_triggers.SENDER_TYPE_TOGGLE_OFF] = "lzr_triggers_icon_sender_type_toggle_off.png",
	[lzr_triggers.SENDER_TYPE_TOGGLE_ON] = "lzr_triggers_icon_sender_type_toggle_on.png",
}
-- Very short forms of the sender type names
lzr_triggers.SENDER_TYPE_NAMES_SHORT = {
	--!+! Short sender type name: Activate OFF
	[lzr_triggers.SENDER_TYPE_ACTIVATE_OFF] = SA("A.OFF"),
	--!+! Short sender type name: Activate ON
	[lzr_triggers.SENDER_TYPE_ACTIVATE_ON] = SA("A.ON"),
	--!+! Short sender type name: Activate TOGGLE
	[lzr_triggers.SENDER_TYPE_ACTIVATE_TOGGLE] = SA("A.TOG"),
	--!+! Short sender type name: Deactivate OFF
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_OFF] = SA("D.OFF"),
	--!+! Short sender type name: Deactivate ON
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_ON] = SA("D.ON"),
	--!+! Short sender type name: Deactivate TOGGLE
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_TOGGLE] = SA("D.TOG"),
	--!+! Short sender type name: Synchronized
	[lzr_triggers.SENDER_TYPE_SYNC] = SA("Sync"),
	--!+! Short sender type name: Synchronized inverted
	[lzr_triggers.SENDER_TYPE_SYNC_INV] = SA("Sync inv"),
	--!+! Short sender type name: Toggle
	[lzr_triggers.SENDER_TYPE_TOGGLE] = SA("T."),
	--!+! Short sender type name: Toggle OFF
	[lzr_triggers.SENDER_TYPE_TOGGLE_OFF] = SA("T.OFF"),
	--!+! Short sender type name: Toggle ON
	[lzr_triggers.SENDER_TYPE_TOGGLE_ON] = SA("T.ON"),
}

-- Long form of the sender type names
lzr_triggers.SENDER_TYPE_NAMES = {
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_ACTIVATE_OFF] = S("Activate OFF"),
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_ACTIVATE_ON] = S("Activate ON"),
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_ACTIVATE_TOGGLE] = S("Activate TOGGLE"),
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_OFF] = S("Deactivate OFF"),
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_ON] = S("Deactivate ON"),
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_TOGGLE] = S("Deactivate TOGGLE"),
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_SYNC] = S("Synchronous"),
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_SYNC_INV] = S("Synchronous inverted"),
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_TOGGLE] = S("Toggle"),
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_TOGGLE_OFF] = S("Toggle OFF"),
	--~ Sender type name
	[lzr_triggers.SENDER_TYPE_TOGGLE_ON] = S("Toggle ON"),
}

-- Descriptions of sender types
lzr_triggers.SENDER_TYPE_DESCRIPTIONS = {
	--~ Sender type description for sender type 'Synchronous'
	[lzr_triggers.SENDER_TYPE_SYNC] = S("send ON signal when activated, send OFF signal when deactivated"),
	--~ Sender type description for sender type 'Synchronous inverted'
	[lzr_triggers.SENDER_TYPE_SYNC_INV] = S("send OFF signal when activated, send ON signal when deactivated"),
	--~ Sender type description for sender type 'Toggle'
	[lzr_triggers.SENDER_TYPE_TOGGLE] = S("send TOGGLE signal when toggled"),
	--~ Sender type description for sender type 'Toggle ON'
	[lzr_triggers.SENDER_TYPE_TOGGLE_ON] = S("send ON signal when toggled"),
	--~ Sender type description for sender type 'Toggle OFF'
	[lzr_triggers.SENDER_TYPE_TOGGLE_OFF] = S("send OFF signal when toggled"),
	--~ Sender type description for sender type 'Activate ON'
	[lzr_triggers.SENDER_TYPE_ACTIVATE_ON] = S("send ON signal when activated"),
	--~ Sender type description for sender type 'Activate TOGGLE'
	[lzr_triggers.SENDER_TYPE_ACTIVATE_TOGGLE] = S("send TOGGLE signal when activated"),
	--~ Sender type description for sender type 'Activate OFF'
	[lzr_triggers.SENDER_TYPE_ACTIVATE_OFF] = S("send OFF signal when activated"),
	--~ Sender type description for sender type 'Deactivate ON'
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_ON] = S("send ON signal when deactivated"),
	--~ Sender type description for sender type 'Deactivate TOGGLE'
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_TOGGLE] = S("send TOGGLE signal when deactivated"),
	--~ Sender type description for sender type 'Deactivate OFF'
	[lzr_triggers.SENDER_TYPE_DEACTIVATE_OFF] = S("send OFF signal when deactivated"),
}

--[[ RECEIVER TYPES ]]

-- The receiver reacts to any incoming signal (default)
--
-- RECOMMENDED USE: Best used for receivers that are
-- triggered by only one sender, although multiple
-- senders are still allowed. But be aware that ANY
-- signal may change the state, so carefully test the
-- level if you want to build complex setups.
lzr_triggers.RECEIVER_TYPE_ANY = 0

-- This is basically a logical AND of its senders.
-- If the receiver receives any signal
-- (ON, OFF or TOGGLE), it does the following:
-- First, it checks if it has any senders
-- of type SENDER_TYPE SYNC or SENDER_TYPE_SYNC_INV.
-- If it doesn't, the receiver activates.
-- If it does, it asks two questions:
-- 1) Are all my senders of type SENDER_TYPE_SYNC active? (if there are none, answer "yes")
-- 2) Are all my senders of type SENDER_TYPE_SYNC_INV inactive? (if there are none, answer "yes")
-- If the answer is "yes" for both questions
-- the receiver activates. Otherwise, it deactivates.
--
-- RECOMMENDED USE: This receiver type is best used with
-- a number of senders of type SENDER_TYPE_SYNC if you want
-- all of them to be active to activate the receiver.
-- Alternatively, use SENDER_TYPE_SYNC_INV for the senders
-- if you expect all senders to be inactive in order
-- to activate the receiver. You may also mix both signal
-- types for more complex levels.
-- Using other sender types with this receiver is possible
-- but mostly pointless.
lzr_triggers.RECEIVER_TYPE_SYNC_AND = 1

-- Maximum receiver type ID (for iterations)
lzr_triggers.MAX_RECEIVER_TYPE = 1

-- Icons for each receiver type
lzr_triggers.RECEIVER_TYPE_ICONS = {
	[lzr_triggers.RECEIVER_TYPE_ANY] = "lzr_triggers_icon_receiver_type_any.png",
	[lzr_triggers.RECEIVER_TYPE_SYNC_AND] = "lzr_triggers_icon_receiver_type_sync_and.png",
}
-- Very short forms of the receiver type names
lzr_triggers.RECEIVER_TYPE_NAMES_SHORT = {
	--!+! Short receiver type name: Any
	[lzr_triggers.RECEIVER_TYPE_ANY] = SA("Any"),
	--!+! Short receiver type name: Synchronized AND
	[lzr_triggers.RECEIVER_TYPE_SYNC_AND] = SA("S.AND"),
}
-- Long form of the receiver type names
lzr_triggers.RECEIVER_TYPE_NAMES = {
	--~ Receiver type name
	[lzr_triggers.RECEIVER_TYPE_ANY] = S("Any"),
	--~ Receiver type name
	[lzr_triggers.RECEIVER_TYPE_SYNC_AND] = S("Synchronous AND"),
}
-- Descriptions of receiver types
lzr_triggers.RECEIVER_TYPE_DESCRIPTIONS = {
	--~ Receiver type description for type 'Any'
	[lzr_triggers.RECEIVER_TYPE_ANY] = S("react to any signal"),
	--~ Receiver type description for type 'Synchronous AND'
	[lzr_triggers.RECEIVER_TYPE_SYNC_AND] = S("activates when receiving a signal and all its synchronous senders are active and all its inverted synchronous senders are inactive; deactivates when receiving a signal when that’s not the case"),
}


lzr_triggers.reset_triggers = function()
	triggers = {}
	signals = {}
end

-- Marks a node as a trigger. This function should be
-- called on all sender and receiver nodes right
-- after the level was built.
-- This MUST NOT be called if the trigger already exists.
lzr_triggers.add_trigger = function(pos)
	local id = minetest.pos_to_string(pos)
	if triggers[id] then
		minetest.log("error", "[lzr_triggers] Tried to add trigger '"..id.."' twice!")
		return
	end
	triggers[id] = {
		location = table.copy(pos),
		sender_type = 0,
		receiver_type = 0,
	}
	signals[id] = {}
	minetest.log("verbose", "[lzr_triggers] Added trigger '"..id.."'")
	return id
end

-- Returns true if the trigger with the given ID
-- exists.
lzr_triggers.trigger_exists = function(id)
	return triggers[id] ~= nil
end

-- Removes a trigger from the trigger database.
-- If the trigger doesn't exist, nothing happens.
lzr_triggers.remove_trigger = function(id)
	if not triggers[id] then
		return
	end
	triggers[id] = nil
	signals[id] = nil
	-- Also remove the trigger from all signal lists
	for sender_id, receiver_ids in pairs(signals) do
		for r=1, #receiver_ids do
			local receiver_id = receiver_ids[r]
			if receiver_id == id then
				table.remove(receiver_ids, r)
				break
			end
		end
	end
	minetest.log("verbose", "[lzr_triggers] Removed trigger '"..id.."'")
end

-- Set the list of signals at once for the given sender.
-- Will replace the old list.
-- * sender_trigger_id: trigger_id of sender
-- * receiver_trigger_ids: list of trigger_ids for all receivers to send to.
lzr_triggers.set_signals = function(sender_trigger_id, receiver_trigger_ids)
	if not signals[sender_trigger_id] then
		minetest.log("error", "[lzr_triggers] Cannot add signal for '"..tostring(sender_trigger_id).."', trigger does not exist yet")
		return false
	end
	signals[sender_trigger_id] = receiver_trigger_ids
	minetest.log("verbose", "[lzr_triggers] Set signals for '"..sender_trigger_id.."'")
	return true
end

-- Add a single signal from sender to receiver (each given by trigger_id),
-- but not if the signal already exists.
lzr_triggers.add_signal = function(sender_trigger_id, receiver_trigger_id)
	if not signals[sender_trigger_id] then
		minetest.log("error", "[lzr_triggers] Cannot add signal for '"..tostring(sender_trigger_id).."', trigger does not exist yet")
		return false
	end
	-- Check if this signal already exists
	local my_signals = signals[sender_trigger_id]
	for s=1, #my_signals do
		if my_signals[s] == receiver_trigger_id then
			return false
		end
	end
	-- Add the signal
	table.insert(signals[sender_trigger_id], receiver_trigger_id)
	minetest.log("verbose", "[lzr_triggers] Added signal to '"..receiver_trigger_id.."' for '"..sender_trigger_id.."'")
	return true
end

-- Returns the trigger of the given ID
lzr_triggers.get_trigger = function(id)
	return triggers[id]
end

-- Returns a list of all triggers
lzr_triggers.get_triggers = function()
	return table.copy(triggers)
end

-- Returns the list of triggers the given trigger sends to
lzr_triggers.get_receivers = function(id)
	if signals[id] then
		return signals[id]
	else
		return {}
	end
end

-- Returns the list of triggers the given trigger receives from
lzr_triggers.get_senders = function(id)
	local senders = {}
	for sender_id, receivers in pairs(signals) do
		for r=1, #receivers do
			if receivers[r] == id then
				table.insert(senders, sender_id)
				break
			end
		end
	end
	return senders
end

lzr_triggers.set_trigger_location = function(id, location)
	triggers[id].location = location
	local locdump
	if type(location) == "table" then
		locdump = minetest.pos_to_string(location)
	else
		locdump = tostring(location)
	end
	minetest.log("verbose", "[lzr_triggers] Set trigger location for '"..id.."' to "..locdump)
end
lzr_triggers.set_trigger_sender_type = function(id, sender_type)
	triggers[id].sender_type = sender_type
	minetest.log("verbose", "[lzr_triggers] Set trigger sender type for '"..id.."' to "..sender_type)
end
lzr_triggers.set_trigger_receiver_type = function(id, receiver_type)
	triggers[id].receiver_type = receiver_type
	minetest.log("verbose", "[lzr_triggers] Set trigger receiver type for '"..id.."' to "..receiver_type)
end


lzr_triggers.internal_trigger_export = function()
	local exported_triggers = table.copy(triggers)
	for trigger_id, receivers in pairs(signals) do
		exported_triggers[trigger_id].send_to = table.copy(receivers)
	end
	return exported_triggers
end

-- Relocate all triggers. This changes the ID and location
-- of all triggers. This should only be used when all
-- trigger locations are position-based (like in the editor).
-- * source_pos: The original position where all triggers are
--   "anchored" to. Usually this is the level position.
-- * target_pos: The new position to move the triggers to
lzr_triggers.relocate_triggers = function(source_pos, target_pos)
	local new_triggers = {}
	local trigger_mappings = {}
	for id, trigger in pairs(triggers) do
		local pos = minetest.string_to_pos(id)
		if pos then
			local relpos = vector.subtract(pos, source_pos)
			local newpos = vector.add(relpos, target_pos)
			local new_trigger = table.copy(trigger)
			new_trigger.location = newpos
			local newpos_str = minetest.pos_to_string(newpos)
			new_triggers[newpos_str] = new_trigger
			trigger_mappings[id] = newpos_str
		end
	end
	triggers = new_triggers

	-- Update the signals table, too (because the trigger IDs have changed)
	local new_signals = {}
	for sender_id, receiver_ids in pairs(signals) do
		local new_sender_id = trigger_mappings[sender_id]
		if not new_sender_id then
			minetest.log("error", "[lzr_triggers] Inconsistency between triggers and signals! (bad new_sender_id)")
			signals = {}
			return
		end
		local new_receiver_ids = {}
		for r=1, #receiver_ids do
			local new_receiver_id = trigger_mappings[receiver_ids[r]]
			if not new_receiver_id then
				minetest.log("error", "[lzr_triggers] Inconsistency between triggers and signals! (bad new_receiver_id)")
				signals = {}
				return
			end
			table.insert(new_receiver_ids, new_receiver_id)
		end
		new_signals[new_sender_id] = new_receiver_ids
	end
	signals = new_signals
end

-- Checks if player has a node item with the given Trigger ID in
-- inventory ("main" list). If found, returns:
--    <ItemStack>, <item slot>
-- If not found, returns nil.
lzr_triggers.find_trigger_in_player_inventory = function(player, trigger_id)
	if trigger_id == "" or trigger_id == nil then
		return
	end
	local inv = player:get_inventory()
	for i=1, #inv:get_list("main") do
		local item = inv:get_stack("main", i)
		local imeta = item:get_meta()
		local this_trigger_id = imeta:get_string("trigger_id")
		if this_trigger_id == trigger_id then
			return item, i
		end
	end
end

-- Helper function to check triggers for validity
dofile(minetest.get_modpath("lzr_triggers").."/check.lua")
-- Trigger debug dialog
dofile(minetest.get_modpath("lzr_triggers").."/dialog.lua")
