local ent = ent
local scene = entities.currentScene()
local metadata = ent:getComponent("Metadata")

-- assign targets
_G.IOTargets = _G.IOTargets or {}

local metadataValve = metadata["valve"] or {}
if metadataValve["targetname"] then
	local tname = metadataValve["targetname"]
	if not _G.IOTargets[tname] then
		_G.IOTargets[tname] = {}
	end

	local uid = ent:uid()
	local exists = false
	for _, id in ipairs(_G.IOTargets[tname]) do
		if id == uid then exists = true break end
	end
	if not exists then
		table.insert(_G.IOTargets[tname], uid)
	end
end

ent:addHook("io:Input.%UID%", function(payload)
	local input = payload.input
	local param = payload.parameter

	-- kill
	if input == "Kill" or input == "KillHierarchy" then
		entities.destroy(ent)
	-- relays
	elseif input == "FireUser1" then
		ent:callHook("io:FireOutput.%UID%", { output = "OnUser1" })
	elseif input == "FireUser2" then
		ent:callHook("io:FireOutput.%UID%", { output = "OnUser2" })
	elseif input == "FireUser3" then
		ent:callHook("io:FireOutput.%UID%", { output = "OnUser3" })
	elseif input == "FireUser4" then
		ent:callHook("io:FireOutput.%UID%", { output = "OnUser4" })
	-- colors
	elseif input == "Alpha" then
		local alphaVal = tonumber(param) / 255.0
		if alphaVal then
			-- to-do: apply to instance / material
		end
	elseif input == "Color" then
		local r, g, b = param:match("(%d+) (%d+) (%d+)")
		-- to-do: apply to instance / material
	else
	   --print("I/O [(".. ent:uid() ..") " .. ent:name() .. "]: Received Input '" .. input .. "' with param '" .. tostring(param) .. "'")
	end
end )

-- no connections defined, bail early
local connections = metadata["connections"]
if not connections then return end

-- define connections
local timer = Timer.new()
if not timer:running() then
	timer:start()
end
local timesFired = {}
for i = 1, #connections do
	timesFired[i] = 0
end

local pendingOutputs = {}

ent:bind("tick", function(self)
	local currentTime = timer:elapsed()

	for i = #pendingOutputs, 1, -1 do
		local job = pendingOutputs[i]
        if currentTime >= job.fireTime then
			local targetUIDs = _G.IOTargets[job.target]
			if targetUIDs then
				for _, targetUID in ipairs(targetUIDs) do
					local targetEnt = entities.get(targetUID)
					if targetEnt:uid() then
						targetEnt:callHook("io:Input." .. targetUID, {
							input = job.input,
							parameter = job.parameter,
							caller = ent:uid()
						})
					end
				end
			else
				print("I/O Warning: Targetname '" .. job.target .. "' not found!")
			end

			table.remove(pendingOutputs, i)
		end
	end
end)

ent:addHook("io:FireOutput.%UID%", function(payload)
	local output = payload.output

	for i = 1, #connections do
		local conn = connections[i]

		if conn.output == output then
			local limit = conn.times or -1

			if limit == -1 or timesFired[i] < limit then
				timesFired[i] = timesFired[i] + 1

				local delay = conn.delay or 0.0
				
				table.insert(pendingOutputs, {
					fireTime = timer:elapsed() + delay,
					target = conn.target,
					input = conn.input,
					parameter = conn.parameter
				})
			end
		end
	end
end)

ent:addHook("entity:Destroy.%UID%", function()
	if metadataValve["targetname"] then
		local tname = metadataValve["targetname"]
		if _G.IOTargets[tname] then
			for i, id in ipairs(_G.IOTargets[tname]) do
				if id == ent:uid() then
					table.remove(_G.IOTargets[tname], i)
					break
				end
			end
		end
	end
end)

ent:addHook("entity:Use.%UID%", function(payload)
	if payload.user == ent:uid() then return end
	ent:callHook("io:FireOutput.%UID%", { output = "OnPlayerUse" })
	ent:callHook("io:FireOutput.%UID%", { output = "OnUse" })
	ent:callHook("io:FireOutput.%UID%", { output = "OnPressed" })
    ent:callHook("io:FireOutput.%UID%", { output = "OnIn" })
end)