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

use crate::{Register, RegisterUnderlyingType, RoundingControl, RoundingControlUnderlyingType};
use core::{fmt, mem};

// GENERATOR-BEGIN: MemoryOperandSize
// ⚠️This was generated by GENERATOR!🦹‍♂️
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[allow(dead_code)]
pub(crate) enum MemoryOperandSize {
	None,
	Byte,
	Word,
	Dword,
	Qword,
	Tbyte,
	Fword,
	Xword,
	Yword,
	Zword,
}
#[rustfmt::skip]
static GEN_DEBUG_MEMORY_OPERAND_SIZE: [&str; 10] = [
	"None",
	"Byte",
	"Word",
	"Dword",
	"Qword",
	"Tbyte",
	"Fword",
	"Xword",
	"Yword",
	"Zword",
];
impl fmt::Debug for MemoryOperandSize {
	#[inline]
	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
		write!(f, "{}", GEN_DEBUG_MEMORY_OPERAND_SIZE[*self as usize])
	}
}
impl Default for MemoryOperandSize {
	#[must_use]
	#[inline]
	fn default() -> Self {
		MemoryOperandSize::None
	}
}
// GENERATOR-END: MemoryOperandSize

struct CodeAsmOpStateFlags1;
impl CodeAsmOpStateFlags1 {
	pub const NONE: u8 = 0;
	pub const SEGMENT_SHIFT: u8 = 0;
	pub const SEGMENT_MASK: u8 = 7;
	pub const SIZE_SHIFT: u8 = 3;
	pub const SIZE_MASK: u8 = 0xF;
	pub const BROADCAST: u8 = 0x80;
}

struct CodeAsmOpStateFlags2;
impl CodeAsmOpStateFlags2 {
	pub const NONE: u8 = 0;
	pub const OP_MASK_SHIFT: u8 = 0;
	pub const OP_MASK_MASK: u8 = 7;
	pub const ROUNDING_CONTROL_SHIFT: u8 = 3;
	pub const ROUNDING_CONTROL_MASK: u8 = 7;
	pub const ZEROING_MASKING: u8 = 0x40;
	pub const SUPPRESS_ALL_EXCEPTIONS: u8 = 0x80;
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct CodeAsmOpState {
	flags1: u8, // CodeAsmOpStateFlags1
	flags2: u8, // CodeAsmOpStateFlags2
}

impl CodeAsmOpState {
	#[must_use]
	#[inline]
	pub const fn new() -> Self {
		Self { flags1: CodeAsmOpStateFlags1::NONE, flags2: CodeAsmOpStateFlags2::NONE }
	}

	#[must_use]
	#[inline]
	pub fn is_default(&self) -> bool {
		(self.flags1 | self.flags2) == 0
	}

	#[must_use]
	pub fn merge(&self, other: CodeAsmOpState) -> Self {
		const FLAGS1_KEEP: u8 = CodeAsmOpStateFlags1::BROADCAST;
		const FLAGS2_KEEP: u8 = (CodeAsmOpStateFlags2::OP_MASK_MASK << CodeAsmOpStateFlags2::OP_MASK_SHIFT)
			| CodeAsmOpStateFlags2::SUPPRESS_ALL_EXCEPTIONS
			| CodeAsmOpStateFlags2::ZEROING_MASKING;

		// Verify that all values are valid. If `self` and `other` have both set an opmask register,
		// we'll produce the wrong result but still a valid enum variant.
		const _: () = assert!(CodeAsmOpStateFlags2::OP_MASK_MASK == 7);

		let mut flags1 = (self.flags1 | other.flags1) & FLAGS1_KEEP;
		let mut flags2 = (self.flags2 | other.flags2) & FLAGS2_KEEP;

		macro_rules! copy_bits {
			($flags_name:ident, $mask:expr, $shift:expr) => {{
				const BITS: u8 = $mask << $shift;
				// No error if it's set in both `self` and `other`
				let bits = if (self.$flags_name & BITS) != 0 { self.$flags_name } else { other.$flags_name };
				$flags_name |= bits & BITS;
			}};
		}
		copy_bits!(flags1, CodeAsmOpStateFlags1::SEGMENT_MASK, CodeAsmOpStateFlags1::SEGMENT_SHIFT);
		copy_bits!(flags1, CodeAsmOpStateFlags1::SIZE_MASK, CodeAsmOpStateFlags1::SIZE_SHIFT);
		copy_bits!(flags2, CodeAsmOpStateFlags2::ROUNDING_CONTROL_MASK, CodeAsmOpStateFlags2::ROUNDING_CONTROL_SHIFT);

		Self { flags1, flags2 }
	}

