-- SPDX-License-Identifier: MIT
-- Copyright (C) 2018-present iced project and contributors

local from_hex = require("iced_test_utils").from_hex
local FormatterSyntax = require("iced_x86.FormatterSyntax")

local fmt_syntaxes = {
	FormatterSyntax.Gas,
	FormatterSyntax.Intel,
	FormatterSyntax.Masm,
	FormatterSyntax.Nasm,
}

describe("Formatter", function()
	local CC_a = require("iced_x86.CC_a")
	local CC_ae = require("iced_x86.CC_ae")
	local CC_b = require("iced_x86.CC_b")
	local CC_be = require("iced_x86.CC_be")
	local CC_e = require("iced_x86.CC_e")
	local CC_g = require("iced_x86.CC_g")
	local CC_ge = require("iced_x86.CC_ge")
	local CC_l = require("iced_x86.CC_l")
	local CC_le = require("iced_x86.CC_le")
	local CC_ne = require("iced_x86.CC_ne")
	local CC_np = require("iced_x86.CC_np")
	local CC_p = require("iced_x86.CC_p")
	local Decoder = require("iced_x86.Decoder")
	local Formatter = require("iced_x86.Formatter")
	local MemorySizeOptions = require("iced_x86.MemorySizeOptions")
	local OpAccess = require("iced_x86.OpAccess")
	local Register = require("iced_x86.Register")

	it("invalid syntax", function()
		assert.has_error(function()
			Formatter.new(0x789A)
		end)
	end)

	it("invalid op access arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local decoder = Decoder.new(64, from_hex("62F24FDD725001"))
			local instr = decoder:decode()
			local formatter = Formatter.new(syntax)
			formatter:op_access(instr, OpAccess.None)
			assert.has_error(function()
				formatter:op_access(instr, 100)
			end)
			assert.has_error(function()
				formatter:op_access(instr, -0x80000001)
			end)
			assert.has_error(function()
				formatter:op_access(instr, 0x100000000)
			end)
		end
	end)

	it("invalid get_instruction_operand() arg)", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local decoder = Decoder.new(64, from_hex("62F24FDD725001"))
			local instr = decoder:decode()
			local formatter = Formatter.new(syntax)
			formatter:get_instruction_operand(instr, 0)
			assert.has_error(function()
				formatter:get_instruction_operand(instr, 100)
			end)
			assert.has_error(function()
				formatter:get_instruction_operand(instr, -0x80000001)
			end)
			assert.has_error(function()
				formatter:get_instruction_operand(instr, 0x100000000)
			end)
		end
	end)

	it("invalid format_operand() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local decoder = Decoder.new(64, from_hex("62F24FDD725001"))
			local instr = decoder:decode()
			local formatter = Formatter.new(syntax)
			formatter:format_operand(instr, 0)
			assert.has_error(function()
				formatter:format_operand(instr, 100)
			end)
			assert.has_error(function()
				formatter:format_operand(instr, -0x80000001)
			end)
			assert.has_error(function()
				formatter:format_operand(instr, 0x100000000)
			end)
		end
	end)

	it("number base", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			for base = 0, 20 do
				local formatter = Formatter.new(syntax)
				if base == 2 or base == 8 or base == 10 or base == 16 then
					assert.equals(16, formatter:number_base())
					formatter:set_number_base(base)
					assert.equals(base, formatter:number_base())
				else
					assert.equals(16, formatter:number_base())
					assert.has_error(function()
						formatter:set_number_base(base)
					end)
					assert.equals(16, formatter:number_base())
				end
			end
			do
				local formatter = Formatter.new(syntax)
				assert.has_error(function()
					formatter:set_number_base(-0x80000001)
				end)
				assert.has_error(function()
					formatter:set_number_base(0x100000000)
				end)
			end
		end
	end)

	it("options", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)

			formatter:set_uppercase_prefixes(true)
			formatter:set_uppercase_mnemonics(true)
			formatter:set_uppercase_registers(true)
			formatter:set_uppercase_keywords(true)
			formatter:set_uppercase_decorators(true)
			formatter:set_uppercase_all(true)
			formatter:set_first_operand_char_index(10)
			formatter:set_tab_size(4)
			formatter:set_space_after_operand_separator(true)
			formatter:set_space_after_memory_bracket(true)
			formatter:set_space_between_memory_add_operators(true)
			formatter:set_space_between_memory_mul_operators(true)
			formatter:set_scale_before_index(true)
			formatter:set_always_show_scale(true)
			formatter:set_always_show_segment_register(true)
			formatter:set_show_zero_displacements(true)
			formatter:set_hex_prefix("0X")
			formatter:set_hex_suffix("H")
			formatter:set_hex_digit_group_size(5)
			formatter:set_decimal_prefix("0D")
			formatter:set_decimal_suffix("D")
			formatter:set_decimal_digit_group_size(6)
			formatter:set_octal_prefix("0O")
			formatter:set_octal_suffix("O")
			formatter:set_octal_digit_group_size(7)
			formatter:set_binary_prefix("0B")
			formatter:set_binary_suffix("B")
			formatter:set_binary_digit_group_size(8)
			formatter:set_digit_separator("`")
			formatter:set_leading_zeros(true)
			formatter:set_uppercase_hex(false)
			formatter:set_small_hex_numbers_in_decimal(false)
			formatter:set_add_leading_zero_to_hex_numbers(false)
			formatter:set_number_base(8)
			formatter:set_branch_leading_zeros(false)
			formatter:set_signed_immediate_operands(true)
			formatter:set_signed_memory_displacements(false)
			formatter:set_displacement_leading_zeros(true)
			formatter:set_memory_size_options(MemorySizeOptions.Never)
			formatter:set_rip_relative_addresses(true)
			formatter:set_show_branch_size(false)
			formatter:set_use_pseudo_ops(false)
			formatter:set_show_symbol_address(true)
			formatter:set_gas_naked_registers(true)
			formatter:set_gas_show_mnemonic_size_suffix(true)
			formatter:set_gas_space_after_memory_operand_comma(true)
			formatter:set_masm_add_ds_prefix32(false)
			formatter:set_masm_symbol_displ_in_brackets(false)
			formatter:set_masm_displ_in_brackets(false)
			formatter:set_nasm_show_sign_extended_immediate_size(true)
			formatter:set_prefer_st0(true)
			formatter:set_show_useless_prefixes(true)
			formatter:set_cc_b(CC_b.c)
			formatter:set_cc_ae(CC_ae.nb)
			formatter:set_cc_e(CC_e.z)
			formatter:set_cc_ne(CC_ne.nz)
			formatter:set_cc_be(CC_be.na)
			formatter:set_cc_a(CC_a.nbe)
			formatter:set_cc_p(CC_p.pe)
			formatter:set_cc_np(CC_np.po)
			formatter:set_cc_l(CC_l.nge)
			formatter:set_cc_ge(CC_ge.nl)
			formatter:set_cc_le(CC_le.ng)
			formatter:set_cc_g(CC_g.nle)

			assert.is_true(formatter:uppercase_prefixes())
			assert.is_true(formatter:uppercase_mnemonics())
			assert.is_true(formatter:uppercase_registers())
			assert.is_true(formatter:uppercase_keywords())
			assert.is_true(formatter:uppercase_decorators())
			assert.is_true(formatter:uppercase_all())
			assert.equals(10, formatter:first_operand_char_index())
			assert.equals(4, formatter:tab_size())
			assert.is_true(formatter:space_after_operand_separator())
			assert.is_true(formatter:space_after_memory_bracket())
			assert.is_true(formatter:space_between_memory_add_operators())
			assert.is_true(formatter:space_between_memory_mul_operators())
			assert.is_true(formatter:scale_before_index())
			assert.is_true(formatter:always_show_scale())
			assert.is_true(formatter:always_show_segment_register())
			assert.is_true(formatter:show_zero_displacements())
			assert.equals("0X", formatter:hex_prefix())
			assert.equals("H", formatter:hex_suffix())
			assert.equals(5, formatter:hex_digit_group_size())
			assert.equals("0D", formatter:decimal_prefix())
			assert.equals("D", formatter:decimal_suffix())
			assert.equals(6, formatter:decimal_digit_group_size())
			assert.equals("0O", formatter:octal_prefix())
			assert.equals("O", formatter:octal_suffix())
			assert.equals(7, formatter:octal_digit_group_size())
			assert.equals("0B", formatter:binary_prefix())
			assert.equals("B", formatter:binary_suffix())
			assert.equals(8, formatter:binary_digit_group_size())
			assert.equals("`", formatter:digit_separator())
			assert.is_true(formatter:leading_zeros())
			assert.is_false(formatter:uppercase_hex())
			assert.is_false(formatter:small_hex_numbers_in_decimal())
			assert.is_false(formatter:add_leading_zero_to_hex_numbers())
			assert.equals(8, formatter:number_base())
			assert.is_false(formatter:branch_leading_zeros())
			assert.is_true(formatter:signed_immediate_operands())
			assert.is_false(formatter:signed_memory_displacements())
			assert.is_true(formatter:displacement_leading_zeros())
			assert.equals(MemorySizeOptions.Never, formatter:memory_size_options())
			assert.is_true(formatter:rip_relative_addresses())
			assert.is_false(formatter:show_branch_size())
			assert.is_false(formatter:use_pseudo_ops())
			assert.is_true(formatter:show_symbol_address())
			assert.is_true(formatter:gas_naked_registers())
			assert.is_true(formatter:gas_show_mnemonic_size_suffix())
			assert.is_true(formatter:gas_space_after_memory_operand_comma())
			assert.is_false(formatter:masm_add_ds_prefix32())
			assert.is_false(formatter:masm_symbol_displ_in_brackets())
			assert.is_false(formatter:masm_displ_in_brackets())
			assert.is_true(formatter:nasm_show_sign_extended_immediate_size())
			assert.is_true(formatter:prefer_st0())
			assert.is_true(formatter:show_useless_prefixes())
			assert.equals(CC_b.c, formatter:cc_b())
			assert.equals(CC_ae.nb, formatter:cc_ae())
			assert.equals(CC_e.z, formatter:cc_e())
			assert.equals(CC_ne.nz, formatter:cc_ne())
			assert.equals(CC_be.na, formatter:cc_be())
			assert.equals(CC_a.nbe, formatter:cc_a())
			assert.equals(CC_p.pe, formatter:cc_p())
			assert.equals(CC_np.po, formatter:cc_np())
			assert.equals(CC_l.nge, formatter:cc_l())
			assert.equals(CC_ge.nl, formatter:cc_ge())
			assert.equals(CC_le.ng, formatter:cc_le())
			assert.equals(CC_g.nle, formatter:cc_g())
		end
	end)

	it("invalid memory_size_options() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_memory_size_options(MemorySizeOptions.Default)
			assert.has_error(function()
				formatter:set_memory_size_options(123)
			end)
			assert.has_error(function()
				formatter:set_memory_size_options(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_memory_size_options(0x100000000)
			end)
		end
	end)

	it("invalid cc_b() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_b(CC_b.b)
			assert.has_error(function()
				formatter:set_cc_b(123)
			end)
			assert.has_error(function()
				formatter:set_cc_b(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_b(0x100000000)
			end)
		end
	end)

	it("invalid cc_ae() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_ae(CC_ae.ae)
			assert.has_error(function()
				formatter:set_cc_ae(123)
			end)
			assert.has_error(function()
				formatter:set_cc_ae(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_ae(0x100000000)
			end)
		end
	end)

	it("invalid cc_e() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_e(CC_e.e)
			assert.has_error(function()
				formatter:set_cc_e(123)
			end)
			assert.has_error(function()
				formatter:set_cc_e(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_e(0x100000000)
			end)
		end
	end)

	it("invalid cc_ne() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_ne(CC_ne.ne)
			assert.has_error(function()
				formatter:set_cc_ne(123)
			end)
			assert.has_error(function()
				formatter:set_cc_ne(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_ne(0x100000000)
			end)
		end
	end)

	it("invalid cc_be() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_be(CC_be.be)
			assert.has_error(function()
				formatter:set_cc_be(123)
			end)
			assert.has_error(function()
				formatter:set_cc_be(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_be(0x100000000)
			end)
		end
	end)

	it("invalid cc_a() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_a(CC_a.a)
			assert.has_error(function()
				formatter:set_cc_a(123)
			end)
			assert.has_error(function()
				formatter:set_cc_a(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_a(0x100000000)
			end)
		end
	end)

	it("invalid cc_p() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_p(CC_p.p)
			assert.has_error(function()
				formatter:set_cc_p(123)
			end)
			assert.has_error(function()
				formatter:set_cc_p(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_p(0x100000000)
			end)
		end
	end)

	it("invalid cc_np() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_np(CC_np.np)
			assert.has_error(function()
				formatter:set_cc_np(123)
			end)
			assert.has_error(function()
				formatter:set_cc_np(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_np(0x100000000)
			end)
		end
	end)

	it("invalid cc_l() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_l(CC_l.l)
			assert.has_error(function()
				formatter:set_cc_l(123)
			end)
			assert.has_error(function()
				formatter:set_cc_l(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_l(0x100000000)
			end)
		end
	end)

	it("invalid cc_ge() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_ge(CC_ge.ge)
			assert.has_error(function()
				formatter:set_cc_ge(123)
			end)
			assert.has_error(function()
				formatter:set_cc_ge(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_ge(0x100000000)
			end)
		end
	end)

	it("invalid cc_le() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_le(CC_le.le)
			assert.has_error(function()
				formatter:set_cc_le(123)
			end)
			assert.has_error(function()
				formatter:set_cc_le(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_le(0x100000000)
			end)
		end
	end)

	it("invalid cc_g() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:set_cc_g(CC_g.g)
			assert.has_error(function()
				formatter:set_cc_g(123)
			end)
			assert.has_error(function()
				formatter:set_cc_g(-0x80000001)
			end)
			assert.has_error(function()
				formatter:set_cc_g(0x100000000)
			end)
		end
	end)

	it("invalid format i8/u8 arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:format_i8(0)
			assert.has_error(function()
				formatter:format_i8(-0x81)
			end)
			assert.has_error(function()
				formatter:format_i8(0x100)
			end)
			formatter:format_u8(0)
			assert.has_error(function()
				formatter:format_u8(-0x81)
			end)
			assert.has_error(function()
				formatter:format_u8(0x100)
			end)
		end
	end)

	it("invalid format i16/u16 arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:format_i16(0)
			assert.has_error(function()
				formatter:format_i16(-0x8001)
			end)
			assert.has_error(function()
				formatter:format_i16(0x10000)
			end)
			formatter:format_u16(0)
			assert.has_error(function()
				formatter:format_u16(-0x8001)
			end)
			assert.has_error(function()
				formatter:format_u16(0x10000)
			end)
		end
	end)

	it("invalid format i32/u32 arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:format_i32(0)
			assert.has_error(function()
				formatter:format_i32(-0x80000001)
			end)
			assert.has_error(function()
				formatter:format_i32(0x100000000)
			end)
			formatter:format_u32(0)
			assert.has_error(function()
				formatter:format_u32(-0x80000001)
			end)
			assert.has_error(function()
				formatter:format_u32(0x100000000)
			end)
		end
	end)

	it("invalid format_mnemonic() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local decoder = Decoder.new(64, from_hex("62F24FDD725001"))
			local instr = decoder:decode()
			local formatter = Formatter.new(syntax)
			formatter:format_mnemonic(instr, 0)
			assert.has_error(function()
				formatter:format_mnemonic(instr, -0x80000001)
			end)
			assert.has_error(function()
				formatter:format_mnemonic(instr, 0x100000000)
			end)
		end
	end)

	it("invalid get_formatter_operand() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local decoder = Decoder.new(64, from_hex("62F24FDD725001"))
			local instr = decoder:decode()
			local formatter = Formatter.new(syntax)
			formatter:get_formatter_operand(instr, 0)
			assert.has_error(function()
				formatter:get_formatter_operand(instr, 0x789A)
			end)
			assert.has_error(function()
				formatter:get_formatter_operand(instr, -0x80000001)
			end)
			assert.has_error(function()
				formatter:get_formatter_operand(instr, 0x100000000)
			end)
		end
	end)

	it("invalid format_register() arg", function()
		for _, syntax in ipairs(fmt_syntaxes) do
			local formatter = Formatter.new(syntax)
			formatter:format_register(Register.RCX)
			assert.has_error(function()
				formatter:format_register(0x789A)
			end)
			assert.has_error(function()
				formatter:format_register(-0x80000001)
			end)
			assert.has_error(function()
				formatter:format_register(0x100000000)
			end)
		end
	end)
end)
