--[[

	TechAge
	=======

	Copyright (C) 2019-2020 Joachim Stolberg

	AGPL v3
	See LICENSE.txt for more information
	
	Liquid transportation API via Pipe(s) (peer, put, take)

]]--

local P2S = minetest.pos_to_string
local M = minetest.get_meta
local N = function(pos) return minetest.get_node(pos).name end
local LQD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).liquid end
local Pipe = techage.LiquidPipe
local S = techage.S

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

local liquid = techage.liquid

--
-- Networks
--

-- determine network ID (largest hash number of all pumps)
local function determine_netID(pos, outdir)
	local netID = 0
	networks.connection_walk(pos, outdir, Pipe, function(pos, indir, node)
		local ntype = net_def(pos, "pipe2").ntype
		if ntype and ntype == "pump" then
			local new = minetest.hash_node_position(pos) * 8 + outdir
			if netID <= new then
				netID = new
			end
		end
	end)
	return netID
end

-- store network ID on each pump like node
local function store_netID(pos, outdir, netID)
	networks.connection_walk(pos, outdir, Pipe, function(pos, indir, node)
		local ntype = net_def(pos, "pipe2").ntype
		if ntype and ntype == "pump" then
			local nvm = techage.get_nvm(pos)
			local outdir = networks.Flip[indir]
			nvm.pipe2 = nvm.pipe2 or {}
			nvm.pipe2.netIDs = nvm.pipe2.netIDs or {}
			nvm.pipe2.netIDs[outdir] = netID
		end
	end)
end

-- delete network and ID on each pump like node
local function delete_netID(pos, outdir)
	local netID = 0
	networks.connection_walk(pos, outdir, Pipe, function(pos, indir, node)
		local ntype = net_def(pos, "pipe2").ntype
		if ntype and ntype == "pump" then
			local nvm = techage.get_nvm(pos)
			local outdir = networks.Flip[indir]
			if nvm.pipe2 and nvm.pipe2.netIDs and nvm.pipe2.netIDs[outdir] then
				netID = nvm.pipe2.netIDs[outdir]
				nvm.pipe2.netIDs[outdir] = nil
			end
		end
	end)
	networks.delete_network("pipe2", netID)
end

local function get_netID(pos, outdir)
	local nvm = techage.get_nvm(pos)
	if not nvm.pipe2 or not nvm.pipe2.netIDs or not nvm.pipe2.netIDs[outdir] then
		local netID = determine_netID(pos, outdir)
		store_netID(pos, outdir, netID)
	end
	return nvm.pipe2 and nvm.pipe2.netIDs and nvm.pipe2.netIDs[outdir]
end

-- return list of nodes {pos = ..., indir = ...} of given type
local function get_network_table(pos, outdir, ntype)
	local netID = get_netID(pos, outdir)
	if netID then
		local netw = networks.get_network("pipe2", netID)
		if not netw then
			netw = networks.collect_network_nodes(pos, outdir, Pipe)
			networks.set_network("pipe2", netID, netw)
		end
		if not netw[ntype] then -- connection lost (e.g. tank cart)?
			-- reactivate network
			networks.node_connections(pos, Pipe)
			delete_netID(pos, outdir)
		end
		--print("netw", string.format("%012X", netID),  dump(netw[ntype]))
		return netw[ntype] or {}
	end
	return {}
end


--
-- Client remote functions
--

-- Determine and return liquid 'name' from the
-- remote inventory.
function liquid.peek(pos, outdir)
	for _,item in ipairs(get_network_table(pos, outdir, "tank")) do
		local liquid = LQD(item.pos)
		if liquid and liquid.peek then
			return liquid.peek(item.pos, item.indir)
		end
	end
end

-- Add given amount of liquid to the remote inventory.
-- return leftover amount
function liquid.put(pos, outdir, name, amount, player_name)
	for _,item in ipairs(get_network_table(pos, outdir, "tank")) do
		local liquid = LQD(item.pos)
		if liquid and liquid.put and liquid.peek then
			-- wrong items?
			local peek = liquid.peek(item.pos, item.indir)
			if peek and peek ~= name then return amount or 0 end
			if player_name then
				local num = techage.get_node_number(pos) or "000"
				techage.mark_position(player_name, item.pos, "("..num..") put", "", 1)
			end
			amount = liquid.put(item.pos, item.indir, name, amount)
			if not amount or amount == 0 then break end
		end
	end
	return amount or 0
end

-- Take given amount of liquid from the remote inventory.
-- return taken amount and item name
function liquid.take(pos, outdir, name, amount, player_name)
	local taken = 0
	local item_name = nil
	for _,item in ipairs(get_network_table(pos, outdir, "tank")) do
		local liquid = LQD(item.pos)
		if liquid and liquid.take then
			if player_name then
				local num = techage.get_node_number(pos)
				techage.mark_position(player_name, item.pos, "("..num..") take", "", 1)
			end
			local val, name = liquid.take(item.pos, item.indir, name, amount - taken)
			if val and name then
				taken = taken + val
				item_name = name
				if amount - taken == 0 then break end
			end
		end
	end
	return taken, item_name
end

function liquid.untake(pos, outdir, name, amount)
	for _,item in ipairs(get_network_table(pos, outdir, "tank")) do
		local liquid = LQD(item.pos)
		if liquid and liquid.untake then
			amount = liquid.untake(item.pos, item.indir, name, amount)
			if not amount or amount == 0 then break end
		end
	end
	return amount or 0
end

--
-- Server local functions
--

function liquid.srv_peek(pos, indir)
	local nvm = techage.get_nvm(pos)
	nvm.liquid = nvm.liquid or {}
	return nvm.liquid.name
end

function liquid.srv_put(pos, indir, name, amount)
	local nvm = techage.get_nvm(pos)
	nvm.liquid = nvm.liquid or {}
	amount = amount or 0
	if not nvm.liquid.name then
		nvm.liquid.name = name
		nvm.liquid.amount = amount
		return 0
	elseif nvm.liquid.name == name then
		nvm.liquid.amount = nvm.liquid.amount or 0
		local capa = LQD(pos).capa
		if nvm.liquid.amount + amount <= capa then
			nvm.liquid.amount = nvm.liquid.amount + amount
			return 0
		else
			local rest = nvm.liquid.amount + amount - capa
			nvm.liquid.amount = capa
			return rest
		end
	end
	return amount
end

function liquid.srv_take(pos, indir, name, amount)
	local nvm = techage.get_nvm(pos)
	nvm.liquid = nvm.liquid or {}
	amount = amount or 0
	if not name or nvm.liquid.name == name then
		name = nvm.liquid.name
		nvm.liquid.amount = nvm.liquid.amount or 0
		if nvm.liquid.amount > amount then
			nvm.liquid.amount = nvm.liquid.amount - amount
			return amount, name
		else 
			local rest = nvm.liquid.amount
			local name = nvm.liquid.name
			nvm.liquid.amount = 0
			nvm.liquid.name = nil
			return rest, name
		end
	end
	return 0
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 liquid.update_network(pos, outdir)
	networks.node_connections(pos, Pipe)
	delete_netID(pos, outdir)
end

-- To be called from each pump in 'after_dig_node'
-- before calling 'techage.del_mem(pos)'
function liquid.after_dig_pump(pos)
	local nvm = techage.get_nvm(pos)
	if nvm.pipe2 and nvm.pipe2.netIDs then
		for outdir, netID in pairs(nvm.pipe2.netIDs) do
			networks.delete_network("pipe2", netID)
		end
	end
end