local m = peripheral.find("modem", function(_, o) return o.isWireless() end)
local ec_oc = peripheral.find "ender_chest"
local ec_cc = peripheral.find "minecraft:ender chest"
local spatial = "back"
local spatialp = peripheral.wrap(spatial)
local name = os.getComputerLabel()
local channel = 236
local spatial_ec_loc = settings.get "spatial_ec_loc" or "east"
local busy = false
local needed_energy = 34000

local function send(x)
	print("->", unpack(x))
	m.transmit(channel, channel, x)
end

local function recv_filter(fn)
	while true do
		local _, _, c, rc, msg = os.pullEvent "modem_message"
		print("<-", unpack(msg))
		if type(msg) == "table" and fn(msg) then return msg end
	end
end

local function pick_channel()
	return math.random(0xA00, 0xC00)
end

local function run_spatial()
	rs.setOutput(spatial, true)
	sleep(0.1)
	rs.setOutput(spatial, false)
	sleep(0.1)
	print "Run spatial"
end

local function run_transfer(trg)
	busy = true
	local function run_transfer_internal()
		if spatialp.getNetworkEnergyStored() < needed_energy then print "Insufficient energy" return end
		print "Pinging"
		send { "ping", trg }
		if (recv_filter(function(m) return m[1] == "pong" and m[2] == trg end))[3] then print "Remote busy" return end
		print "Destination available"
		local ec_channel = pick_channel()
		ec_oc.setFrequency(ec_channel)
		send { "transfer", trg, ec_channel }
		if not (recv_filter(function(m) return m[1] == "transfer_ack" and m[2] == trg end))[3] then print "Remote denied transfer" return end
		print "Ack received"
		run_spatial()
		spatialp.pushItems(spatial_ec_loc, 2, 1, 1)
		print "Sent item to remote"
		repeat
			sleep(0.1)
		until ec_cc.getItemMeta(27) ~= nil
		print("Remote sent item")
		spatialp.pullItems(spatial_ec_loc, 27, 1, 1)
		run_spatial()
		spatialp.pullItems("self", 2, 1, 1)
		print "Done"
	end
	parallel.waitForAny(run_transfer_internal, function() sleep(5) print "Transfer timed out" end)
	busy = false
end

local pings_timer = nil
local function list_all()
	pings_timer = os.startTimer(1)
	send { "ping" }
end

local function inbound()
	local function run_transfer_recv(msg)
		print("Transfer request from", msg[2])
		if spatialp.getNetworkEnergyStored() < needed_energy then print "Insufficient energy" send { "transfer_ack", msg[2], false } return end
		if busy then print "Already busy, cannot accept transfer" send { "transfer_ack", msg[2], false } return end
		busy = true
		send { "transfer_ack", msg[2], true }
		print("Accepting transfer from", msg[2])
		ec_oc.setFrequency(msg[3])
		repeat
			sleep(0.1)
		until ec_cc.getItemMeta(1) ~= nil
		print "Remote sent item"
		run_spatial()
		spatialp.pullItems(spatial_ec_loc, 1, 1, 1)
		spatialp.pushItems(spatial_ec_loc, 2, 1, 27)
		print "Sent item to remote"
		run_spatial()
		spatialp.pullItems("self", 2, 1, 1)
		print "Done"
	end

	local function process(msg)
		local cmd = msg[1]
		if cmd == "ping" and (msg[2] == nil or msg[2] == name) then
			send { "pong", name, busy }
		elseif pings_timer and cmd == "pong" then
			print(msg[2])
		elseif cmd == "transfer" and msg[2] == name and type(msg[3]) == "number" then
			parallel.waitForAny(function() run_transfer_recv(msg) end, function() sleep(5) print "Inbound transfer timed out" end)
			busy = false
		end
	end

	m.open(channel)
	while true do
		local ev, timer, c, rc, msg = os.pullEvent()
		if ev == "modem_message" and c == channel and rc == channel and type(msg) == "table" then
			local ok, err = pcall(process, msg)
			if not ok then printError(err) end
		elseif ev == "timer" and timer == pings_timer then
			pings_timer = nil
		end
	end
end

local function split_at_spaces(s)
    local t = {}
    for i in string.gmatch(s, "%S+") do
       table.insert(t, i)
    end
    return t
end

local function update_self()
	print "Downloading update."
	local h = http.get "https://pastebin.com/raw/R4HrijSg"
	local t = h.readAll()
	h.close()
	local fn, err = load(t, "@program")
	if not fn then printError("Not updating: syntax error in new version:\n" .. err) return end
	local f = fs.open("startup", "w")
	f.write(t)
	f.close()
	os.reboot()
end

local function ui()
	local history = {}
	while true do
		write "|> "
		local input = read(nil, history)
		table.insert(history, input)
		local tokens = split_at_spaces(input)
		if tokens[1] == "list" then
			list_all()
			sleep(1)
		elseif tokens[1] == "update" then
			update_self()
		elseif tokens[1] == "tp" then
			table.remove(tokens, 1)
			run_transfer(table.concat(tokens, " "))
		else
			printError "Not found"
		end
	end
end

parallel.waitForAll(ui, inbound)