--- Assembly dialect detection
-- @module detect

local detect = {}

-- Each marker is deliberately architecture-specific enough
-- to avoid common directive noise
local dialects = {
	{
		module = "asm.gnu_aarch64",
		markers = {
			{ "%.arch%s+armv8", 8 },
			{ "%f[%a]aarch64%f[%A]", 8 },
			{ "%f[%a]adrp%f[%A]", 4 },
			{ "%f[%w][xw][12][0-9]%f[^%w]", 3 },
			{ "%f[%w][xw]30%f[^%w]", 4 },
			{ "%f[%w][xw]zr%f[^%w]", 6 },
			{ "%f[%w]v[12][0-9]%.%d+[bhsd]%f[^%w]", 3 },
		},
	},
	{
		module = "asm.gnu_arm",
		markers = {
			{ "%.syntax%s+unified", 8 },
			{ "%.thumb%f[%W]", 7 },
			{ "%.code%s+32", 7 },
			{ "%f[%a]arm_arch%f[%A]", 5 },
			{ "%f[%a]vld%d?%.", 5 },
			{ "%f[%a]vst%d?%.", 5 },
			{ "%f[%a]stmdb%f[%A]", 5 },
			{ "%f[%a]ldmia%f[%A]", 5 },
			{ "%f[%w]r1[0-5]%f[^%w]", 2 },
			{ "\n%s*@", 4 },
		},
	},
	{
		module = "asm.gnu_riscv",
		markers = {
			{ "%.option%f[%W]", 7 },
			{ "%%pcrel_", 7 },
			{ "%f[%a]riscv%f[%A]", 8 },
			{ "%f[%a]auipc%f[%A]", 5 },
			{ "%f[%a]csrr[%a]*%f[%A]", 5 },
			{ "%f[%a]amo[%a]*%.[bhdw]%f[^%w]", 5 },
			{ "%f[%a]mstatus%f[%A]", 5 },
			{ "%f[%a]vsetvli%f[%A]", 6 },
		},
	},
	{
		module = "asm.gnu_x86",
		markers = {
			{ "%.intel_syntax%f[%W]", 8 },
			{ "%f[%a]x86_64%f[%A]", 6 },
			{ "%f[%a]amd64%f[%A]", 6 },
			{ "%f[%a]sse[%w_]*", 5 },
			{ "%f[%a]avx[%w_]*", 5 },
			{ "%f[%d]3dnow[%w_]*", 5 },
			{ "%%[er]?[abcd]x%f[^%w]", 4 },
			{ "%%[er]?[sb]p%f[^%w]", 4 },
			{ "%%[er]?[sd]i%f[^%w]", 4 },
			{ "%%r1[0-5][bwd]?%f[^%w]", 4 },
			{ "%%[xyz]mm%d+%f[^%w]", 4 },
			{ "%f[%a]cpuid%f[%A]", 4 },
		},
	},
}

--- Score one dialect without letting repeated operands dominate decision
-- @param data string lowercase opening data
-- @param markers table weighted dialect markers
-- @return number score
local function score_dialect(data, markers)
	local score = 0

	for _, marker in ipairs(markers) do
		if data:find(marker[1]) then
			score = score + marker[2]
		end
	end

	return score
end

--- Return internal module that should interpret one assembly file
-- @param filename string candidate filename
-- @param data string opening file data
-- @return string module_name
function detect.select_module(filename, data)
	local lowercase_filename = (filename or ""):lower()
	if lowercase_filename:match("%.asm$") then
		return "asm.nasm_x86"
	end

	local sample = (data or ""):sub(1, 65536):lower()
	local selected_module = "asm.gnu_generic"
	local selected_score = 0

	for _, dialect in ipairs(dialects) do
		local score = score_dialect(sample, dialect.markers)
		if score > selected_score then
			selected_module = dialect.module
			selected_score = score
		end
	end

	-- One strong marker or two weaker markers required before choosing architecture
	if selected_score < 4 then
		return "asm.gnu_generic"
	end

	return selected_module
end

return detect
