local function get_subs(entities, matchers)
	local subs = {}
	for _, entity in pairs(entities) do
		for name, matcher in pairs(matchers) do
			local matches
			if type(matcher) == "string" then
				matches = entity.name == matcher
			else
				matches = matcher(entity.name)
			end
			if matches then
				if subs[name] ~= nil then
					error("Duplicate entity for "..name)
				end
				subs[name] = entity
			end
		end
	end
	for name, _ in pairs(matchers) do
		if subs[name] == nil then
			error("Missing entity for "..name)
		end
	end
	return subs
end

local function Signals() return { parameters = { -- signals for interface
	["core_temperature"  ] = {signal=SIGNAL_CORE_TEMP,    count=TEMPERATURE, index=1},
	["state_stopped"     ] = {signal=SIGNAL_STATE_STOPPED,          count=1, index=2},
	["state_starting"    ] = {signal=SIGNAL_STATE_STARTING,         count=0, index=3},
	["state_running"     ] = {signal=SIGNAL_STATE_RUNNING,          count=0, index=4},
	["state_scramed"     ] = {signal=SIGNAL_STATE_SCRAMED,          count=0, index=5},
	["coolant-amount"    ] = {signal=SIGNAL_COOLANT_AMOUNT,         count=0, index=6},
	["power-output"      ] = {signal=SIGNAL_REACTOR_POWER_OUTPUT,   count=0, index=7},
	["uranium-fuel-cells"] = {signal=SIGNAL_URANIUM_FUEL_CELLS,     count=0, index=8},
	--["used-uranium-fuel-cells"] = {signal=SIGNAL_USED_URANIUM_FUEL_CELLS, count=0, index=9},
	["efficiency"        ] = {signal=SIGNAL_REACTOR_EFFICIENCY,     count=0, index=10},
	["cell-bonus"        ] = {signal=SIGNAL_REACTOR_CELL_BONUS,     count=0, index=11},
	["electric-power"    ] = {signal=SIGNAL_REACTOR_ELECTRIC_POWER, count=0, index=12},
	["neighbour-bonus"   ] = {signal=SIGNAL_NEIGHBOUR_BONUS,        count=0, index=13},
} } end

local function find_reactors()
	local reactor_max_power, reactor_max_efficiency
	local formula = settings["global"]["realistic-reactors-calculate-stats-function"].value
	if formula == "ingo" then
		reactor_max_power = 123
		reactor_max_efficiency = 200
	elseif formula == "ownly" then
		reactor_max_power = 250
		reactor_max_efficiency = 210
	else
		error("formula missing")
	end

	local reactors = {}
	for name, surface in pairs(game.surfaces) do
		for _, reactor_entity in ipairs(surface.find_entities_filtered{name={REACTOR_ENTITY_NAME, BREEDER_ENTITY_NAME}}) do
			local p = reactor_entity.position
			local subs = get_subs(surface.find_entities({{p.x-1.5, p.y-1.5}, {p.x+1.5, p.y+1.5}}), {
				entity = reactor_entity.name,
				interface = E2I_NAME[reactor_entity.name],
				eccs = BOILER_ENTITY_NAME,
				power = POWER_NAME[reactor_entity.name],
				lamp = function(name) return string.match(name, "rr%-%w+%-lamp") end,
				light = function(name) return string.match(name, "rr%-%w+%-light") end,
			})
			local reactor = {
				id = subs.entity.unit_number, -- ID of the reactor (doesn't change)
				interface = subs.interface, -- interface entity
				eccs = subs.eccs, -- eccs entity
				power = subs.power, -- power entity
				entity = subs.entity, -- displayer entity
				position = subs.entity.position,
				state = 0, -- reactor state
				state_active_since = game.tick - TICKS_PER_UPDATE, -- state begin
				neighbours = 1, -- number of connected reactors and itself
				control = subs.interface.get_or_create_control_behavior(), -- control behaviour for interface signals
				efficiency = 0, -- reactor efficiency
				bonus_cells = {}, -- list of bonus cell amount for breeder
				cooling_history = 0, -- average power loss through cooling in the last TICKS_PER_UPDATE*8 ticks
				lamp = subs.lamp, --reactor lamp dummy
				light = subs.light, --light dummy for the lamp
				interface_warning_tick = 0, --last tick an electricity warning was displayed on the interface (for imitating the game's behaviour)
				interface_warning = nil, --electricity warning dummy (interface)
				cooling_warning_tick = 0, --last tick an electricity warning was displayed on the cooling
				cooling_warning = nil, --electricity warning dummy (cooling)
				power_usage = {starting = 0,cooling = 0, interface = 0}, --portions of the power consumption
				last_states_update = game.tick - TICKS_PER_UPDATE,
				fuel_last_tick = 0, -- fuel value in the core on last tick
				--fluid_amount_last_tick = 0, -- fluid amount in eccs on last tick
				power_output_last_tick = 0, --power output in MW on last tick (only updated in start or running phase)
				last_temp_update = game.tick - TICKS_PER_UPDATE,
				max_power = reactor_max_power, --dynamic maximum value for gui
				max_efficiency = reactor_max_efficiency, --dynamic maximum value for gui
				signals = Signals(),
			}
			for _, saved_signal in pairs(reactor.signals.parameters) do
				local real_signal = reactor.control.parameters[saved_signal.index]
				if (real_signal.signal.type ~= saved_signal.signal.type) or
						(real_signal.signal.name ~= saved_signal.signal.name and saved_signal.index ~= 8) then -- 8 is fuel, it may differ
					error(string.format("Can't migrate signal %d (expected %q:%q, got %q:%q)",
						saved_signal.index, saved_signal.signal.type, saved_signal.signal.name, real_signal.signal.type, real_signal.signal.name))
				end
				saved_signal.count = real_signal.count
			end
			reactor.control.parameters = reactor.signals.parameters
			if reactor.signals.parameters["state_stopped"].count == 1 then
				reactor.state = 0
			elseif reactor.signals.parameters["state_starting"].count == 1 then
				reactor.state = 1
			elseif reactor.signals.parameters["state_running"].count == 1 then
				reactor.state = 2
			elseif reactor.signals.parameters["state_scramed"].count == 1 then
				reactor.state = 3
			end
			reactors[reactor.id] = reactor
		end
	end
	return reactors
end

local function find_towers()
	local towers = {}
	for name, surface in pairs(game.surfaces) do
		for _, entity in ipairs(surface.find_entities_filtered{name=TOWER_ENTITY_NAME}) do
			local p = entity.position
			local subs = get_subs(surface.find_entities({{p.x-1.5, p.y-1.5}, {p.x+1.5, p.y+1.5}}), {
				entity = TOWER_ENTITY_NAME,
				steam = STEAM_ENTITY_NAME,
			})
			local tower = {
				id = entity.unit_number,
				entity = entity,
				steam = subs.steam,
			}
			towers[tower.id] = tower
		end
	end
	return towers
end

global.reactors = find_reactors()
global.towers = find_towers()
