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

use crate::formatter::enums::{FormatterFlowControl, PrefixKind};
use crate::formatter::enums_shared::FormatterTextKind;
use crate::formatter::fmt_utils_all::{show_rep_or_repe_prefix_bool, show_repne_prefix_bool, show_segment_prefix_bool};
use crate::formatter::{FormatterOptions, FormatterOutput};
use crate::{Code, Instruction, PrefixKindUnderlyingType, Register};
use core::{cmp, mem};
use static_assertions::const_assert_eq;

#[rustfmt::skip]
static SPACES_TABLE: [&str; 12] = [
	" ",
	"  ",
	"   ",
	"    ",
	"     ",
	"      ",
	"       ",
	"        ",
	"         ",
	"          ",
	"           ",
	"            ",
];
#[rustfmt::skip]
static TABS_TABLE: [&str; 4] = [
	"\t",
	"\t\t",
	"\t\t\t",
	"\t\t\t\t",
];

pub(super) fn add_tabs(output: &mut dyn FormatterOutput, mut column: u32, mut first_operand_char_index: u32, tab_size: u32) {
	const MAX_FIRST_OPERAND_CHAR_INDEX: u32 = 256;
	first_operand_char_index = cmp::min(first_operand_char_index, MAX_FIRST_OPERAND_CHAR_INDEX);

	if tab_size == 0 {
		let chars_left = if first_operand_char_index <= column { 1 } else { first_operand_char_index - column };
		add_strings(output, &SPACES_TABLE[..], chars_left);
	} else {
		let end_col = if first_operand_char_index <= column { column + 1 } else { first_operand_char_index };
		let end_col_rounded_down = end_col / tab_size * tab_size;
		let added_tabs = end_col_rounded_down > column;
		if added_tabs {
			let tabs = (end_col_rounded_down - (column / tab_size * tab_size)) / tab_size;
			add_strings(output, &TABS_TABLE[..], tabs);
			column = end_col_rounded_down;
		}
		if first_operand_char_index > column {
			add_strings(output, &SPACES_TABLE[..], first_operand_char_index - column);
		} else if !added_tabs {
			add_strings(output, &SPACES_TABLE[..], 1);
		}
	}
}

fn add_strings(output: &mut dyn FormatterOutput, strings: &[&str], count: u32) {
	let mut count = count as usize;
	while count > 0 {
		let n = cmp::min(count, strings.len());
		output.write(strings[n - 1], FormatterTextKind::Text);
		count -= n;
	}
}

#[must_use]
#[inline]
pub(super) fn is_call(kind: FormatterFlowControl) -> bool {
	kind == FormatterFlowControl::NearCall || kind == FormatterFlowControl::FarCall
}

