-- IndustrialTest
-- Copyright (C) 2025 mrkubax10

-- This program is free software: you can redistribute it and/or modify
-- it under the terms of the GNU General Public License as published by
-- the Free Software Foundation, either version 3 of the License, or
-- (at your option) any later version.

-- This program is distributed in the hope that it will be useful,
-- but WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-- GNU General Public License for more details.

-- You should have received a copy of the GNU General Public License
-- along with this program.  If not, see <http://www.gnu.org/licenses/>.

local S=minetest.get_translator("industrialtest")

local function destructMiningPipe(pos,player)
	local count=0
	local originalPos=table.copy(pos)
	local node=minetest.get_node(pos)
	while node.name=="industrialtest:mining_pipe" do
		count=count+1
		pos.y=pos.y-1
		node=minetest.get_node(pos)
		if node.name=="ignore" then
			minetest.load_area(pos)
			node=minetest.get_node(pos)
		end
	end
	local endPos=table.copy(pos)

	local manip=minetest.get_voxel_manip()
	local minp,maxp=manip:read_from_map(pos,originalPos)
	local data=manip:get_data()
	local area=VoxelArea(minp,maxp)
	pos=table.copy(originalPos)
	while pos.y>endPos.y do
		data[area:index(pos.x,pos.y,pos.z)]=minetest.CONTENT_AIR
		pos.y=pos.y-1
	end
	manip:set_data(data)
	manip:write_to_map()

	if player then
		local itemstack=ItemStack("industrialtest:mining_pipe "..tostring(count))
		if industrialtest.mtgAvailable then
			local inv=player:get_inventory()
			local leftover=inv:add_item("main",itemstack)
			if not leftover:is_empty() then
				minetest.add_item(player:get_pos(),leftover)
			end
		elseif industrialtest.mclAvailable then
			minetest.add_item(originalPos,itemstack)
		end
	end
end

local function miningPipeUpdateMiner(pos)
	local meta=minetest.get_meta(pos)
	if meta:contains("miner") then
		local minerPos=minetest.deserialize(meta:get_string("miner"))
		local minerMeta=minetest.get_meta(minerPos)
		minerMeta:set_int("level",pos.y)
	end
end

local definition={
	description=S("Mining Pipe"),
	tiles={"industrialtest_mining_pipe.png"},
	paramtype="light",
	sunlight_propagates=true,
	drawtype="nodebox",
	node_box={
		type="fixed",
		fixed={
			-0.25,
			-0.5,
			-0.25,
			0.25,
			0.5,
			0.25
		}
	},
	on_destruct=function(pos)
		miningPipeUpdateMiner(pos)
		destructMiningPipe(pos,nil)
	end,
	on_dig=function(pos,node,digger)
		miningPipeUpdateMiner(pos)
		destructMiningPipe(pos,digger)
	end
}
if industrialtest.mtgAvailable then
	definition.groups={
		cracky=3,
		oddly_breakable_by_hand=1
	}
	definition.sounds=default.node_sound_metal_defaults()
elseif industrialtest.mclAvailable then
	definition.groups={
		pickaxey=1,
		handy=1
	}
	definition.sounds=mcl_sounds.node_sound_metal_defaults()
	definition._mcl_blast_resistance=1
	definition._mcl_hardness=1
end
minetest.register_node("industrialtest:mining_pipe",definition)

minetest.register_craft({
	type="shaped",
	output="industrialtest:mining_pipe 8",
	recipe={
		{"industrialtest:refined_iron_ingot","","industrialtest:refined_iron_ingot"},
		{"industrialtest:refined_iron_ingot","","industrialtest:refined_iron_ingot"},
		{"industrialtest:refined_iron_ingot",industrialtest.elementKeys.treetap,"industrialtest:refined_iron_ingot"}
	}
})

industrialtest.Miner=table.copy(industrialtest.ElectricMachine)
industrialtest.internal.unpackTableInto(industrialtest.Miner,{
	name="industrialtest:miner",
	description=S("Miner"),
	tiles={
		"industrialtest_machine_block.png",
		"industrialtest_machine_block.png",
		"industrialtest_machine_block.png",
		"industrialtest_machine_block.png",
		"industrialtest_machine_block.png",
		"industrialtest_machine_block.png^industrialtest_miner_front.png"
	},
	sounds="metal",
	storageLists={
		"drill",
		"src",
		"scanner",
		"dst",
		"powerStorage",
		"upgrades"
	},
	powerLists={
		{
			list="powerStorage",
			direction="i"
		}
	},
	requiresWrench=true,
	facedir=true,
	flow=industrialtest.api.lvPowerFlow,
	capacity=5000,
	ioConfig="iiiiii",
	hasPowerInput=true,
	_opPower=1000,
	_scannerOpPower=10
})

