local function round(x)
	if x >= 0 then
		return math.floor(x+0.5)
	else
		return math.ceil(x-0.5)
	end
end


local function wakeup(pos,share)
	--called to test if a node needs to be woken up
	local node = minetest.get_node(pos)
	if (minetest.get_item_group(node.name, "sparktech_energy_wakeup") >= 1) then
		local ntype = minetest.get_item_group(node.name, "spaktech_energy_type")
		local meta = minetest.get_meta(pos)
		local energy = meta:get_int("energy")
		if (energy == share) then return end -- only makes sense if we actually change the energy in the block
		if ntype == 2 then
			if energy == 0 then
				minetest.get_node_timer(pos):start(0.5)
			end
		elseif ntype == 4 then
			if minetest.get_item_group(node.name, "sparktech_energy_wakeup") <= energy then -- set the value to when you want to be woken up key
				minetest.get_node_timer(pos):start(0.5)
			end
		end
	end
end


-- internal var net_master stores all slave nodes
-- sparktech_net_passive = are used to follow nets, do not count towards energy
-- sparktech_net_trigger = trigger updates
-- Updates blocks every seconds


local function net_get_slaves(master)
	local slaves = {}
	local meta = minetest.get_meta(master)
	local slave_s = meta:get_string("net_master")
	slave_s = string.sub(slave_s, 2)
	local slave_s_dict = string.split(slave_s, "|")
	for _, val in pairs(slave_s_dict) do
			local cor = string.split(val, ":")
			slaves[#slaves + 1] = { x=tonumber(cor[1]), y=tonumber(cor[2]), z=tonumber(cor[3])}
	end
	return slaves
end


local function net_distribute(pos)
	-- sparktech_energy_type types:
	--   2 - Producer (gets emptied)
	--   3 - Storage  (backup / overflow)
	--   4 - Consumer (gets filled)

	local nodes = net_get_slaves(pos)
	nodes[#nodes +1] = pos
	-- all nodes are in local now

	-- Gather all information we need for the three node types
	local energy_pr = 0
	local energy_st = 0
	local energy_co = 0
	local max_energy_pr = 0
	local max_energy_st = 0
	local max_energy_co = 0
	for x = 1, #nodes do
		local ntype = minetest.get_item_group(minetest.get_node(nodes[x]).name, "sparktech_energy_type")
		local nenergy = minetest.get_meta(nodes[x]):get_int("energy")
		local nenergy_max = minetest.get_item_group(minetest.get_node(nodes[x]).name, "sparktech_energy_max")
		if ntype == 2 then
			energy_pr = energy_pr + nenergy
			max_energy_pr = max_energy_pr + nenergy_max
		elseif ntype == 3 then
			energy_st = energy_st + nenergy
			max_energy_st = max_energy_st + nenergy_max
		elseif ntype == 4 then
			energy_co = energy_co + nenergy
			max_energy_co = max_energy_co + nenergy_max
		end
	end

	-- now we know all energy in the system, now to distribute it

	local pr_new = energy_pr
	local st_new = energy_st
	local co_new = energy_co

	-- How much energy is available to be put into storage / is missing to fill up the consumers
	local prod_buf = energy_pr - (max_energy_co - energy_co)

	-- Producers -> Consumers transfer
	if prod_buf >= 0 then
		-- Fill up the consumers to their limit
		pr_new = prod_buf
		co_new = co_new + (energy_pr - prod_buf)
	else
		-- Empty the producers into the consumers
		pr_new = 0
		co_new = co_new + energy_pr
	end

	-- Storage -> Consumers and Producers -> Storage transfers
	if prod_buf < 0 then
		-- Fill up the consumers with energy in storage
		local sto_buf = energy_st + prod_buf
		if sto_buf >= 0 then
			st_new = sto_buf
			co_new = co_new + energy_st - sto_buf
		else
			st_new = 0
			co_new = co_new + energy_st
		end
	else
		-- Store excess energy into storage
		local st_cap = max_energy_st - energy_st
		if st_cap >= prod_buf then
			pr_new = 0
			st_new = st_new + prod_buf
		else
			pr_new = prod_buf - st_cap
			st_new = st_new + st_cap
		end
	end

	-- Now the new energy distribution is known

	-- Apply the later to be improved (maybe) share system to all node types
	local share_pr = pr_new / max_energy_pr;
	local share_st = st_new / max_energy_st;
	local share_co = co_new / max_energy_co;
	for x = 1, #nodes do
		local meta = minetest.get_meta(nodes[x])
		local node = minetest.get_node(nodes[x])
		local ntype = minetest.get_item_group(node.name, "sparktech_energy_type")
		local nmax = minetest.get_item_group(node.name, "sparktech_energy_max")
		if ntype == 2 then --produces
			wakeup(nodes[x], round(share_pr * nmax)) -- TODO: test this
			meta:set_int("energy", round(share_pr * nmax))
		elseif ntype == 3 then --stores
			meta:set_int("energy", round(share_st * nmax))
		elseif ntype == 4 then --consumes
			wakeup(nodes[x], round(share_co * nmax))
			meta:set_int("energy", round(share_co * nmax))
		end
	end
end


local function filter(pos)-- function is called severall times, only run if this is a master node
	local meta = minetest.get_meta(pos)
	local net_master = meta:get_string("net_master")
	if (net_master == nil or net_master == "") then -- if it is not set this is NOT a master node, so we can ignore it eh
		return
	else
		net_distribute(pos)
	end
end


minetest.register_abm({
	nodenames = {"group:sparktech_net_trigger"},
	interval = 1.0,
	chance = 1,
	catch_up = true,
	action = function(pos, node, active_object_count, active_object_count_wider)
		filter(pos)
	end
})