	#[must_use]
	#[inline]
	#[allow(trivial_numeric_casts)]
	pub fn size(&self) -> MemoryOperandSize {
		// SAFETY: we only store valid variants
		unsafe { mem::transmute((self.flags1 >> CodeAsmOpStateFlags1::SIZE_SHIFT) & CodeAsmOpStateFlags1::SIZE_MASK) }
	}

	#[inline]
	pub fn ptr(&mut self, size: MemoryOperandSize) {
		self.flags1 = (self.flags1 & !((CodeAsmOpStateFlags1::SIZE_MASK << CodeAsmOpStateFlags1::SIZE_SHIFT) | CodeAsmOpStateFlags1::BROADCAST))
			| ((size as u8) << CodeAsmOpStateFlags1::SIZE_SHIFT);
	}

	#[inline]
	pub fn bcst(&mut self, size: MemoryOperandSize) {
		self.flags1 = (self.flags1 & !(CodeAsmOpStateFlags1::SIZE_MASK << CodeAsmOpStateFlags1::SIZE_SHIFT))
			| (((size as u8) << CodeAsmOpStateFlags1::SIZE_SHIFT) | CodeAsmOpStateFlags1::BROADCAST);
	}

	#[must_use]
	#[inline]
	#[allow(trivial_numeric_casts)]
	pub fn segment(&self) -> Register {
		let number = ((self.flags1 >> CodeAsmOpStateFlags1::SEGMENT_SHIFT) & CodeAsmOpStateFlags1::SEGMENT_MASK).wrapping_sub(1);
		const _: () = assert!(Register::ES as u32 + 1 == Register::CS as u32);
		const _: () = assert!(Register::ES as u32 + 2 == Register::SS as u32);
		const _: () = assert!(Register::ES as u32 + 3 == Register::DS as u32);
		const _: () = assert!(Register::ES as u32 + 4 == Register::FS as u32);
		const _: () = assert!(Register::ES as u32 + 5 == Register::GS as u32);
		if number < 6 {
			// SAFETY: the created variants all exist, see asserts above
			unsafe { mem::transmute(Register::ES as RegisterUnderlyingType + number as RegisterUnderlyingType) }
		} else {
			Register::None
		}
	}

	#[inline]
	fn set_segment(&mut self, number: u8) {
		self.flags1 = (self.flags1 & !(CodeAsmOpStateFlags1::SEGMENT_MASK << CodeAsmOpStateFlags1::SEGMENT_SHIFT))
			| (number << CodeAsmOpStateFlags1::SEGMENT_SHIFT);
	}

	#[inline]
	pub fn set_es(&mut self) {
		self.set_segment(1);
	}

	#[inline]
	pub fn set_cs(&mut self) {
		self.set_segment(2);
	}

	#[inline]
	pub fn set_ss(&mut self) {
		self.set_segment(3);
	}

	#[inline]
	pub fn set_ds(&mut self) {
		self.set_segment(4);
	}

	#[inline]
	pub fn set_fs(&mut self) {
		self.set_segment(5);
	}

	#[inline]
	pub fn set_gs(&mut self) {
		self.set_segment(6);
	}

