-- Copyright (c) 2023 Adrian "asie" Siekierka
--
-- Licensed under the Apache License, Version 2.0 with LLVM Exceptions,
-- See https://github.com/llvm-mos/llvm-mos-sdk/blob/main/LICENSE for license
-- information.

local CONFIG_MAP = {
	-- mapper name (program argument)
	["anrom"] = {
		-- bits, value, comparator, step
		{4, "__prg_rom_size", ">log2", 32, ["description"] = "PRG ROM bank"},
		-- bits, {0 == always clear, 1 == always set, nil = any value}
		{1, nil, ["description"] = "1 KB VRAM page selector"},
		-- no bus conflicts (empty table) if...
		["none_if"] = "__submapper == 1"
	},
	["bnrom"] = {
		{8, "__prg_rom_size", ">log2", 32, ["description"] = "PRG ROM bank"}
	},
	["cnrom"] = {
		{8, "(__chr_rom_size + __chr_ram_size + __chr_nvram_size)", ">log2", 8, ["description"] = "CHR ROM bank"},
		["none_if"] = "__submapper == 1"
	},
	["color-dreams"] = {
		{2, "__prg_rom_size", ">log2", 32, ["description"] = "PRG ROM bank"},
		{2, nil, ["description"] = "Lockout"},
		{4, "(__chr_rom_size + __chr_ram_size + __chr_nvram_size)", ">log2", 4, ["description"] = "CHR ROM bank"}
	},
	["cprom"] = {
		{8, "(__chr_rom_size + __chr_ram_size + __chr_nvram_size)", ">log2", 4, ["description"] = "CHR RAM bank"}
	},
	["gnrom"] = {
		{4, "__chr_rom_size", ">log2", 8, ["description"] = "CHR ROM bank"},
		{4, "__prg_rom_size", ">log2", 32, ["description"] = "PRG ROM bank"}
	},
	["unrom"] = {
		{8, "__prg_rom_size", ">log2", 16, ["description"] = "PRG ROM bank"},
		["none_if"] = "__submapper == 1"
	},
	["unrom-512"] = {
		{5, "__prg_rom_size", ">log2", 16, ["description"] = "PRG ROM bank"},
		{2, "(__chr_rom_size + __chr_ram_size + __chr_nvram_size)", ">log2", 8, ["description"] = "CHR ROM bank"},
		{1, "(__mirroring == 0 && __four_screen == 1)", "==", ["description"] = "1-screen mirroring select"},
		["none_if"] = "__battery == 1"
	}
}

print([[/**
 * Automatically generated by generate-rompoke-mapper-linkscript.lua.
 *
 * This script generates the ROM poke table size (nes/misc/rompoke/README.md)
 * for writes of maximum values determined by the following bit field:
 *]])

local args = {...}
local config = CONFIG_MAP[args[1]]

-- Table: {"expression", value}
-- Maps to: if "expression", then value
results = {}

-- Print the bitfields.
local function bitfield_to_string(bitfield)
	local s = ""
	if bitfield[2] == nil then
		if bitfield["description"] ~= nil then
			return bitfield["description"]
		else
			return "any value"
		end
	elseif type(bitfield[2]) == "number" then
		s = "always " .. bitfield[2]
	else
		s = "linker expression: " .. bitfield[2]
	end
	if bitfield["description"] ~= nil then
		s = bitfield["description"] .. " (" .. s .. ")"
	end
	return s
end

local bitpos = 0
for i=1,#config do
	local bitfield = config[i]
	if bitfield[1] == 1 then
		print(string.format(" * bit  %d  : %s", bitpos, bitfield_to_string(bitfield)))
	else
		print(string.format(" * bits %d-%d: %s", bitpos, bitpos + bitfield[1] - 1, bitfield_to_string(bitfield)))
	end
	bitpos = bitpos + bitfield[1]
end

--- Append one linker condition to another using the logical AND operator.
local function cond_and(cond, cond2)
	if #cond == 0 then
		return cond2
	else
		return cond .. " && " .. cond2
	end
end

print(" */")

--- Step through one bitfield.
-- @param parts A table of all remaining bitfields. The function will process
--              the first among those, then call itself recursively with the
--              remainder.
-- @param value The maximum ROM write value from all previous conditions.
-- @param cond  The linker expression corresponding to this maximum ROM write
--              value.
-- @param shift The current position (value << shift) within the ROM write
--              value, from 0 to 8.
local function step_bitfield(parts, value, cond, shift)
	if #parts == 0 then
		-- If no parts remain, add the complete expression -> value mapping.
		table.insert(results, {cond, value})
		return
	end

	-- The current bitfield.
	local head = parts[1]
	local tail = {}
	for i=2,#parts do
		tail[i - 1] = parts[i]
	end

	-- The mask of the current bitfield.
	local mask = ((1 << head[1]) - 1) << shift
	-- The next shift position.
	local next_shift = shift + head[1]

	--- UNCONDITIONAL ---

	-- 0: always clear
	if head[2] == 0 then
		step_bitfield(tail, value, cond, next_shift)
		return
	end

	-- nil: clear or set
	-- 1: always set
	if head[2] == 1 or head[2] == nil then
		step_bitfield(tail, value | mask, cond, next_shift)
		return
	end

	--- CONDITIONAL ---

	-- ">log2": use value, log2 progression
	if head[3] == ">log2" then
		local v = (1 << (head[1] - 1))
		while v > 0 do
			local v_compare = v * (head[4] or 1)
			local v_mask = (((v << 1) - 1) << shift)
			step_bitfield(tail, value | v_mask, cond_and(cond, head[2] .. " > " .. v_compare), next_shift)
			v = v >> 1
		end
		step_bitfield(tail, value, cond, next_shift)
		return
	end

	-- ">": use value, linear progression
	if head[3] == ">" then
		local v_max = ((1 << head[1]) - 1)
		for v = v_max,0,-1 do
			local v_compare = v * (head[4] or 1)
			step_bitfield(tail, value | (v << shift), cond_and(cond, head[2] .. " > " .. v_compare), next_shift)
			v = v >> 1
		end
		step_bitfield(tail, value, cond, next_shift)
		return
	end

	-- "==": use value (true/false)
	if head[3] == "==" then
		step_bitfield(tail, value | mask, cond_and(cond, head[2]), next_shift)
		step_bitfield(tail, value, cond, next_shift)
		return
	end

	error("unknown condition: " .. head[3])
end

-- Step through all the bitfields.
step_bitfield(config, 0, "", 0)

table.sort(results, function(a, b)
	return a[2] > b[2]
end)

-- Write output.
io.write("__rom_poke_table_size = ")
if config["none_if"] ~= nil then
		io.write(string.format("(%s) ? %d : (", config["none_if"], 0))
end
for i=1,#results do
	if #results[i][1] == 0 then
		io.write(results[i][2])
	else
		io.write(string.format("(%s) ? %d : (", results[i][1], results[i][2]+1))
	end
end
for i=1,#results do
	if #results[i][1] ~= 0 then
		io.write(")")
	end
end
if config["none_if"] ~= nil then
	io.write(")")
end
print(";")
