--
-- Power network specific functions and data should live here
--
local S = technic.getter

local off_delay_seconds = tonumber(technic.config:get("switch_off_delay_seconds"))

local network_node_arrays = {"PR_nodes","BA_nodes","RE_nodes"}

technic.active_networks = {}
local networks = {}
technic.networks = networks
local technic_cables = {}
technic.cables = technic_cables

function technic.create_network(sw_pos)
	local network_id = core.hash_node_position(vector.offset(sw_pos, 0, -1, 0))
	technic.build_network(network_id)
	return network_id
end

local function pos_in_array(pos, array)
	for _,pos2 in ipairs(array) do
		if vector.equals(pos, pos2) then
			return true
		end
	end
	return false
end

function technic.merge_networks(net1, net2)
	-- TODO: Optimize for merging small network into larger by first checking network
	-- node counts for both networks and keep network id with most nodes.
	assert(type(net1) == "table", "Invalid net1 for technic.merge_networks")
	assert(type(net2) == "table", "Invalid net2 for technic.merge_networks")
	assert(net1 ~= net2, "Deadlock recipe: net1 & net2 equals for technic.merge_networks")
	-- Move data in cables table
	for node_id,cable_net_id in pairs(technic_cables) do
		if cable_net_id == net2.id then
			technic_cables[node_id] = net1.id
		end
	end
	-- Move data in machine tables
	for _,tablename in ipairs(network_node_arrays) do
		for _,pos in ipairs(net2[tablename]) do
			table.insert(net1[tablename], pos)
		end
	end
	-- Move data in all_nodes table
	for node_id,pos in pairs(net2.all_nodes) do
		net1.all_nodes[node_id] = pos
	end
	-- Merge queues for incomplete networks
	if net1.queue and net2.queue then
		for _,pos in ipairs(net2.queue) do
			if not pos_in_array(pos, net1.queue) then
				table.insert(net1.queue, pos)
			end
		end
	else
		net1.queue = net1.queue or net2.queue
	end
	-- Remove links to net2
	networks[net2.id] = nil
	technic.active_networks[net2.id] = nil
end

function technic.activate_network(network_id, timeout)
	-- timeout is optional ttl for network in seconds, if not specified use default
	local network = networks[network_id]
	if network then
		-- timeout is absolute time in microseconds
		network.timeout = core.get_us_time() + ((timeout or off_delay_seconds) * 1000 * 1000)
		technic.active_networks[network_id] = network
	end
end

function technic.sw_pos2tier(pos, load_node)
	-- Get cable tier for switching station or nil if no cable
	-- load_node true to use core.load_area to load node
	local cable_pos = vector.offset(pos, 0, -1, 0)
	if load_node then
		core.load_area(cable_pos)
	end
	return technic.get_cable_tier(core.get_node(cable_pos).name)
end

-- Destroy network data
function technic.remove_network(network_id)
	for pos_hash,cable_net_id in pairs(technic_cables) do
		if cable_net_id == network_id then
			technic_cables[pos_hash] = nil
		end
	end
	networks[network_id] = nil
	technic.active_networks[network_id] = nil
end

local function switch_index(pos, net)
	for index, spos in ipairs(net.swpos) do
		if vector.equals(pos, spos) then
			return index
		end
	end
end

function technic.switch_insert(pos, net)
	if not switch_index(pos, net) then
		table.insert(net.swpos, table.copy(pos))
	end
	return #net.swpos
end

function technic.switch_remove(pos, net)
	local swindex = switch_index(pos, net)
	if swindex then
		table.remove(net.swpos, swindex)
	end
	return #net.swpos
end

function technic.sw_pos2network(pos)
	return technic_cables[core.hash_node_position(vector.offset(pos, 0, -1, 0))]
end

function technic.pos2network(pos)
	return technic_cables[core.hash_node_position(pos)]
end

function technic.network2pos(network_id)
	return core.get_position_from_hash(network_id)
end

function technic.network2sw_pos(network_id)
	-- Return switching station position for network.
	-- It is not guaranteed that position actually contains switching station.
	local sw_pos = core.get_position_from_hash(network_id)
	sw_pos.y = sw_pos.y + 1
	return sw_pos
end

function technic.network_infotext(network_id, text)
	local network = networks[network_id]
	if network then
		if text then
			network.infotext = text
		elseif network.queue then
			local count = 0
			for _ in pairs(network.all_nodes) do count = count + 1 end
			return S("Building Network: @1 Nodes", count)
		end
		return network.infotext
	end
