-- see elepower_papi >> external_nodes_items.lua for explanation
-- shorten table ref
local epr = ele.external.ref
local epi = ele.external.ing

local c_air = minetest.get_content_id("air")

local function get_formspec(power, fluid, state, level)
	if not level then level = 0 end
	return "size[8,8.5]"..
		epr.gui_bg..
		epr.gui_bg_img..
		epr.gui_slots..
		ele.formspec.state_switcher(7, 2.5, state)..
		ele.formspec.power_meter(power)..
		ele.formspec.fluid_bar(7, 0, fluid)..
		"label[1,0;Pump level: "..level.."]"..
		"list[current_player;main;0,4.25;8,1;]"..
		"list[current_player;main;0,5.5;8,3;8]"..
		epr.get_hotbar_bg(0, 4.25)
end

-- Dig a node in a certain radius on the same Y level
local function dig_node_leveled_radius(pos, radius, node)
	local c_node = minetest.get_content_id(node)
	local startp = vector.subtract(pos, {x=radius,y=0,z=radius})
	local endp   = vector.add(pos, {x=radius,y=0,z=radius})

	local manip = minetest.get_voxel_manip()
	local e1, e2 = manip:read_from_map(startp, endp)
	local area = VoxelArea:new{MinEdge=e1, MaxEdge=e2}
	local data = manip:get_data()

	local dug = false

	for i in area:iter(
		startp.x, startp.y, startp.z,
		endp.x, endp.y, endp.z
	) do
		if data[i] == c_node then
			data[i] = c_air
			dug = true
			break
		end
	end

	if dug then
		manip:set_data(data)
		manip:write_to_map()
	end

	return dug
end

local function timer(pos, elapsed)
	local refresh = false
	local meta    = minetest.get_meta(pos)
	local inv     = meta:get_inventory()

	local machine_node = "elepower_machines:pump"
	local machine_def  = minetest.registered_nodes[machine_node]

	local capacity = ele.helpers.get_node_property(meta, pos, "capacity")
	local usage    = ele.helpers.get_node_property(meta, pos, "usage")
	local storage  = ele.helpers.get_node_property(meta, pos, "storage")
	local state    = meta:get_int("state")
	local comps    = meta:get_string("components")
	local status   = "Idle"

	local is_enabled = ele.helpers.state_enabled(meta, pos, state)
	local res_time = 0

	local pow_buffer = {capacity = capacity, storage = storage, usage = 0}
	local fl_buffer  = fluid_lib.get_buffer_data(pos, "pump")

	-- Pump level
	local plevel  = meta:get_int("level")
	local pliquid = meta:get_string("liquid")
	if plevel == 0 then plevel = -1 end

	local ppos = vector.add(pos, {x=0,y=plevel,z=0})

	local heavy = comps:match("elepower_machines:heavy_filter") ~= nil

	while true do
		if not is_enabled then
			status = "Off"
			break
		end

		if pow_buffer.storage < usage then
			status = "Out of Power!"
			break
		end

		local dig_node = pliquid
		local amount = 1000
		if pliquid == "elepower_nuclear:heavy_water_source" and heavy then
			dig_node = epr.water_source
			amount = 200
		end

		if fl_buffer.amount + amount > fl_buffer.capacity then
			status = "Tank Full!"
			break
		end

		if pliquid == "" then
			local node = minetest.get_node_or_nil(ppos)
			if not node or node.name == "air" or (bucket.liquids[node.name] and bucket.liquids[node.name].flowing == node.name) then
				plevel = plevel - 1
				status = "Seeking"
				refresh = true
				if plevel < -16 then
					plevel = -1
				end
				break
			end

			-- Valid liquid, proceed pumping
			if bucket.liquids[node.name] and bucket.liquids[node.name].source == node.name then
				if node.name == epr.water_source and heavy then
					node.name = "elepower_nuclear:heavy_water_source"
				end

				pliquid = node.name
				refresh = true
			else
				-- Run into a non-liquid node, stop the timer
				refresh = false
			end
			break
		end

		if pliquid ~= "" then
			-- Filter was installed
			if pliquid == epr.water_source and heavy and fl_buffer.amount > 0 then
				pliquid = "elepower_nuclear:heavy_water_source"
				fl_buffer.amount = 0
				refresh = true
				break
			end

			-- We are looking for `fl_buffer.fluid` on Y level `plevel`
			-- If we find a fluid node, we dig it, and add it to the buffer's storage
			-- If we don't find a fluid node, we go a level down
			local dug = dig_node_leveled_radius(ppos, 16, dig_node)
			if not dug then
				local node = minetest.get_node_or_nil(ppos)
				if node.name == epr.water_source and heavy then
					node.name = "elepower_nuclear:heavy_water_source"
				end

				if not node or (node.name ~= pliquid and node.name ~= "air") then
					status  = "No More Fluid!"
					refresh = false
					break
				end

				plevel = plevel - 1
				refresh = true
				if plevel < -16 then
					plevel = -1
				end
				break
			else
				fl_buffer.amount = fl_buffer.amount + amount
				pow_buffer.usage = usage
				pow_buffer.storage = pow_buffer.storage - usage
				status = "Pumping"
				refresh = true
			end
		end

		break
	end

	fl_buffer.fluid = pliquid

	-- Spawn tube entities
	if status == "Pumping" or status == "Seeking" then
		for i = 1, math.abs(plevel) do
			local tentpos = vector.subtract(pos, {x=0,y=i,z=0})
			local skip = false
			for i, ob in pairs(minetest.get_objects_inside_radius(tentpos, 0.5)) do
				if ob:get_luaentity() and ob:get_luaentity().name == "elepower_machines:pump_tube" then
					skip = true
					break
				end
			end

			if not skip then
				local e = minetest.add_entity(tentpos, "elepower_machines:pump_tube")
			
				local ent = e:get_luaentity()
				ent.pump = pos
				ent.level = i * -1
			end
		end
	end

	meta:set_string("formspec", get_formspec(pow_buffer, fl_buffer, state, plevel))
	meta:set_string("infotext", ("%s %s"):format(machine_def.description, status) ..
		"\n" .. ele.capacity_text(capacity, storage))

	meta:set_int("storage", pow_buffer.storage)
	meta:set_int("level", plevel)
	meta:set_string("liquid", pliquid)

	meta:set_int("pump_fluid_storage", fl_buffer.amount)
	meta:set_string("pump_fluid", fl_buffer.fluid)

	return refresh