function industrialtest.Miner.onConstruct(self,pos)
	local meta=minetest.get_meta(pos)
	local inv=meta:get_inventory()
	inv:set_size("drill",1)
	inv:set_size("src",1)
	inv:set_size("scanner",1)
	inv:set_size("dst",15)
	inv:set_size("powerStorage",1)
	inv:set_size("upgrades",1)
	meta:set_int("level",pos.y-1)
	-- Keep last fluid node here so pump has more time to access it
	meta:set_string("lastFluidNode","")
	industrialtest.ElectricMachine.onConstruct(self,pos)
end

function industrialtest.Miner.onDestruct(self,pos)
	destructMiningPipe(vector.new(pos.x,pos.y-1,pos.z),nil)
	industrialtest.ElectricMachine.onDestruct(self,pos)
end

function industrialtest.Miner.onDig(self,pos,node,digger)
	destructMiningPipe(vector.new(pos.x,pos.y-1,pos.z),digger)
	return industrialtest.ElectricMachine.onDig(self,pos,node,digger)
end

function industrialtest.Miner.getFormspec(self,pos)
	local parentFormspec=industrialtest.ElectricMachine.getFormspec(self,pos)
	local meta=minetest.get_meta(pos)
	local powerPercent=meta:get_int("industrialtest.powerAmount")/meta:get_int("industrialtest.powerCapacity")*100
	local formspec={
		"label[0.7,1.15;"..S("Drill").."]",
		industrialtest.internal.getItemSlotBg(0.7,1.5,1,1),
		"list[context;drill;0.7,1.5;1,1]",
		"label[0.7,2.75;"..S("Pipe").."]",
		industrialtest.internal.getItemSlotBg(0.7,3.1,1,1),
		"list[context;src;0.7,3.1;1,1]",
		"label[0.7,4.35;"..S("Scanner").."]",
		industrialtest.internal.getItemSlotBg(0.7,4.7,1,1),
		"list[context;scanner;0.7,4.7;1,1]",
		industrialtest.internal.getItemSlotBg(2.28,1.9,5,3),
		"list[context;dst;2.28,1.9;5,3]",
		industrialtest.internal.getItemSlotBg(9.1,1.2,1,1),
		"list[context;upgrades;9.1,1.2;1,1]",
		(powerPercent>0 and "image[9.1,2.29;1,1;industrialtest_gui_electricity_bg.png^[lowpart:"..powerPercent..":industrialtest_gui_electricity_fg.png]"
		 or "image[9.1,2.29;1,1;industrialtest_gui_electricity_bg.png]"),
		industrialtest.internal.getItemSlotBg(9.1,3.5,1,1),
		"list[context;powerStorage;9.1,3.5;1,1]",
		"listring[context;src]",
		"listring[context;dst]"
	}
	return parentFormspec..table.concat(formspec,"")
end

function industrialtest.Miner.canUpdate(self,pos)
	local meta=minetest.get_meta(pos)
	local inv=meta:get_inventory()
	local drillSlot=inv:get_stack("drill",1)
	local srcSlot=inv:get_stack("src",1)
	local level=meta:get_int("level")
	local requiredPower=self:getRequiredPower(pos)
	return meta:get_int("industrialtest.powerAmount")>=requiredPower and not drillSlot:is_empty() and not srcSlot:is_empty() and
		self:canContinue(pos,level)
end

function industrialtest.Miner.allowMetadataInventoryMove(self,pos,fromList,fromIndex,toList,toIndex,count)
	local meta=minetest.get_meta(pos)
	local inv=meta:get_inventory()
	local itemstack=inv:get_stack(fromList,fromIndex)
	if toList=="drill" then
		return self.allowDrillPut(itemstack) and count or 0
	end
	if toList=="src" then
		return itemstack:get_name()=="industrialtest:mining_pipe" and count or 0
	end
	if toList=="scanner" then
		return self.allowScannerPut(itemstack) and 1 or 0
	end
	if toList=="dst" then
		return 0
	end
	return industrialtest.ElectricMachine.allowMetadataInventoryMove(self,pos,fromList,fromIndex,toList,toIndex,count)
end

function industrialtest.Miner.allowMetadataInventoryPut(self,pos,listname,index,itemstack,player)
	if listname=="drill" then
		return self.allowDrillPut(itemstack) and itemstack:get_count() or 0
	end
	if listname=="src" then
		return itemstack:get_name()=="industrialtest:mining_pipe" and itemstack:get_count() or 0
	end
	if listname=="scanner" then
		return self.allowScannerPut(itemstack) and 1 or 0
	end
	if listname=="dst" then
		return 0
	end
	return industrialtest.ElectricMachine.allowMetadataInventoryPut(self,pos,listname,index,itemstack,player)
end

function industrialtest.Miner.onMetadataInventoryMove(self,pos,fromList,fromIndex,toList,toIndex,count)
	self:onInventoryPut(pos,toList)
	industrialtest.ElectricMachine.onMetadataInventoryMove(self,pos,fromList,fromIndex,toList,toIndex,count)
end

function industrialtest.Miner.onMetadataInventoryPut(self,pos,listname,index,stack)
	self:onInventoryPut(pos,listname)
	industrialtest.ElectricMachine.onMetadataInventoryPut(self,pos,listname,index,stack)
