slaves = {}
slices = {}
nextSliceNum = 1

function discoverSlaves()
	rednet.broadcast("pconsp_discover")
	local slaveIds = {}
	while true do
		local sender, message, dist = rrp.receive(1)
		if sender == nil then break end
		if message == "pconsp_discover_resp" and dist < 16 then
			table.insert(slaveIds, sender)
		end
	end
	return slaveIds
end

function attachSlaves(slavesToAttach)
	slaves = {}
	local curSlaveIdx, curSlaveId
	for curSlaveIdx, curSlaveId in ipairs(slavesToAttach) do
		rrp.send(curSlaveId, "pconsp_attach")
	end
	while true do
		local sender, message, dist = rrp.receive(1)
		if sender == nil then break end
		if message == "pconsp_attached" then
			local cSlave = {
				["id"] = sender
			}
			table.insert(slaves, cSlave)
		end
	end
end

function releaseSlaves()
	local curSlaveIdx, curSlave
	for curSlaveIdx, curSlave in ipairs(slaves) do
		rrp.send(curSlave.id, "pconsp_release")
	end
end

function initSlaves()
	local offsetX, offsetY, offsetZ = pcons.getOffset()
	local msg = "pconsp_initproject " .. offsetX .. " " .. offsetY .. " " .. offsetZ .. " " .. pcons.getNumMaterials() .. " "
	if pcons.getHasFuelChest() then msg = msg .. "1" else msg = msg .. "0" end
	local curSlaveIdx, curSlave
	for curSlaveIdx, curSlave in ipairs(slaves) do
		rrp.send(curSlave.id, msg)
	end
end

function triggerChestDiscover()
	local curSlaveIdx, curSlave
	for curSlaveIdx, curSlave in ipairs(slaves) do
		print("Chest discover for " .. curSlave.id .. " ...")
		rrp.send(curSlave.id, "pconsp_chestdiscover")
		while true do
			local sender, message = rrp.receive()
			if sender == curSlave.id then
				if message == "pconsp_chestdiscoversuccess" then
					print("Success.")
				else
					print("Failure.")
					return false
				end
				break
			end
		end
	end
	return true
end

function initSlices()
	local startX, startY, startZ = pcons.getStart()
	local sizeX, sizeY, sizeZ = pcons.getSize()
	local numSlices = (#slaves) * 3
	local sliceSize = math.ceil(sizeZ / numSlices)
	local i
	slices = {}
	for i = 1,numSlices do
		local sliceStartZ = (i - 1) * sliceSize + startZ
		if sliceStartZ >= startZ + sizeZ then
			break
		end
		local sliceEndZ = sliceStartZ + sliceSize - 1
		if sliceEndZ >= startZ + sizeZ then
			sliceEndZ = startZ + sizeZ - 1
		end
		local posState = pcons.initNextPosState(startX, startY, sliceStartZ, startX + sizeX - 1, startY + sizeY - 1, sliceEndZ)
		local slice = {
			["startZ"] = sliceStartZ,
			["endZ"] = sliceEndZ,
			["finished"] = false,
			["nextPosState"] = posState
		}
		table.insert(slices, slice)
	end
	nextSliceNum = 1
	print("Splitting into " .. (#slices) .. " slices.")
end

function configureSliceBatch(sliceNum, posState)
	return pcons.configureNextBatch(posState)
end

function configureNextSliceBatch(sliceNum)
	local startX, startY, startZ = pcons.getStart()
	local sizeX, sizeY, sizeZ = pcons.getSize()
	local slice = slices[sliceNum]
	if slice.finished then
		return nil
	end
	if slice.nextPosState == nil then
		slice.finished = true
		return nil
	end
	local batch, nextPosState = configureSliceBatch(sliceNum, slice.nextPosState)
	slice.nextPosState = nextPosState
	return batch
end

function configureNextSlaveBatch(slaveNum)
	local slave = slaves[slaveNum]
	if slave.sliceNum == nil or slices[slave.sliceNum].finished then
		if nextSliceNum == nil or nextSliceNum > (#slices) then
			return nil
		end
		slave.sliceNum = nextSliceNum
		nextSliceNum = nextSliceNum + 1
	end
	local slice = slices[slave.sliceNum]
	local batch = configureNextSliceBatch(slave.sliceNum)
	if batch == nil then
		if slice.finished then
			return configureNextSlaveBatch(slaveNum)
		else
			return nil
		end
	end
	return batch
end

-- returns true if all slaves are finished
function slaveNextBatch(slaveNum)
	local slave = slaves[slaveNum]
	local batch = configureNextSlaveBatch(slaveNum)
	if batch == nil then
		slave.finished = true
		local cSlaveIdx, cSlave
		for cSlaveIdx, cSlave in ipairs(slaves) do
			if cSlave.finished == nil or cSlave.finished == false then
				return false
			end
		end
		return true
	else
		-- send batch to slave
		print("Sending batch to slave " .. slave.id)
		rrp.send(slave.id, "pconsp_dobatch " .. textutils.serialize(batch))
	end
	return false
end

function sendInitialBatchesToSlaves()
	local cSlaveIdx, cSlave
	for cSlaveIdx, cSlave in ipairs(slaves) do
		print("Sending initial batch to slave " .. cSlaveIdx .. " ...")
		slaveNextBatch(cSlaveIdx)
	end
end

function getSlaveByID(id)
	local cSlaveIdx, cSlave
	for cSlaveIdx, cSlave in ipairs(slaves) do
		if cSlave.id == id then
			return cSlaveIdx, cSlave
		end
	end
	return nil
end

chestRequestOwner = nil
chestRequestQueue = {}

function listenMain()
	while true do
		local sender, message = rrp.receive()
		local slaveIdx, slave = getSlaveByID(sender)
		if slaveIdx ~= nil then
			if message == "pconsp_chestrequest" then
				print("Got chest request from slave " .. slaveIdx)
				if chestRequestOwner == nil then
					print("Granting")
					chestRequestOwner = slaveIdx
					rrp.send(sender, "pconsp_chestaccessgranted")
				else
					print("Enqueueing")
					table.insert(chestRequestQueue, slaveIdx)
				end
			elseif message == "pconsp_chestrelease" and chestRequestOwner ~= nil and chestRequestOwner == slaveIdx then
				print("Got chest release from slave " .. slaveIdx)
				chestRequestOwner = nil
				if #chestRequestQueue > 0 then
					local dequeued = chestRequestQueue[1]
					print("Dequeueing chest request from slave " .. dequeued)
					table.remove(chestRequestQueue, 1)
					chestRequestOwner = dequeued
					rrp.send(slaves[dequeued].id, "pconsp_chestaccessgranted")
				end
			elseif message == "pconsp_batchdone" then
				print("Got batch done from slave " .. slaveIdx)
				if slaveNextBatch(slaveIdx) then
					print("Finished!")
					return
				end
			end
		end
	end
end

function start(provider, bx, by, bz)
	pcons.init(provider, bx, by, bz)
	rrp.openall()
	local discoveredSlaves = discoverSlaves()
	print("Discovered " .. #discoveredSlaves .. " slaves.")
	attachSlaves(discoveredSlaves)
	print("Attached " .. #slaves .. " slaves.")
	print("Initializing slaves ...")
	initSlaves()
	print("Allowing slaves to discover chests and locations ...")
	triggerChestDiscover()
	initSlices()
	sendInitialBatchesToSlaves()
	print("Listening ...")
	listenMain()
	print("Releasing slaves ...")
	releaseSlaves()
end