end

ele.register_machine("elepower_machines:pump", {
	description = "Pickup Pump Tank",
	tiles = {
		"elepower_machine_top.png^elepower_power_port.png", "elepower_machine_base.png^elepower_pump_base.png",
		"elepower_machine_side.png^elepower_pump_side.png",
		"elepower_machine_side.png^elepower_pump_side.png", "elepower_machine_side.png^elepower_pump_side.png",
		"elepower_machine_side.png^elepower_pump_side.png",
	},
	groups = {
		ele_machine     = 1,
		ele_user        = 1,
		fluid_container = 1,
		oddly_breakable_by_hand = 1,
	},
	ele_no_automatic_ports = true,
	fluid_buffers = {
		pump = {
			capacity = 16000,
			drainable = true,
		},
	},
	on_timer = timer,
	on_construct = function (pos)
		local meta = minetest.get_meta(pos)
		meta:set_string("formspec", get_formspec(nil, nil, 0, -1))
	end,
	-- Upgradable
	ele_upgrades = {
		pump_filter = {},
		capacitor   = {"capacity"},
	},
})

minetest.register_entity("elepower_machines:pump_tube", {
	initial_properties = {
		hp_max   = 1,
		visual   = "mesh",
		mesh     = "elepower_pump_tube.obj",
		physical = false,
		textures = {"elepower_pump_tube.png"}

	},
	timer = 0,
	pump = {x=0,y=0,z=0},
	level = 0,
	on_step = function (self, dt)
		self.timer = self.timer + 1
		if self.timer < 50 then
			return self
		end
		self.timer = 0

		-- Check for pump
		local pump = minetest.get_node_or_nil(self.pump)
		if not pump or pump.name ~= "elepower_machines:pump" then
			-- Delete self if pump wasn't found
			self.object:remove()
			return self
		else
			-- Delete self if pump's discovery level is above tube's level
			local meta = minetest.get_meta(self.pump)
			local pl = meta:get_int("level")
			if pl > self.level then
				self.object:remove()
				return self
			end
		end
	end
})
