local debugInfo = {}
local function getDebugInfo()
	return " ("..debugInfo.mnem.." sub "..debugInfo.sub..")"
end

local function hex(n) return string.format("%02X", n) end

local function cycleAddSignals(cycle, cyc2, cyc2t, ops, sigs)
	for _, name in ipairs(cycle) do
		if ops[name] then
			cycleAddSignals(ops[name], cyc2, cyc2t, ops, sigs)
		elseif sigs[name] then
			if not cyc2t[name] then
				cyc2t[name] = true
				table.insert(cyc2, name)
			else
				error("overspecified signal name: "..name..getDebugInfo())
			end
		else
			error("invalid signal name: "..name.." does not name a signal or operation")
		end
	end
end
local function cycleSimplify(cycle, ops, sigs)
	local cyc2, cyc2t = {}, {}
	cycleAddSignals(cycle, cyc2, cyc2t, ops, sigs)
	return cyc2
end
local function cycleToUcycle(cycle, ops, sigs)
	local ucycle = {}
	local cyc2 = cycleSimplify(cycle, ops, sigs)
	local hasbase = false
	local nonempty = false
	for _, name in ipairs(cyc2) do
		nonempty = true
		if name=="always1" then hasbase = true end
		local sig = sigs[name]
		assert(sig, "no signal named "..name)
		for i, sbit in ipairs(sig) do
			ucycle[sbit.rom] = ucycle[sbit.rom] or {}
			assert(not ucycle[sbit.rom][sbit.bit], "multiply defined bit")
			ucycle[sbit.rom][sbit.bit] = true
		end
	end
	if nonempty and (not hasbase) then error("cycle has no base"..getDebugInfo()) end
	return ucycle
end
local function encodeInstruction(opcode, instr, ucode, ops, sigs)
	for sub, cycle in ipairs(instr) do
		debugInfo.sub = sub-1
		local ucycle = cycleToUcycle(cycle, ops, sigs)
		local uaddr = opcode*4 + (sub-1)
		assert(not ucode[uaddr], "overused ucode addr "..uaddr)
		ucode[uaddr] = ucycle
	end
end

local function sigsFromRoms(roms)
	local sigs = {}
	for romidx, rom in ipairs(roms) do
		for sigidx, sig in ipairs(rom.signals) do
			sigs[sig] = sigs[sig] or {}
			table.insert(sigs[sig], {
				rom = romidx,
				bit = sigidx-1,
			})
		end
	end
	return sigs
end

local function archToUcode(arch)
	local sigs = sigsFromRoms(arch.roms)
	local ops = arch.operations
	
	local ucode = {}
	local opcodesUsed = {}
	local numOpcodesUsed = 0
	local infolines = {}
	local catlet = "X"
	for _, instr in ipairs(arch.instructions) do
		if instr.category then
			catlet = instr.catlet
			table.insert(infolines, "\n")
			table.insert(infolines, instr.category.." ("..catlet.."):\n")
		else
			local mnem = instr.mnem or error("instr has no mnem")
			local opcode = instr.opcode or error("instr has no opcode "..mnem)
			local ncycles = instr.ncycles or #instr
			
			assert(#instr <= 8, "operation too long "..opcode)
			local len = math.ceil(#instr / 4)
			if len>1 then assert(opcode%2==0, "len>1 on odd opcode") end
			for i = 1, len do
				local opcode2 = opcode+i-1
				assert(not opcodesUsed[opcode2], "overused opcode "..hex(opcode))
				opcodesUsed[opcode2] = catlet
				numOpcodesUsed = numOpcodesUsed+1
			end
			
			debugInfo.mnem = mnem
			encodeInstruction(opcode, instr, ucode, ops, sigs)
			
			if instr.desc then
				table.insert(infolines, mnem..(" "):rep(13-#mnem)..hex(opcode).."  "..ncycles.."  "..instr.desc.."\n")
			end
		end
	end
	
	local lt = {"Opcodes used: "..numOpcodesUsed.."/255\n", "     "}
	for i = 0, 15 do table.insert(lt, hex(i):sub(2, 2)) end
	table.insert(lt, "\n")
	for line = 0, 255, 16 do
		table.insert(lt, hex(line).." | ")
		for opcode = line, line+15 do
			if opcodesUsed[opcode] then table.insert(lt, opcodesUsed[opcode])
			else                        table.insert(lt, "-") end
		end
		table.insert(lt, "\n")
	end
	
	local info = table.concat(infolines).."\n"..table.concat(lt)
	print(info)
	
	local fo = io.open("instructionList.txt", "w")
	if fo then
		fo:write(info)
		fo:close()
	end
	
	return ucode
end

local function ucodeToBricks(ucode, roms)
	local bricks = {}
	for uaddr, ucycle in pairs(ucode) do
		for romidx, uline in ipairs(ucycle) do
			for bitidx, t in pairs(uline) do
				local rom = roms[romidx]
				assert(bitidx >=0 and bitidx <= rom.size[3]-1)
				local x = rom.pos[1] + (uaddr % rom.size[1])
				local y = rom.pos[2] - math.floor(uaddr / rom.size[1])
				local z = rom.pos[3] + bitidx
				bricks[x] = bricks[x] or {}
				bricks[x][y] = bricks[x][y] or {}
				assert(not bricks[x][y][z], "overlapping brick at "..x.." "..y.." "..z)
				bricks[x][y][z] = true
			end
		end
	end
	return bricks
end

ts = ts or {
	eval = function() end,
	call = function() end,
}
ts.eval [[
	function commandShiftBrick(%x, %y, %z) { commandToServer('shiftBrick', %x, %y, %z); }
	function commandPlantBrick() { commandToServer('plantBrick'); }
]]
local function plantBrickAt(pos, brickPos)
	local dx, dy, dz = pos[1]-brickPos[1], pos[2]-brickPos[2], pos[3]-brickPos[3]
	ts.call("commandShiftBrick", dy, -dx, dz)
	brickPos[1] = pos[1]; brickPos[2] = pos[2]; brickPos[3] = pos[3];
	ts.call("commandPlantBrick")
end
local function buildBricks(bricks)
	local brickPos = {0, 0, 0}
	for x, xb in pairs(bricks) do
		for y, xyb in pairs(xb) do
			for z, t in pairs(xyb) do
				plantBrickAt({x, y, z}, brickPos)
			end
		end
	end
end

local function buildArch(arch)
	local ucode = archToUcode(arch)
	local bricks = ucodeToBricks(ucode, arch.roms)
	buildBricks(bricks)
end

local arch = require("rom-8608-defs")
buildArch(arch)