#[must_use]
pub(super) fn get_flow_control(instruction: &Instruction) -> FormatterFlowControl {
	#[cfg_attr(feature = "cargo-fmt", rustfmt::skip)]
	match instruction.code() {
		// GENERATOR-BEGIN: FormatterFlowControlSwitch
		// ⚠️This was generated by GENERATOR!🦹‍♂️
		Code::Jo_rel8_16
		| Code::Jo_rel8_32
		| Code::Jo_rel8_64
		| Code::Jno_rel8_16
		| Code::Jno_rel8_32
		| Code::Jno_rel8_64
		| Code::Jb_rel8_16
		| Code::Jb_rel8_32
		| Code::Jb_rel8_64
		| Code::Jae_rel8_16
		| Code::Jae_rel8_32
		| Code::Jae_rel8_64
		| Code::Je_rel8_16
		| Code::Je_rel8_32
		| Code::Je_rel8_64
		| Code::Jne_rel8_16
		| Code::Jne_rel8_32
		| Code::Jne_rel8_64
		| Code::Jbe_rel8_16
		| Code::Jbe_rel8_32
		| Code::Jbe_rel8_64
		| Code::Ja_rel8_16
		| Code::Ja_rel8_32
		| Code::Ja_rel8_64
		| Code::Js_rel8_16
		| Code::Js_rel8_32
		| Code::Js_rel8_64
		| Code::Jns_rel8_16
		| Code::Jns_rel8_32
		| Code::Jns_rel8_64
		| Code::Jp_rel8_16
		| Code::Jp_rel8_32
		| Code::Jp_rel8_64
		| Code::Jnp_rel8_16
		| Code::Jnp_rel8_32
		| Code::Jnp_rel8_64
		| Code::Jl_rel8_16
		| Code::Jl_rel8_32
		| Code::Jl_rel8_64
		| Code::Jge_rel8_16
		| Code::Jge_rel8_32
		| Code::Jge_rel8_64
		| Code::Jle_rel8_16
		| Code::Jle_rel8_32
		| Code::Jle_rel8_64
		| Code::Jg_rel8_16
		| Code::Jg_rel8_32
		| Code::Jg_rel8_64
		| Code::Jmp_rel8_16
		| Code::Jmp_rel8_32
		| Code::Jmp_rel8_64
		| Code::VEX_KNC_Jkzd_kr_rel8_64
		| Code::VEX_KNC_Jknzd_kr_rel8_64
		=> FormatterFlowControl::ShortBranch,
		Code::Loopne_rel8_16_CX
		| Code::Loopne_rel8_32_CX
		| Code::Loopne_rel8_16_ECX
		| Code::Loopne_rel8_32_ECX
		| Code::Loopne_rel8_64_ECX
		| Code::Loopne_rel8_16_RCX
		| Code::Loopne_rel8_64_RCX
		| Code::Loope_rel8_16_CX
		| Code::Loope_rel8_32_CX
		| Code::Loope_rel8_16_ECX
		| Code::Loope_rel8_32_ECX
		| Code::Loope_rel8_64_ECX
		| Code::Loope_rel8_16_RCX
		| Code::Loope_rel8_64_RCX
		| Code::Loop_rel8_16_CX
		| Code::Loop_rel8_32_CX
		| Code::Loop_rel8_16_ECX
		| Code::Loop_rel8_32_ECX
		| Code::Loop_rel8_64_ECX
		| Code::Loop_rel8_16_RCX
		| Code::Loop_rel8_64_RCX
		| Code::Jcxz_rel8_16
		| Code::Jcxz_rel8_32
		| Code::Jecxz_rel8_16
		| Code::Jecxz_rel8_32
		| Code::Jecxz_rel8_64
		| Code::Jrcxz_rel8_16
		| Code::Jrcxz_rel8_64
		=> FormatterFlowControl::AlwaysShortBranch,
		Code::Call_rel16
		| Code::Call_rel32_32
		| Code::Call_rel32_64
		=> FormatterFlowControl::NearCall,
		Code::Jmp_rel16
		| Code::Jmp_rel32_32
		| Code::Jmp_rel32_64
		| Code::Jo_rel16
		| Code::Jo_rel32_32
		| Code::Jo_rel32_64
		| Code::Jno_rel16
		| Code::Jno_rel32_32
		| Code::Jno_rel32_64
		| Code::Jb_rel16
		| Code::Jb_rel32_32
		| Code::Jb_rel32_64
		| Code::Jae_rel16
		| Code::Jae_rel32_32
		| Code::Jae_rel32_64
		| Code::Je_rel16
		| Code::Je_rel32_32
		| Code::Je_rel32_64
		| Code::Jne_rel16
		| Code::Jne_rel32_32
		| Code::Jne_rel32_64
		| Code::Jbe_rel16
		| Code::Jbe_rel32_32
		| Code::Jbe_rel32_64
		| Code::Ja_rel16
		| Code::Ja_rel32_32
		| Code::Ja_rel32_64
		| Code::Js_rel16
		| Code::Js_rel32_32
		| Code::Js_rel32_64
		| Code::Jns_rel16
		| Code::Jns_rel32_32
		| Code::Jns_rel32_64
		| Code::Jp_rel16
		| Code::Jp_rel32_32
		| Code::Jp_rel32_64
		| Code::Jnp_rel16
		| Code::Jnp_rel32_32
		| Code::Jnp_rel32_64
		| Code::Jl_rel16
		| Code::Jl_rel32_32
		| Code::Jl_rel32_64
		| Code::Jge_rel16
		| Code::Jge_rel32_32
		| Code::Jge_rel32_64
		| Code::Jle_rel16
		| Code::Jle_rel32_32
		| Code::Jle_rel32_64
		| Code::Jg_rel16
		| Code::Jg_rel32_32
		| Code::Jg_rel32_64
		| Code::Jmpe_disp16
		| Code::Jmpe_disp32
		| Code::VEX_KNC_Jkzd_kr_rel32_64
		| Code::VEX_KNC_Jknzd_kr_rel32_64
		=> FormatterFlowControl::NearBranch,
		Code::Call_ptr1616
		| Code::Call_ptr1632
		=> FormatterFlowControl::FarCall,
		Code::Jmp_ptr1616
		| Code::Jmp_ptr1632
		=> FormatterFlowControl::FarBranch,
		Code::Xbegin_rel16
		| Code::Xbegin_rel32
		=> FormatterFlowControl::Xbegin,
		// GENERATOR-END: FormatterFlowControlSwitch
		_ => unreachable!(),
	}
}