	#[must_use]
	#[inline]
	#[allow(trivial_numeric_casts)]
	pub fn op_mask(&self) -> Register {
		let number = (self.flags2 >> CodeAsmOpStateFlags2::OP_MASK_SHIFT) & CodeAsmOpStateFlags2::OP_MASK_MASK;
		const _: () = assert!(CodeAsmOpStateFlags2::OP_MASK_MASK == 7);
		if number == 0 {
			Register::None
		} else {
			const _: () = assert!(Register::K0 as u32 + 1 == Register::K1 as u32);
			const _: () = assert!(Register::K0 as u32 + 2 == Register::K2 as u32);
			const _: () = assert!(Register::K0 as u32 + 3 == Register::K3 as u32);
			const _: () = assert!(Register::K0 as u32 + 4 == Register::K4 as u32);
			const _: () = assert!(Register::K0 as u32 + 5 == Register::K5 as u32);
			const _: () = assert!(Register::K0 as u32 + 6 == Register::K6 as u32);
			const _: () = assert!(Register::K0 as u32 + 7 == Register::K7 as u32);
			// SAFETY: 1<=number<=7 (see first assert and if check) and all created variants are valid (K1-K7)
			unsafe { mem::transmute(Register::K0 as RegisterUnderlyingType + number as RegisterUnderlyingType) }
		}
	}

	#[inline]
	fn set_op_mask(&mut self, number: u8) {
		self.flags2 = (self.flags2 & !(CodeAsmOpStateFlags2::OP_MASK_MASK << CodeAsmOpStateFlags2::OP_MASK_SHIFT))
			| (number << CodeAsmOpStateFlags2::OP_MASK_SHIFT);
	}

	#[inline]
	pub fn set_k1(&mut self) {
		self.set_op_mask(1);
	}

	#[inline]
	pub fn set_k2(&mut self) {
		self.set_op_mask(2);
	}

	#[inline]
	pub fn set_k3(&mut self) {
		self.set_op_mask(3);
	}

	#[inline]
	pub fn set_k4(&mut self) {
		self.set_op_mask(4);
	}

	#[inline]
	pub fn set_k5(&mut self) {
		self.set_op_mask(5);
	}

	#[inline]
	pub fn set_k6(&mut self) {
		self.set_op_mask(6);
	}

	#[inline]
	pub fn set_k7(&mut self) {
		self.set_op_mask(7);
	}

	#[must_use]
	#[inline]
	#[allow(trivial_numeric_casts)]
	pub fn rounding_control(&self) -> RoundingControl {
		let number = (self.flags2 >> CodeAsmOpStateFlags2::ROUNDING_CONTROL_SHIFT) & CodeAsmOpStateFlags2::ROUNDING_CONTROL_MASK;
		// SAFETY: we only store valid variants
		unsafe { mem::transmute(number as RoundingControlUnderlyingType) }
	}

	#[inline]
	fn set_rounding_control(&mut self, rc: RoundingControl) {
		self.flags2 = (self.flags2 & !(CodeAsmOpStateFlags2::ROUNDING_CONTROL_MASK << CodeAsmOpStateFlags2::ROUNDING_CONTROL_SHIFT))
			| ((rc as u8) << CodeAsmOpStateFlags2::ROUNDING_CONTROL_SHIFT);
	}

	#[inline]
	pub fn rn_sae(&mut self) {
		self.set_rounding_control(RoundingControl::RoundToNearest);
	}

	#[inline]
	pub fn rd_sae(&mut self) {
		self.set_rounding_control(RoundingControl::RoundDown);
	}

	#[inline]
	pub fn ru_sae(&mut self) {
		self.set_rounding_control(RoundingControl::RoundUp);
	}

	#[inline]
	pub fn rz_sae(&mut self) {
		self.set_rounding_control(RoundingControl::RoundTowardZero);
	}

	#[must_use]
	#[inline]
	pub fn is_broadcast(&self) -> bool {
		(self.flags1 & CodeAsmOpStateFlags1::BROADCAST) != 0
	}

	#[must_use]
	#[inline]
	pub fn suppress_all_exceptions(&self) -> bool {
		(self.flags2 & CodeAsmOpStateFlags2::SUPPRESS_ALL_EXCEPTIONS) != 0
	}

	#[inline]
	pub fn set_suppress_all_exceptions(&mut self) {
		self.flags2 |= CodeAsmOpStateFlags2::SUPPRESS_ALL_EXCEPTIONS;
	}

	#[must_use]
	#[inline]
	pub fn zeroing_masking(&self) -> bool {
		(self.flags2 & CodeAsmOpStateFlags2::ZEROING_MASKING) != 0
	}

	#[inline]
	pub fn set_zeroing_masking(&mut self) {
		self.flags2 |= CodeAsmOpStateFlags2::ZEROING_MASKING;
	}
}