end

local node_timeout = {}
local default_timeout = 2

function technic.set_default_timeout(timeout)
	default_timeout = timeout or 2
end

function technic.get_timeout(tier, pos)
	if node_timeout[tier] == nil then
		-- it is normal that some multi tier nodes always drop here when checking all LV, MV and HV tiers
		return 0
	end
	return node_timeout[tier][core.hash_node_position(pos)] or 0
end

local function touch_node(tier, pos, timeout)
	if node_timeout[tier] == nil then
		-- this should get built up during registration
		node_timeout[tier] = {}
	end
	node_timeout[tier][core.hash_node_position(pos)] = timeout or default_timeout
end
technic.touch_node = touch_node

function technic.disable_machine(pos, node)
	local nodedef = core.registered_nodes[node.name]
	if nodedef then
		local meta = core.get_meta(pos)
		meta:set_string("infotext", S("@1 Has No Network", nodedef.description))
	end
	if nodedef and nodedef.technic_disabled_machine_name then
		node.name = nodedef.technic_disabled_machine_name
		core.swap_node(pos, node)
	end
	if nodedef and nodedef.technic_on_disable then
		nodedef.technic_on_disable(pos, node)
	end
	local node_id = core.hash_node_position(pos)
	for _,nodes in pairs(node_timeout) do
		nodes[node_id] = nil
	end
end

local function match_cable_tier_filter(name, tiers)
	-- Helper to check for set of cable tiers
	if tiers then
		for _, tier in ipairs(tiers) do if technic.is_tier_cable(name, tier) then return true end end
		return false
	end
	return technic.get_cable_tier(name) ~= nil
end

local function get_neighbors(pos, tiers)
	local tier_machines = tiers and technic.machines[tiers[1]]
	local is_cable = match_cable_tier_filter(core.get_node(pos).name, tiers)
	local network = is_cable and technic.networks[technic.pos2network(pos)]
	local cables = {}
	local machines = {}
	local positions = {
		vector.offset(pos, 1, 0, 0),
		vector.offset(pos, -1, 0, 0),
		vector.offset(pos, 0, 1, 0),
		vector.offset(pos, 0, -1, 0),
		vector.offset(pos, 0, 0, 1),
		vector.offset(pos, 0, 0, -1),
	}
	for _,connected_pos in ipairs(positions) do
		local name = core.get_node(connected_pos).name
		if tier_machines and tier_machines[name] then
			table.insert(machines, connected_pos)
		elseif match_cable_tier_filter(name, tiers) then
			local cable_network = technic.networks[technic.pos2network(connected_pos)]
			table.insert(cables,{
				pos = connected_pos,
				network = cable_network,
			})
			if not network then network = cable_network end
		end
	end
	return network, cables, machines
end

function technic.place_network_node(pos, tiers, name)
	-- Get connections and primary network if there's any
	local network, cables, machines = get_neighbors(pos, tiers)
	if not network then
		-- We're evidently not on a network, nothing to add ourselves to
		return
	end

	-- Attach to primary network, this must be done before building branches from this position
	technic.add_network_node(pos, network)
	if not match_cable_tier_filter(name, tiers) then
		if technic.machines[tiers[1]][name] == technic.producer_receiver then
			-- FIXME: Multi tier machine like supply converter should also attach to other networks around pos.
			--      Preferably also with connection rules defined for machine.
			--      nodedef.connect_sides could be used to generate these rules.
			--		For now, assume that all multi network machines belong to technic.producer_receiver group:
			-- Get cables and networks around PR_RE machine
			local _, machine_cables, _ = get_neighbors(pos)
			for _,connection in ipairs(machine_cables) do
				if connection.network and connection.network.id ~= network.id then
					-- Attach PR_RE machine to secondary networks (last added is primary until above note is resolved)
					technic.add_network_node(pos, connection.network)
				end
			end
		else
			-- Check connected cables for foreign networks, overload if machine was connected to multiple networks
			for _, connection in ipairs(cables) do
				if connection.network and connection.network.id ~= network.id then
					technic.overload_network(connection.network.id)
					technic.overload_network(network.id)
				end
			end
		end
		-- Machine added, skip all network building
		return
	end

	-- Attach neighbor machines if cable was added
	for _,machine_pos in ipairs(machines) do
		technic.add_network_node(machine_pos, network)
	end

	-- Attach neighbor cables
	for _,connection in ipairs(cables) do
		if connection.network then
			if connection.network.id ~= network.id then
				-- Remove network if position belongs to another network
				-- FIXME: Network requires partial rebuild but avoid doing it here if possible.
				-- This might cause problems when merging two active networks into one
				technic.remove_network(network.id)
				technic.remove_network(connection.network.id)
				connection.network = nil
			end
		else
			-- There's cable that does not belong to any network, attach whole branch
			technic.add_network_node(connection.pos, network)
			technic.add_network_branch({connection.pos}, network)
		end
	end