pub(super) fn show_rep_or_repe_prefix(code: Code, options: &FormatterOptions) -> bool {
	show_rep_or_repe_prefix_bool(code, options.show_useless_prefixes())
}

pub(super) fn show_repne_prefix(code: Code, options: &FormatterOptions) -> bool {
	show_repne_prefix_bool(code, options.show_useless_prefixes())
}

#[must_use]
#[inline]
pub(super) fn get_segment_register_prefix_kind(register: Register) -> PrefixKind {
	debug_assert!(
		register == Register::ES
			|| register == Register::CS
			|| register == Register::SS
			|| register == Register::DS
			|| register == Register::FS
			|| register == Register::GS
	);
	const_assert_eq!(PrefixKind::ES as u32 + 1, PrefixKind::CS as u32);
	const_assert_eq!(PrefixKind::ES as u32 + 2, PrefixKind::SS as u32);
	const_assert_eq!(PrefixKind::ES as u32 + 3, PrefixKind::DS as u32);
	const_assert_eq!(PrefixKind::ES as u32 + 4, PrefixKind::FS as u32);
	const_assert_eq!(PrefixKind::ES as u32 + 5, PrefixKind::GS as u32);
	// SAFETY: callers only pass in a valid segment register (ES,CS,SS,DS,FS,GS)
	unsafe { mem::transmute(((register as u32 - Register::ES as u32) + PrefixKind::ES as u32) as PrefixKindUnderlyingType) }
}

pub(super) fn show_index_scale(instruction: &Instruction, options: &FormatterOptions) -> bool {
	options.show_useless_prefixes() || !instruction.code().ignores_index()
}

pub(super) fn show_segment_prefix(default_seg_reg: Register, instruction: &Instruction, options: &FormatterOptions) -> bool {
	show_segment_prefix_bool(default_seg_reg, instruction, options.show_useless_prefixes())
}

#[allow(unused_variables)]
pub(super) fn can_show_rounding_control(instruction: &Instruction, options: &FormatterOptions) -> bool {
	#[cfg(not(feature = "no_evex"))]
	{
		let code = instruction.code();
		if code == Code::EVEX_Vcvtsi2sd_xmm_xmm_rm32_er
			|| code == Code::EVEX_Vcvtusi2sd_xmm_xmm_rm32_er
			|| code == Code::EVEX_Vcvtdq2pd_zmm_k1z_ymmm256b32_er
			|| code == Code::EVEX_Vcvtudq2pd_zmm_k1z_ymmm256b32_er
		{
			return options.show_useless_prefixes();
		}
	}
	true
}
