-- IndustrialTest
-- Copyright (C) 2024 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/>.

industrialtest.ElectricMachine=table.copy(industrialtest.Machine)

function industrialtest.ElectricMachine.onConstruct(self,pos)
	local meta=minetest.get_meta(pos)

	industrialtest.api.addPowerStorage(meta,self.capacity,self.flow,self.ioConfig)

	if self.hasPowerInput then
		local connections=industrialtest.api.getConnections(pos)
		for _,conn in ipairs(connections) do
			local connectionMeta=minetest.get_meta(conn)
			if industrialtest.api.isNetworkMaster(connectionMeta) then
				industrialtest.api.createNetworkMapForNode(conn)
				local def=minetest.get_node(conn)
				if def and def._industrialtest_self then
					def._industrialtest_self:flowPower(conn)
				else
					-- Support for bare definitions that don't use industrialtest pseudo-OOP
					minetest.get_node_timer(conn):start(industrialtest.config.updateDelay)
				end
			else
				local def=minetest.registered_nodes[minetest.get_node(conn).name]
				if def.groups._industrialtest_cable then
					local networks=industrialtest.api.isAttachedToNetwork(connectionMeta)
					if networks then
						for _,network in ipairs(networks) do
							industrialtest.api.createNetworkMapForNode(network)
							local def=minetest.registered_nodes[minetest.get_node(network).name]
							if def and def._industrialtest_self then
								def._industrialtest_self:flowPower(network)
							else
								-- Support for bare definitions that don't use industrialtest pseudo-OOP
								minetest.get_node_timer(network):start(industrialtest.config.updateDelay)
							end
						end
					end
				end
			end
		end
	end
	if self.hasPowerOutput then
		meta:set_string("industrialtest.network",minetest.serialize(industrialtest.api.createNetworkMap(pos)))
	end

	industrialtest.Machine.onConstruct(self,pos)
end

function industrialtest.ElectricMachine.onDestruct(self,pos)
	local meta=minetest.get_meta(pos)

	if industrialtest.api.isNetworkMaster(meta) then
		local network=industrialtest.api.createNetworkMap(pos,true)
		for _,endpoint in ipairs(network) do
			local endpointMeta=minetest.get_meta(endpoint.position)
			local networks=industrialtest.api.isAttachedToNetwork(endpointMeta)
			for key,value in ipairs(networks) do
				if value.x==pos.x and value.y==pos.y and value.z==pos.z then
					table.remove(networks,key)
					break
				end
			end
			endpointMeta:set_string("industrialtest.networks",minetest.serialize(networks))
		end
	end
	local networks=industrialtest.api.isAttachedToNetwork(meta)
	if networks then
		for _,network in ipairs(networks) do
			industrialtest.api.removeNodeFromNetwork(network,pos)
		end
	end

	-- don't call onDestruct from parent class because it doesn't do anything
end

function industrialtest.ElectricMachine.onTimer(self,pos,elapsed)
	local result=industrialtest.Machine.onTimer(self,pos,elapsed)
	local result2=self:powerExchange(pos)
	return result or result2
end

function industrialtest.ElectricMachine.allowMetadataInventoryMove(self,pos,fromList,fromIndex,toList,toIndex,count)
	local meta=minetest.get_meta(pos)
	local inv=meta:get_inventory()
	local movedItemStack=inv:get_stack(fromList,fromIndex)
	local found=false

	if self.powerLists then
		for _,value in ipairs(self.powerLists) do
			if value.list==toList then
				found=true
				break
			end
		end
	end
	if found and not industrialtest.api.hasPowerStorage(movedItemStack:get_meta()) and not self.isValidElectricItemDef(movedItemStack) then
		return 0
	end

	return industrialtest.Machine.allowMetadataInventoryMove(self,pos,fromList,fromIndex,toList,toIndex,count)
end

function industrialtest.ElectricMachine.allowMetadataInventoryPut(self,pos,listname,index,stack,player)
	local found=false
	if self.powerLists then
		for _,value in ipairs(self.powerLists) do
			if value.list==listname then
				found=true
				break
			end
		end
	end
	if found and not industrialtest.api.hasPowerStorage(stack:get_meta()) and not self.isValidElectricItemDef(stack) then
		return 0
	end

	return industrialtest.Machine.allowMetadataInventoryPut(self,pos,listname,index,stack,player)
end

function industrialtest.ElectricMachine.onMetadataInventoryMove(self,pos,fromList,fromIndex,toList,toIndex,count)
	if self.powerLists then
		for _,value in ipairs(self.powerLists) do
			if value.list==toList then
				self:trigger(pos)
				break
			end
		end
	end
	industrialtest.Machine.onMetadataInventoryMove(self,pos,fromList,fromIndex,toList,toIndex,count)