end

-- Remove machine or cable from network
local function remove_network_node(network_id, pos)
	local network = networks[network_id]
	if not network then return end
	-- Clear hash tables, cannot use table.remove
	local node_id = core.hash_node_position(pos)
	technic_cables[node_id] = nil
	network.all_nodes[node_id] = nil
	-- TODO: All following things can be skipped if node is not machine
	--   check here if it is or is not cable
	--   or add separate function to remove cables and move responsibility to caller
	-- Clear indexed arrays, do NOT leave holes
	local machine_removed = false
	for _,tblname in ipairs(network_node_arrays) do
		local tbl = network[tblname]
		for i=#tbl,1,-1 do
			local mpos = tbl[i]
			if vector.equals(mpos, pos) then
				table.remove(tbl, i)
				machine_removed = true
				break
			end
		end
	end
	if machine_removed then
		-- Machine can still be in world, just not connected to any network. If so then disable it.
		local node = core.get_node(pos)
		technic.disable_machine(pos, node)
	end
end

function technic.remove_network_node(pos, tiers, name)
	-- Get the network and neighbors
	local network, cables, machines = get_neighbors(pos, tiers)
	if not network then return end

	if not match_cable_tier_filter(name, tiers) then
		-- Machine removed, skip cable checks to prevent unnecessary network cleanups
		for _,connection in ipairs(cables) do
			if connection.network then
				-- Remove machine from all networks around it
				remove_network_node(connection.network.id, pos)
			end
		end
		return
	end

	if #cables == 1 then
		-- Dead end cable removed, remove it from the network
		remove_network_node(network.id, pos)
		-- Remove neighbor machines from network if cable was removed
		if match_cable_tier_filter(name, tiers) then
			for _,machine_pos in ipairs(machines) do
				local net, _, _ = get_neighbors(machine_pos, tiers)
				if not net then
					-- Remove machine from network if it does not have other connected cables
					remove_network_node(network.id, machine_pos)
				end
			end
		end
	else
		-- TODO: Check branches around and switching stations for branches:
		--   remove branches that do not have switching station. Switching stations not tracked but could be easily tracked.
		--   remove branches not connected to another branch. Individual branches not tracked, requires simple AI heuristics.
		--   move branches that have switching station to new networks without checking or loading actual nodes in world.
		--   To do all this network must be aware of individual branches and switching stations, might not be worth it...
		-- For now remove whole network and let ABM rebuild it
		technic.remove_network(network.id)
	end
end

--
-- Functions to traverse the electrical network
--

-- Add a machine node to the LV/MV/HV network
local function add_network_machine(nodes, pos, network_id, all_nodes, multitier)
	local node_id = core.hash_node_position(pos)
	local net_id_old = technic_cables[node_id]
	if net_id_old == nil or (multitier and net_id_old ~= network_id and all_nodes[node_id] == nil) then
		-- Add machine to network only if it is not already added
		table.insert(nodes, pos)
		-- FIXME: Machines connecting to multiple networks should have way to store multiple network ids
		technic_cables[node_id] = network_id
		all_nodes[node_id] = pos
		return true
	elseif not multitier and net_id_old ~= network_id then
		-- Do not allow running from multiple networks, trigger overload
		technic.overload_network(network_id)
		technic.overload_network(net_id_old)
		local meta = core.get_meta(pos)
		meta:set_string("infotext",S("Network Overloaded"))
	end
end

-- Add a wire node to the LV/MV/HV network
local function add_cable_node(pos, network)
	local node_id = core.hash_node_position(pos)
	if not technic_cables[node_id] then
		technic_cables[node_id] = network.id
		network.all_nodes[node_id] = pos
		if network.queue then
			table.insert(network.queue, pos)
		end
	elseif technic_cables[node_id] ~= network.id then
		-- Conflicting network connected, merge networks if both are still in building stage
		local net2 = networks[technic_cables[node_id]]
		if net2 and net2.queue then
			technic.merge_networks(network, net2)
		end
	end
end