end

function industrialtest.Miner.onMetadataInventoryTake(self,pos,listname,index,stack)
	if listname=="dst" then
		self:triggerIfNeeded(pos)
	end
end

function industrialtest.Miner.update(self,pos,elapsed,meta,inv)
	if not self:canUpdate(pos) then
		return false,false
	end

	local level=meta:get_int("level")
	if not self:canContinue(pos,level) then
		return false,false
	end

	local srcSlot=inv:get_stack("src",1)
	srcSlot:take_item(1)
	inv:set_stack("src",1,srcSlot)
	local targetPos=vector.new(pos.x,level,pos.z)

	-- Check if target node is fluid so pump, if attached, can get it
	local targetNode=minetest.get_node(targetPos)
	local targetFluid=industrialtest.api.getPumpFluid(targetNode.name)
	if targetFluid then
		meta:set_string("lastFluidNode",targetNode.name)
	end

	local drop=self.getNodeDrop(targetPos)
	self.placeMiningPipe(pos,targetPos)
	inv:add_item("dst",drop)

	local scannerSlot=inv:get_stack("scanner",1)
	if not scannerSlot:is_empty() then
		local def=scannerSlot:get_definition()
		if def and def._industrialtest_self then
			local filtered=def._industrialtest_self:filter(targetPos)
			for _,filteredPos in ipairs(filtered) do
				drop=self.getNodeDrop(filteredPos)
				if inv:room_for_item("dst",drop) then
					minetest.remove_node(filteredPos)
					inv:add_item("dst",drop)
				end
			end
		end
	end

	local requiredPower=self:getRequiredPower(pos)
	meta:set_int("level",level-1)
	industrialtest.api.addPower(meta,-requiredPower)

	return true,true
end

function industrialtest.Miner.onInventoryPut(self,pos,listname)
	if listname=="drill" or listname=="src" then
		self:triggerIfNeeded(pos)
	end
end

function industrialtest.Miner.allowDrillPut(itemstack)
	local def=itemstack:get_definition()
	return def and def.groups and def.groups._industrialtest_miningDrill
end

function industrialtest.Miner.allowScannerPut(itemstack)
	local def=itemstack:get_definition()
	return def and def.groups and def.groups._industrialtest_scanner
end

function industrialtest.Miner.canContinue(self,pos,level)
	local meta=minetest.get_meta(pos)
	local inv=meta:get_inventory()
	local targetPos=vector.new(pos.x,level,pos.z)
	local targetNode=minetest.get_node(targetPos)
	if targetNode.name=="ignore" then
		minetest.load_area(targetPos)
		targetNode=minetest.get_node(targetPos)
		if targetNode.name=="ignore" then
			return false
		end
	end

	local def=minetest.registered_nodes[targetNode.name]
	local drop=self.getNodeDrop(vector.new(pos.x,level,pos.z))
	return not (def and def.groups and def.groups.unbreakable) and inv:room_for_item("dst",drop)
end

function industrialtest.Miner.getRequiredPower(self,pos)
	local meta=minetest.get_meta(pos)
	local inv=meta:get_inventory()
	local scannerSlot=inv:get_stack("scanner",1)
	local result=self._opPower
	if not scannerSlot:is_empty() then
		local def=scannerSlot:get_definition()
		if def and def._industrialtest_self then
			local distance=def._industrialtest_self.minerDistance or 0
			result=result+(distance*distance-1)*self._scannerOpPower
		end
	end
	return result
end

function industrialtest.Miner.getNodeDrop(pos)
	local node=minetest.get_node(pos)
	local def=minetest.registered_nodes[node.name]
	if not def.pointable then
		return ItemStack()
	end
	return ItemStack((def and def.drop and def.drop~="") and def.drop or node.name)
end

function industrialtest.Miner.placeMiningPipe(minerPos,pos)
	minetest.add_node(pos,{
		name="industrialtest:mining_pipe"
	})
	local meta=minetest.get_meta(pos)
	meta:set_string("miner",minetest.serialize(minerPos))
end

function industrialtest.Miner.pullFluid(self,pos,amount)
	local meta=minetest.get_meta(pos)
	local lastFluidNode=meta:get_string("lastFluidNode")
	if lastFluidNode~="" then
		local result={
			fluidType=lastFluidNode
		}
		result.remaining=industrialtest.api.nodeFluidCapacity
		-- If everything was pulled then change to empty
		if amount>=industrialtest.api.nodeFluidCapacity then
			meta:set_string("lastFluidNode","")
		end
		return result
	end
	return nil
end

industrialtest.Miner:register()

industrialtest.api.registerPumpTarget("industrialtest:miner","i")

minetest.register_craft({
	type="shaped",
	output="industrialtest:miner",
	recipe={
		{"",industrialtest.elementKeys.chest,""},
		{"industrialtest:electronic_circuit","industrialtest:machine_block","industrialtest:electronic_circuit"},
		{"","industrialtest:mining_pipe",""}
	}
})
