--[[

	TechAge
	=======

	Copyright (C) 2019-2020 Joachim Stolberg

	AGPL v3
	See LICENSE.txt for more information
	
	API for Power Nodes

]]--

--local P2S = minetest.pos_to_string
--local M = minetest.get_meta
--local N = function(pos) return minetest.get_node(pos).name end
--local S = techage.S

local net_def = techage.networks.net_def
local networks = techage.networks

-- Consumer States
local STOPPED = 1
local NOPOWER = 2
local RUNNING = 3

techage.power = {}

techage.power.STOPPED = STOPPED
techage.power.NOPOWER = NOPOWER
techage.power.RUNNING = RUNNING

-- determine network ID (largest hash number of all generators)
local function determine_netID(pos, outdir, Cable)
	local netID = 0
	networks.connection_walk(pos, outdir, Cable, function(pos, indir, node)
		local ntype = net_def(pos, Cable.tube_type).ntype
		if ntype ~= "junc" then
			local new = minetest.hash_node_position(pos)
			if netID <= new then
				netID = new
			end
		end
	end)
	return netID
end

-- store network ID on each node
local function store_netID(pos, outdir, netID, Cable)
	networks.connection_walk(pos, outdir, Cable, function(pos, indir, node)
		--techage.mark_position("singleplayer", pos, "store", "", 2)-----------------------------------------
		--print(node.name, dump(net_def(pos, Cable.tube_type)))
		if net_def(pos, Cable.tube_type) then
			local nvm = techage.get_nvm(pos)
			nvm[Cable.tube_type] = nvm[Cable.tube_type] or {}
			nvm[Cable.tube_type]["netID"] = netID
		end
	end)
end

-- delete network and ID on each node
local function delete_netID(pos, outdir, Cable)
	local netID = 0
	networks.connection_walk(pos, outdir, Cable, function(pos, indir, node)
		--techage.mark_position("singleplayer", pos, "delete", "", 2)----------------------------------------
		if net_def(pos, Cable.tube_type) then
			local nvm = techage.get_nvm(pos)
			if nvm[Cable.tube_type] and nvm[Cable.tube_type]["netID"] then
				netID = nvm[Cable.tube_type]["netID"]
				nvm[Cable.tube_type]["netID"] = nil
			end
		end
	end)
	networks.delete_network(Cable.tube_type, netID)
end

-- Keep the network up and running
local function trigger_network(pos, outdir, Cable)
	local nvm = techage.get_nvm(pos)
	local netID = nvm[Cable.tube_type] and nvm[Cable.tube_type]["netID"]
	if not netID then
		--print("determine_netID !!!!!!!!!!!!!!!!!!!!")
		netID = determine_netID(pos, outdir, Cable)
		store_netID(pos, outdir, netID, Cable)
		networks.build_network(pos, outdir, Cable, netID)
	elseif not networks.get_network(Cable.tube_type, netID) then
		--print("build_network !!!!!!!!!!!!!!!!!!!!")
		netID = determine_netID(pos, outdir, Cable)
		store_netID(pos, outdir, netID, Cable)
		networks.build_network(pos, outdir, Cable, netID)
	end
end

local function build_network_consumer(pos, Cable)
	local outdirs = techage.networks.get_node_connections(pos, Cable.tube_type)
	if #outdirs == 1 then
		local netID = determine_netID(pos, outdirs[1], Cable)
		store_netID(pos, outdirs[1], netID, Cable)
		networks.build_network(pos, outdirs[1], Cable, netID)
	end
end

-- To be called from each node via 'tubelib2_on_update2'
-- 'output' is optional and only needed for nodes with dedicated
-- pipe sides (e.g. pumps).
function techage.power.update_network(pos, outdir, Cable)
	networks.node_connections(pos, Cable) -- update node internal data
	delete_netID(pos, outdir, Cable) -- network walk to delete all IDs
end

--
-- Read the current power value from all connected devices (used for solar cells)
-- Only used by the solar inverter to collect the power of all solar cells.
-- Only one inverter per network is allowed. Therefore, we have to check,
-- if additional inverters are in the network.
-- Function returns in addition the number of found inverters.
function techage.power.get_power(pos, outdir, Cable, inverter)
	local sum = 0
	local num_inverter = 0
	networks.connection_walk(pos, outdir, Cable, function(pos, indir, node)
		--techage.mark_position("singleplayer", pos, "get_power", "", 2)-----------------------------------------
		local def = net_def(pos, Cable.tube_type)
		if def and def.on_getpower then
			sum = sum + def.on_getpower(pos)
		else
			local node = techage.get_node_lvm(pos)
			if node.name == inverter then
				num_inverter = num_inverter + 1
			end
		end
	end)
	return sum, num_inverter
end	



--
-- Consumer related functions
--