-- Generic function to add found connected nodes to the right classification array
local function add_network_node(network, pos, machines)
	local name = technic.get_or_load_node(pos).name

	if technic.get_cable_tier(name) == network.tier then
		add_cable_node(pos, network)
	elseif machines[name] then
		if     machines[name] == technic.producer then
			add_network_machine(network.PR_nodes, pos, network.id, network.all_nodes)
		elseif machines[name] == technic.receiver then
			add_network_machine(network.RE_nodes, pos, network.id, network.all_nodes)
		elseif machines[name] == technic.producer_receiver then
			if add_network_machine(network.PR_nodes, pos, network.id, network.all_nodes, true) then
				table.insert(network.RE_nodes, pos)
			end
		elseif machines[name] == technic.battery then
			add_network_machine(network.BA_nodes, pos, network.id, network.all_nodes)
		end
	end
end

-- Generic function to add single nodes to the right classification array of existing network
function technic.add_network_node(pos, network)
	add_network_node(network, pos, technic.machines[network.tier])
end

-- Traverse a network given a list of machines and a cable type name
local function traverse_network(network, pos, machines)
	local positions = {
		vector.offset(pos, 1, 0, 0),
		vector.offset(pos, -1, 0, 0),
		vector.offset(pos, 0, 1, 0),
		vector.offset(pos, 0, -1, 0),
		vector.offset(pos, 0, 0, 1),
		vector.offset(pos, 0, 0, -1),}
	for i, cur_pos in pairs(positions) do
		if not network.all_nodes[core.hash_node_position(cur_pos)] then
			add_network_node(network, cur_pos, machines)
		end
	end
end

local function touch_nodes(list, tier)
	for _, pos in ipairs(list) do
		touch_node(tier, pos) -- Touch node
	end
end

local function get_network(network_id, tier)
	local cached = networks[network_id]
	if cached and not cached.queue and cached.tier == tier then
		touch_nodes(cached.PR_nodes, tier)
		touch_nodes(cached.BA_nodes, tier)
		touch_nodes(cached.RE_nodes, tier)
		return cached.PR_nodes, cached.BA_nodes, cached.RE_nodes
	end
	return technic.build_network(network_id)
end

function technic.add_network_branch(queue, network)
	-- Adds whole branch to network, queue positions can be used to bypass sub branches
	local machines = technic.machines[network.tier]
	--print(string.format("technic.add_network_branch(%s, %s, %.17g)",queue,core.pos_to_string(sw_pos),network.id))
	local t1 = core.get_us_time()
	while next(queue) do
		local to_visit = {}
		for _, pos in ipairs(queue) do
			network.queue = to_visit
			traverse_network(network, pos, machines)
		end
		queue = to_visit
		if core.get_us_time() - t1 > 10000 then
			-- time limit exceeded
			break
		end
	end
	-- Set build queue for network if network build was not finished within time limits
	network.queue = #queue > 0 and queue
end

-- Battery charge status updates for network
local function update_battery(self, charge, max_charge, supply, demand)
	self.battery_charge = self.battery_charge + charge
	self.battery_charge_max = self.battery_charge_max + max_charge
	self.battery_supply = self.battery_supply + supply
	self.battery_demand = self.battery_demand + demand
	if demand ~= 0 then
		self.BA_count_active = self.BA_count_active + 1
		self.BA_charge_active = self.BA_charge_active + charge
	end
end

-- Moving average function generator
local function sma(period)
	local values = {}
	local index = 1
	local sum = 0
	return function(n)
		-- Add new value and return average
		sum = sum - (values[index] or 0) + n
		values[index] = n
		index = index ~= period and index + 1 or 1
		return sum / #values
	end
end

function technic.build_network(network_id)
	local network = networks[network_id]
	if network and not network.queue then
		-- Network exists complete and cached
		return network.PR_nodes, network.BA_nodes, network.RE_nodes
	elseif not network then
		-- Build new network if network does not exist
		technic.remove_network(network_id)
		local sw_pos = technic.network2sw_pos(network_id)
		local tier = sw_pos and technic.sw_pos2tier(sw_pos)
		if not tier then
			-- Failed to get initial cable node for network
			return
		end
		network = {
			-- Build queue
			queue = {},
			-- Basic network data and lookup table for attached nodes
			id = network_id, tier = tier, all_nodes = {}, swpos = {},
			-- Indexed arrays for iteration by machine type
			PR_nodes = {}, RE_nodes = {}, BA_nodes = {},
			-- Power generation, usage and capacity related variables
			supply = 0, demand = 0, battery_charge = 0, battery_charge_max = 0,
			BA_count_active = 0, BA_charge_active = 0, battery_supply = 0, battery_demand = 0,
			-- Battery status update function
			update_battery = update_battery,
			-- Network activation and excution control
			timeout = 0, skip = 0, lag = 0, average_lag = sma(5)
		}
		-- Add first cable (one that is holding network id) and build network
		add_cable_node(technic.network2pos(network_id), network)
	end
	-- Continue building incomplete network
	technic.add_network_branch(network.queue, network)
	network.battery_count = #network.BA_nodes
	-- Add newly built network to cache array
	networks[network_id] = network
	if not network.queue then
		-- And return producers, batteries and receivers (should this simply return network?)
		return network.PR_nodes, network.BA_nodes, network.RE_nodes
	end
