--[[

	TechAge
	=======

	Copyright (C) 2019-2022 Joachim Stolberg

	AGPL v3
	See LICENSE.txt for more information

	Consumer node basis functionality.
	It handles:
	- up to 4 stages of nodes (TA2/TA3/TA4/TA5)
	- power consumption
	- node state handling
	- registration of passive and active nodes
	- Tube connections are on left and right side (from left to right)
	- Power connection are on front and back side (front or back)
]]--

-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta

-- Consumer Related Data
local CRD = function(pos) return (minetest.registered_nodes[techage.get_node_lvm(pos).name] or {}).consumer end
local CRDN = function(node) return (minetest.registered_nodes[node.name] or {}).consumer end

local Tube = techage.Tube
local power = networks.power
local liquid = networks.liquid
local CYCLE_TIME = 2

local function get_keys(tbl)
	local keys = {}
	for k,v in pairs(tbl) do
		keys[#keys + 1] = k
	end
	return keys
end

local function has_power(pos, nvm, state)
	local crd = CRD(pos)
	return power.power_available(pos, crd.power_netw)
end

local function start_node(pos, nvm, state)
end

local function stop_node(pos, nvm, state)
end

local function node_timer_pas(pos, elapsed)
	local crd = CRD(pos)
	local nvm = techage.get_nvm(pos)

	-- handle power consumption
	if crd.power_netw and techage.needs_power(nvm) then
		local consumed = power.consume_power(pos, crd.power_netw, nil, crd.power_consumption)
		if consumed == crd.power_consumption then
			crd.State:start(pos, nvm)
		end
	end

		-- call the node timer routine
	if techage.is_operational(nvm) then
		nvm.node_timer_call_cyle = (nvm.node_timer_call_cyle or 0) + 1
		if nvm.node_timer_call_cyle >= crd.call_cycle then
			crd.node_timer(pos, crd.cycle_time)
			nvm.node_timer_call_cyle = 0
		end
	end
	return crd.State:is_active(nvm)
end

local function node_timer_act(pos, elapsed)
	local crd = CRD(pos)
	local nvm = techage.get_nvm(pos)

	-- handle power consumption
	if crd.power_netw and techage.needs_power(nvm) then
		local consumed = power.consume_power(pos, crd.power_netw, nil, crd.power_consumption)
		if consumed < crd.power_consumption then
			crd.State:nopower(pos, nvm)
		end
	end

	-- call the node timer routine
	if techage.is_operational(nvm) then
		nvm.node_timer_call_cyle = (nvm.node_timer_call_cyle or 0) + 1
		if nvm.node_timer_call_cyle >= crd.call_cycle then
			crd.node_timer(pos, crd.cycle_time)
			nvm.node_timer_call_cyle = 0
		end
	end
	return crd.State:is_active(nvm)
end

local function prepare_tiles(tiles, stage, power_png)
	local tbl = {}
	for _,item in ipairs(tiles) do
		if type(item) == "string" then
			tbl[#tbl+1] = item:gsub("#", stage):gsub("{power}", power_png):gsub("@@", '#')
		else
			local temp = table.copy(item)
			temp.image = temp.image:gsub("#", stage):gsub("{power}", power_png):gsub("@@", '#')
			tbl[#tbl+1] = temp
		end
	end
	return tbl
end

-- 'validStates' is optional and can be used to e.g. enable
-- only one TA2 node {false, true, false, false}
function techage.register_consumer(base_name, inv_name, tiles, tNode, validStates, node_name_prefix)
	local names = {}
	validStates = validStates or {true, true, true, true}
	if not node_name_prefix then
		node_name_prefix = "techage:ta"
	end
	for stage = 2,5 do
		local name_pas = node_name_prefix..stage.."_"..base_name.."_pas"
		local name_act = node_name_prefix..stage.."_"..base_name.."_act"
		local name_inv = "TA"..stage.." "..inv_name
		names[#names+1] = name_pas

		if validStates[stage] then
			local power_network
			local power_png = 'techage_axle_clutch.png'
			local power_used = tNode.power_consumption ~= nil
			local sides
			-- power needed?
			if power_used then
				if stage > 2 then
					power_network = techage.ElectricCable
					power_png = 'techage_appl_hole_electric.png'
					sides = get_keys(tNode.power_sides or {F=1, B=1, U=1, D=1})
				else
					power_network = techage.Axle
					power_png = 'techage_axle_clutch.png'
					sides = get_keys(tNode.power_sides or {F=1, B=1, U=1, D=1})
				end
			end

			local tState = techage.NodeStates:new({
				node_name_passive = name_pas,
				node_name_active = name_act,
				infotext_name = name_inv,
				cycle_time = CYCLE_TIME,
				standby_ticks = tNode.standby_ticks,
				formspec_func = tNode.formspec,
				on_state_change = tNode.on_state_change,
				can_start = tNode.can_start,
				quick_start = tNode.quick_start,
				has_power = tNode.has_power or power_used and has_power or nil,
				start_node = power_used and start_node or nil,
				stop_node = power_used and stop_node or nil,
			})

			local tConsumer = {
				stage = stage,
				State = tState,
				-- number of items to be processed per cycle
				num_items = tNode.num_items and tNode.num_items[stage],
				power_consumption = power_used and
				tNode.power_consumption[stage] or 0,
				node_timer = tNode.node_timer,
				cycle_time = tNode.cycle_time,
				call_cycle = tNode.cycle_time / 2,
				power_netw = power_network,
			}

			local after_place_node = function(pos, placer, itemstack, pointed_thing)
				local crd = CRD(pos)
				local meta = M(pos)
				local nvm = techage.get_nvm(pos)
				local node = minetest.get_node(pos)
				meta:set_int("push_dir", techage.side_to_indir("L", node.param2))
				meta:set_int("pull_dir", techage.side_to_indir("R", node.param2))
				meta:set_string("owner", placer:get_player_name())
				-- Delete existing node number. Needed for Digtron compatibility.
				if (meta:contains("node_number")) then
					meta:set_string("node_number", "")
				end
				local number = techage.add_node(pos, name_pas, stage == 2)
				if crd.power_netw then
					crd.power_netw:after_place_node(pos)
				end
				if tNode.after_place_node then
					tNode.after_place_node(pos, placer, itemstack, pointed_thing)
				end
				crd.State:node_init(pos, nvm, number)
			end

			local after_dig_node = function(pos, oldnode, oldmetadata, digger)
				if tNode.after_dig_node then
					tNode.after_dig_node(pos, oldnode, oldmetadata, digger)
				end
				local crd = CRDN(oldnode)
				if crd.power_netw then
					crd.power_netw:after_dig_node(pos)
				end
				techage.remove_node(pos, oldnode, oldmetadata)
				techage.del_mem(pos)
			end

			tNode.groups.not_in_creative_inventory = 0

			local def_pas = {
				description = name_inv,
				tiles = prepare_tiles(tiles.pas, stage, power_png),
				consumer = tConsumer,
				drawtype = tNode.drawtype,
				node_box = tNode.node_box,
				selection_box = tNode.selection_box,

				can_dig = tNode.can_dig,
				on_rotate = tNode.on_rotate or screwdriver.disallow,
				on_timer = node_timer_pas,
				on_receive_fields = tNode.on_receive_fields,
				on_rightclick = tNode.on_rightclick,
				after_place_node = after_place_node,
				after_dig_node = after_dig_node,
				preserve_metadata = tNode.preserve_metadata,
				allow_metadata_inventory_put = tNode.allow_metadata_inventory_put,
				allow_metadata_inventory_move = tNode.allow_metadata_inventory_move,
				allow_metadata_inventory_take = tNode.allow_metadata_inventory_take,
				on_metadata_inventory_move = tNode.on_metadata_inventory_move,
				on_metadata_inventory_put = tNode.on_metadata_inventory_put,
				on_metadata_inventory_take = tNode.on_metadata_inventory_take,
				ta_rotate_node = tNode.ta_rotate_node,
				ta3_formspec = stage == 3 and tNode.ta3_formspec,
				ta4_formspec = stage == 4 and tNode.ta4_formspec,

				paramtype = tNode.paramtype,
				paramtype2 = "facedir",
				drop = tNode.drop,
				groups = table.copy(tNode.groups),
				is_ground_content = false,
				sounds = tNode.sounds,
			}

			-- Copy custom properties (starting with an underscore)
			for k,v in pairs(tNode) do
				if string.sub(k, 1, 1) == "_" then
					def_pas[k] = v
				end
			end

			minetest.register_node(name_pas, def_pas)

			tNode.groups.not_in_creative_inventory = 1

			local def_act = {
				description = name_inv,
				tiles = prepare_tiles(tiles.act, stage, power_png),
				consumer = tConsumer,
				drawtype = tNode.drawtype,
				node_box = tNode.node_box,
				selection_box = tNode.selection_box,

				on_rotate = tNode.on_rotate or screwdriver.disallow,
				on_timer = node_timer_act,
				on_receive_fields = tNode.on_receive_fields,
				on_rightclick = tNode.on_rightclick,
				after_place_node = after_place_node,
				after_dig_node = after_dig_node,
				allow_metadata_inventory_put = tNode.allow_metadata_inventory_put,
				allow_metadata_inventory_move = tNode.allow_metadata_inventory_move,
				allow_metadata_inventory_take = tNode.allow_metadata_inventory_take,
				on_metadata_inventory_move = tNode.on_metadata_inventory_move,
				on_metadata_inventory_put = tNode.on_metadata_inventory_put,
				on_metadata_inventory_take = tNode.on_metadata_inventory_take,
				ta_rotate_node = tNode.ta_rotate_node,
				ta3_formspec = stage == 3 and tNode.ta3_formspec,
				ta4_formspec = stage == 4 and tNode.ta4_formspec,

				paramtype = tNode.paramtype,
				paramtype2 = "facedir",
				drop = "",
				diggable = false,
				groups = table.copy(tNode.groups),
				is_ground_content = false,
				sounds = tNode.sounds,
			}

			-- Copy custom properties (starting with an underscore)
			for k,v in pairs(tNode) do
				if string.sub(k, 1, 1) == "_" then
					def_act[k] = v
				end
			end

			minetest.register_node(name_act, def_act)

			if power_used then
				power.register_nodes({name_pas, name_act}, power_network, "con", sides)
			end
			techage.register_node({name_pas, name_act}, tNode.tubing)

			if tNode.tube_sides then
				Tube:set_valid_sides(name_pas, get_keys(tNode.tube_sides))
				Tube:set_valid_sides(name_act, get_keys(tNode.tube_sides))
			end
		end
	end
	return names[1], names[2], names[3], names[4]
end