-- function checks for a power grid, not for enough power
function techage.power.power_available(pos, Cable)
	local nvm = techage.get_nvm(pos)
	local tlib_type = Cable.tube_type
	local netID = nvm[Cable.tube_type] and nvm[Cable.tube_type]["netID"]
	local netw = networks.has_network(tlib_type, netID)
	return netw and netw.on and netw.alive and netw.alive > 0
end

function techage.power.network_overloaded(pos, Cable)
	local nvm = techage.get_nvm(pos)
	local tlib_type = Cable.tube_type
	local netID = nvm[Cable.tube_type] and nvm[Cable.tube_type]["netID"]
	local netw = networks.has_network(tlib_type, netID)
	if netw then
		local sum = (netw.available1 or 0) + (netw.available2 or 0)
		return sum > 0 and sum < (netw.needed1 or 0)
	end
end

-- this is more a try to start, the start will be performed by on_power()
function techage.power.consumer_start(pos, Cable, cycle_time)
	local nvm = techage.get_nvm(pos)
	local tlib_type = Cable.tube_type
	nvm[tlib_type] = nvm[tlib_type] or {}
	nvm[tlib_type]["calive"] = (cycle_time / 2) + 1
	nvm[tlib_type]["cstate"] = NOPOWER
	nvm[tlib_type]["taken"] = 0
end

function techage.power.consumer_stop(pos, Cable)
	local nvm = techage.get_nvm(pos)
	local tlib_type = Cable.tube_type
	nvm[tlib_type] = nvm[tlib_type] or {}
	nvm[tlib_type]["calive"] = -1
	nvm[tlib_type]["cstate"] = STOPPED
	nvm[tlib_type]["taken"] = 0
end

function techage.power.consumer_alive(pos, Cable, cycle_time)
	local nvm = techage.get_nvm(pos)
	local def = nvm[Cable.tube_type] -- power related network data
	if def then
		-- if network is deleted (cable removed/placed) rebuild it to prevent flickering lights
		if not def["netID"] or not networks.get_network(Cable.tube_type, def["netID"]) then
			build_network_consumer(pos, Cable)
		end
		local rv = (cycle_time / 2) + 1
		if def["netID"] and def["calive"] and def["calive"] < rv then -- network available
			def["calive"] = rv
			return def["taken"] or 0
		elseif not def["cstate"] or def["cstate"] == RUNNING then
			local ndef = net_def(pos, Cable.tube_type)
			ndef.on_nopower(pos, Cable.tube_type)
			def["cstate"] = NOPOWER
		end
	else
		local ndef = net_def(pos, Cable.tube_type)
		ndef.on_nopower(pos, Cable.tube_type)
	end
	return 0
end

--
-- Generator related functions
--
-- curr_power is optional, only needed for generators with variable output power
function techage.power.generator_start(pos, Cable, cycle_time, outdir, curr_power)
	local nvm = techage.get_nvm(pos)
	local tlib_type = Cable.tube_type
	nvm[tlib_type] = nvm[tlib_type] or {}
	nvm[tlib_type]["galive"] = (cycle_time / 2) + 2
	nvm[tlib_type]["gstate"] = RUNNING
	nvm[tlib_type]["given"] = 0
	nvm[tlib_type]["curr_power"] = curr_power
	trigger_network(pos, outdir, Cable)
end

function techage.power.generator_stop(pos, Cable, outdir)
	local nvm = techage.get_nvm(pos)
	local tlib_type = Cable.tube_type
	nvm[tlib_type] = nvm[tlib_type] or {}
	nvm[tlib_type]["galive"] = -1
	nvm[tlib_type]["gstate"] = STOPPED
	nvm[tlib_type]["given"] = 0
end

-- curr_power is optional, only needed for generators with variable output power
function techage.power.generator_alive(pos, Cable, cycle_time, outdir, curr_power)
	local nvm = techage.get_nvm(pos)
	local def = nvm[Cable.tube_type] -- power related network data
	if def then
		trigger_network(pos, outdir, Cable)
		def["galive"] = (cycle_time / 2) + 2
		def["curr_power"] = curr_power
		return def["given"] or 0
	end
	return 0
end

-- Calculate the needed power over all con1 consumers
function techage.power.needed_power(pos, Cable, outdir)
	local sum = 0
	networks.connection_walk(pos, outdir, Cable, function(pos, indir, node)
		local net = net_def(pos, Cable.tube_type) -- network definition
		if net.ntype == "con1" then
			local nvm = techage.get_nvm(pos)
			local def = nvm[Cable.tube_type] -- power related data
		
			if def and def["cstate"] ~= STOPPED then
				if def["calive"] >= 0 then
					sum = sum + (net.nominal or def.curr_power or 0)
				end
			end
		end
	end)
	return sum
end	

-- function delete_netID(pos, outdir, Cable)
techage.power.delete_netID = delete_netID