end

function industrialtest.ElectricMachine.onMetadataInventoryPut(self,pos,listname,index,stack)
	if self.powerLists then
		for _,value in ipairs(self.powerLists) do
			if value.list==listname then
				self:trigger(pos)
				break
			end
		end
	end
	industrialtest.Machine.onMetadataInventoryPut(self,pos,listname,index,stack)
end

function industrialtest.ElectricMachine.flowPower(self,pos)
	if not self.hasPowerOutput then
		return
	end
	local meta=minetest.get_meta(pos)
	if meta:get_int("industrialtest.powerAmount")<=0 then
		return
	end
	local _,flowTransferred=industrialtest.api.powerFlow(pos)
	if flowTransferred then
		self:updateFormspec(pos)
	end
	self:triggerIfNeeded(pos)
end

function industrialtest.ElectricMachine.requestPower(self,pos)
	local meta=minetest.get_meta(pos)
	local networks=industrialtest.api.isAttachedToNetwork(meta)
	if networks then
		for _,network in ipairs(networks) do
			local def=minetest.registered_nodes[minetest.get_node(network).name]
			if def and def._industrialtest_self then
				def._industrialtest_self:flowPower(network)
			else
				-- Support for bare definitions that don't use industrialtest pseudo-OOP
				minetest.get_node_timer(network):start(industrialtest.config.updateDelay)
			end
		end
	end
end

function industrialtest.ElectricMachine.powerExchange(self,pos)
	local meta=minetest.get_meta(pos)
	local shouldRerunTimer=false

	if self.hasPowerInput and not industrialtest.api.isFullyCharged(meta) then
		self:requestPower(pos)
		shouldRerunTimer=shouldRerunTimer or not industrialtest.api.isFullyCharged(meta)
	end

	if self.hasPowerOutput and meta:get_int("industrialtest.powerAmount")>0 then
		local spaceAvailable,flowTransferred=industrialtest.api.powerFlow(pos)
		if flowTransferred then
			self:updateFormspec(pos)
		end
		shouldRerunTimer=shouldRerunTimer or spaceAvailable
	end

	if self.powerLists then
		local inv=meta:get_inventory()
		local powerFlow=meta:get_int("industrialtest.powerFlow")
		for _,listDesc in ipairs(self.powerLists) do
			local slot=inv:get_stack(listDesc.list,1)
			if not industrialtest.api.hasPowerStorage(slot:get_meta()) and self.isValidElectricItemDef(slot) then
				local def=slot:get_definition()
				def._industrialtest_self:prepare(slot)
				inv:set_stack(listDesc.list,1,slot)
			end
			if slot:get_count()>0 then
				if listDesc.direction=="o" then
					if meta:get_int("industrialtest.powerAmount")<=0 then
						break
					end
					if not industrialtest.api.isFullyCharged(slot:get_meta()) then
						industrialtest.api.transferPowerToItem(meta,slot,powerFlow)
						inv:set_stack(listDesc.list,1,slot)
						self:updateFormspec(pos)
						shouldRerunTimer=shouldRerunTimer or (not industrialtest.api.isFullyCharged(slot:get_meta()) and meta:get_int("industrialtest.powerAmount")>0)
					end
				elseif listDesc.direction=="i" then
					if industrialtest.api.isFullyCharged(meta) then
						break
					end
					local slotMeta=slot:get_meta()
					if slotMeta:get_int("industrialtest.powerAmount")>0 then
						industrialtest.api.transferPowerFromItem(slot,meta,powerFlow)
						inv:set_stack(listDesc.list,1,slot)
						self:updateFormspec(pos)
						shouldRerunTimer=shouldRerunTimer or (not industrialtest.api.isFullyCharged(meta) and slotMeta:get_int("industrialtest.powerAmount")>0)
					end
				end
			end
		end
	end

	return shouldRerunTimer
end

-- Checks if itemstack has a valid electric item definition, even if it's not prepared
function industrialtest.ElectricMachine.isValidElectricItemDef(itemstack)
	local def=itemstack:get_definition()
	return def._industrialtest_self and def._industrialtest_self.flow and def._industrialtest_self.flow and def._industrialtest_self.prepare
end

function industrialtest.ElectricMachine.createPowerIndicatorWidget(charged,x,y)
	return table.concat({
		string.format("box[%f,%f;0.3,4.8;#202020]",x,y),
		(charged>0 and string.format("box[%f,%f;0.3,%f;#FF1010]",x,y+4.8-(charged*4.8),charged*4.8) or ""),
	},"")
end