end

--
-- Execute technic power network
--
local node_technic_run = {}
core.register_on_mods_loaded(function()
	for name, tiers in pairs(technic.machine_tiers) do
		local nodedef = core.registered_nodes[name]
		local on_construct = type(nodedef.on_construct) == "function" and nodedef.on_construct
		local on_destruct = type(nodedef.on_destruct) == "function" and nodedef.on_destruct
		local place_node = technic.place_network_node
		local remove_node = technic.remove_network_node
		core.override_item(name, {
			on_construct = on_construct
				and function(pos) on_construct(pos) place_node(pos, tiers, name) end
				or  function(pos) place_node(pos, tiers, name) end,
			on_destruct = on_destruct
				and function(pos) on_destruct(pos) remove_node(pos, tiers, name) end
				or  function(pos) remove_node(pos, tiers, name) end,
		})
	end
	for name, _ in pairs(technic.machine_tiers) do
		if type(core.registered_nodes[name].technic_run) == "function" then
			node_technic_run[name] = core.registered_nodes[name].technic_run
		end
	end
end)

local function run_nodes(list, run_stage, network)
	for _, pos in ipairs(list) do
		local node = core.get_node_or_nil(pos)
		if not node then
			core.load_area(pos, pos)
			node = core.get_node_or_nil(pos)
		end
		if node and node.name and node_technic_run[node.name] then
			node_technic_run[node.name](pos, node, run_stage, network)
		end
	end
end

function technic.network_run(network_id)
	--
	-- !!! !!! !!! !!! !!! !!! !!! !!! !!! !!! !!!
	-- TODO: This function requires a lot of cleanup
	-- It is moved here from switching_station.lua and still
	-- contain a lot of switching station specific stuff which
	-- should be removed and/or refactored.
	--

	local t0 = core.get_us_time()

	local PR_nodes
	local BA_nodes
	local RE_nodes

	local network = networks[network_id]
	if network then
		PR_nodes, BA_nodes, RE_nodes = get_network(network_id, network.tier)
		if not PR_nodes or technic.is_overloaded(network_id) then return end
	else
		--dprint("Not connected to a network")
		technic.network_infotext(network_id, S("@1 Has No Network", S("Switching Station")))
		return
	end

	-- Reset battery data for updates
	network.battery_charge = 0
	network.battery_charge_max = 0
	network.battery_supply = 0
	network.battery_demand = 0
	network.BA_count_active = 0
	network.BA_charge_active = 0

	run_nodes(PR_nodes, technic.producer, network)
	run_nodes(RE_nodes, technic.receiver, network)
	run_nodes(BA_nodes, technic.battery, network)

	-- Strings for the meta data
	local eu_demand_str = network.tier.."_EU_demand"
	local eu_input_str  = network.tier.."_EU_input"
	local eu_supply_str = network.tier.."_EU_supply"

	-- Distribute charge equally across multiple batteries.
	local charge_distributed = math.floor(network.BA_charge_active / network.BA_count_active)
	for n, pos1 in pairs(BA_nodes) do
		local meta1 = core.get_meta(pos1)
		if (meta1:get_int(eu_demand_str) ~= 0) then
			meta1:set_int("internal_EU_charge", charge_distributed)
		end
	end

	-- Get all the power from the PR nodes
	local PR_eu_supply = 0 -- Total power
	for _, pos1 in pairs(PR_nodes) do
		local meta1 = core.get_meta(pos1)
		PR_eu_supply = PR_eu_supply + meta1:get_int(eu_supply_str)
	end
	--dprint("Total PR supply:"..PR_eu_supply)

	-- Get all the demand from the RE nodes
	local RE_eu_demand = 0
	for _, pos1 in pairs(RE_nodes) do
		local meta1 = core.get_meta(pos1)
		RE_eu_demand = RE_eu_demand + meta1:get_int(eu_demand_str)
	end
	--dprint("Total RE demand:"..RE_eu_demand)

	technic.network_infotext(network_id, S("@1. Supply: @2 Demand: @3",
			S("Switching Station"), technic.EU_string(PR_eu_supply),
			technic.EU_string(RE_eu_demand)))

	-- Data that will be used by the power monitor
	network.supply = PR_eu_supply
	network.demand = RE_eu_demand
	network.battery_count = #BA_nodes

	-- If the PR supply is enough for the RE demand supply them all
	local BA_eu_demand = network.battery_demand
	if PR_eu_supply >= RE_eu_demand then
	--dprint("PR_eu_supply"..PR_eu_supply.." >= RE_eu_demand"..RE_eu_demand)
		for _, pos1 in pairs(RE_nodes) do
			local meta1 = core.get_meta(pos1)
			local eu_demand = meta1:get_int(eu_demand_str)
			meta1:set_int(eu_input_str, eu_demand)
		end
		-- We have a surplus, so distribute the rest equally to the BA nodes
		-- Let's calculate the factor of the demand
		PR_eu_supply = PR_eu_supply - RE_eu_demand
		local charge_factor = 0 -- Assume all batteries fully charged
		if BA_eu_demand > 0 then
			charge_factor = PR_eu_supply / BA_eu_demand
		end
		-- TODO: EU input for all batteries: math.floor(BA_eu_demand * charge_factor * #BA_nodes)
		for n, pos1 in pairs(BA_nodes) do
			local meta1 = core.get_meta(pos1)
			local eu_demand = meta1:get_int(eu_demand_str)
			meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor))
			--dprint("Charging battery:"..math.floor(eu_demand*charge_factor))
		end
		local t1 = core.get_us_time()
		local diff = t1 - t0
		if diff > 50000 then
			core.log("warning", "[technic] [+supply] technic_run took " .. diff .. " us at "
				.. core.pos_to_string(core.get_position_from_hash(network_id)))
		end

		return
	end

	-- If the PR supply is not enough for the RE demand we will discharge the batteries too
	local BA_eu_supply = network.battery_supply
	if PR_eu_supply + BA_eu_supply >= RE_eu_demand then
		--dprint("PR_eu_supply "..PR_eu_supply.."+BA_eu_supply "..BA_eu_supply.." >= RE_eu_demand"..RE_eu_demand)
		for _, pos1 in pairs(RE_nodes) do
			local meta1  = core.get_meta(pos1)
			local eu_demand = meta1:get_int(eu_demand_str)
			meta1:set_int(eu_input_str, eu_demand)
		end
		-- We have a deficit, so distribute to the BA nodes
		-- Let's calculate the factor of the supply
		local charge_factor = 0 -- Assume all batteries depleted
		if BA_eu_supply > 0 then
			charge_factor = (PR_eu_supply - RE_eu_demand) / BA_eu_supply
		end
		for n,pos1 in pairs(BA_nodes) do
			local meta1 = core.get_meta(pos1)
			local eu_supply = meta1:get_int(eu_supply_str)
			meta1:set_int(eu_input_str, math.floor(eu_supply * charge_factor))
			--dprint("Discharging battery:"..math.floor(eu_supply*charge_factor))
		end
		local t1 = core.get_us_time()
		local diff = t1 - t0
		if diff > 50000 then
			core.log("warning", "[technic] [-supply] technic_run took " .. diff .. " us at "
				.. core.pos_to_string(core.get_position_from_hash(network_id)))
		end

		return
	end

	-- If the PR+BA supply is not enough for the RE demand: Power only the batteries
	local charge_factor = 0 -- Assume all batteries fully charged
	if BA_eu_demand > 0 then
		charge_factor = PR_eu_supply / BA_eu_demand
	end
	for n, pos1 in pairs(BA_nodes) do
		local meta1 = core.get_meta(pos1)
		local eu_demand = meta1:get_int(eu_demand_str)
		meta1:set_int(eu_input_str, math.floor(eu_demand * charge_factor))
	end
	for n, pos1 in pairs(RE_nodes) do
		local meta1 = core.get_meta(pos1)
		meta1:set_int(eu_input_str, 0)
	end

	local t1 = core.get_us_time()
	local diff = t1 - t0
	if diff > 50000 then
		core.log("warning", "[technic] technic_run took " .. diff .. " us at "
			.. core.pos_to_string(core.get_position_from_hash(network_id)))
	end

end
