// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+

#pragma once

namespace x86Emitter
{

	// =====================================================================================================
	//  xImpl_SIMD Types (template free!)
	// =====================================================================================================

	struct SIMDInstructionInfo {
		/// The prefix byte of a simd instruction.  These match up with their (E)VEX encodings.
		enum class Prefix : u32 {
			None = 0,
			P66  = 1,
			PF3  = 2,
			PF2  = 3,
		};
		/// The opcode map of a simd instruction.  These match up with their (E)VEX encodings.
		enum class Map : u32 {
			M0F   = 1,
			M0F38 = 2,
			M0F3A = 3,
		};
		/// Whether an operation operates on float (ss, ps), integer (b, w, d, q), or double (sd, pd) data.
		/// May be used to choose an appropriate mov instruction if one is needed.
		enum class Type : u32 {
			Float, Integer, Double
		};

		/// The main opcode
		u32 opcode : 8;
		/// Prefix byte
		Prefix prefix : 2;
		/// Opcode map
		Map map : 5;
		/// Information about the data this operation operates on.  Ignored for instructions where the SSE4 and AVX versions have the same number of arguments.
		Type type : 2;
		/// For instructions like pslld, the data that should go into the reg field in place of the first src
		u32 ext : 3;
		/// If true, the two inputs to the function can be swapped without changing its result.
		u32 is_commutative : 1;
		/// If true, the dst and src1 must be the same in AVX (e.g. mov instructions, pshufd)
		u32 is_mov : 1;
		/// If true, get `W` from dst register instead of `w_bit`
		u32 dst_w : 1;
		/// If true, get `W` from src register instead of `w_bit`
		u32 src_w : 1;
		/// If true, the instruction has the VEX W bit set
		u32 w_bit : 1;

		constexpr SIMDInstructionInfo(u8 opcode_, u8 ext_ = 0)
			: opcode(opcode_), prefix(Prefix::None), map(Map::M0F), type(Type::Float), ext(ext_)
			, is_commutative(false), is_mov(false), dst_w(false), src_w(false), w_bit(false)
		{
		}

		// For configuration using in a builder-style
		constexpr SIMDInstructionInfo p66() const { SIMDInstructionInfo copy = *this; copy.prefix = Prefix::P66; return copy; }
		constexpr SIMDInstructionInfo pf3() const { SIMDInstructionInfo copy = *this; copy.prefix = Prefix::PF3; return copy; }
		constexpr SIMDInstructionInfo pf2() const { SIMDInstructionInfo copy = *this; copy.prefix = Prefix::PF2; return copy; }
		constexpr SIMDInstructionInfo m0f38() const { SIMDInstructionInfo copy = *this; copy.map = Map::M0F38; return copy; }
		constexpr SIMDInstructionInfo m0f3a() const { SIMDInstructionInfo copy = *this; copy.map = Map::M0F3A; return copy; }
		constexpr SIMDInstructionInfo f() const { SIMDInstructionInfo copy = *this; copy.type = Type::Float;   return copy; }
		constexpr SIMDInstructionInfo i() const { SIMDInstructionInfo copy = *this; copy.type = Type::Integer; return copy; }
		constexpr SIMDInstructionInfo d() const { SIMDInstructionInfo copy = *this; copy.type = Type::Double;  return copy; }
		constexpr SIMDInstructionInfo w() const { SIMDInstructionInfo copy = *this; copy.w_bit = true; return copy; }
		constexpr SIMDInstructionInfo dstw() const { SIMDInstructionInfo copy = *this; copy.dst_w = true; return copy; }
		constexpr SIMDInstructionInfo srcw() const { SIMDInstructionInfo copy = *this; copy.src_w = true; return copy; }
		constexpr SIMDInstructionInfo commutative() const { SIMDInstructionInfo copy = *this; copy.is_commutative = true; return copy; }
		constexpr SIMDInstructionInfo mov() const { SIMDInstructionInfo copy = *this; copy.is_mov = true; return copy; }
	};

	// ------------------------------------------------------------------------
	// For implementing SSE/AVX logic operations that have two arguments in both SSE and AVX
	// like MOVAPS, CVTPS2DQ, etc
	//
	struct xImplSimd_2Arg
	{
		SIMDInstructionInfo info;

		constexpr xImplSimd_2Arg(SIMDInstructionInfo info_): info(info_.mov()) {}

		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src)  const;
		void operator()(const xRegisterSSE& dst, const xIndirectVoid& src) const;
	};

	// ------------------------------------------------------------------------
	// For implementing SSE/AVX logic operations that have two arguments in both SSE and AVX, plus an immediate
	// like PSHUFD
	//
	struct xImplSimd_2ArgImm
	{
		SIMDInstructionInfo info;

		constexpr xImplSimd_2ArgImm(SIMDInstructionInfo info_): info(info_.mov()) {}

		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src, u8 imm)  const;
		void operator()(const xRegisterSSE& dst, const xIndirectVoid& src, u8 imm) const;
	};

	// ------------------------------------------------------------------------
	// For implementing SSE/AVX logic operations that have three arguments AVX and two in SSE
	// like ANDPS, ANDPD, etc
	//
	struct xImplSimd_3Arg
	{
		SIMDInstructionInfo info;

		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src)  const { (*this)(dst, dst, src); }
		void operator()(const xRegisterSSE& dst, const xIndirectVoid& src) const { (*this)(dst, dst, src); }
		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src1, const xRegisterSSE& src2)  const;
		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src1, const xIndirectVoid& src2) const;
	};

	// ------------------------------------------------------------------------
	// For implementing SSE/AVX logic operations that have three arguments AVX and two in SSE
	// like SHUFPS, INSERTPS, etc
	//
	struct xImplSimd_3ArgImm
	{
		SIMDInstructionInfo info;

		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src,  u8 imm) const { (*this)(dst, dst, src, imm); }
		void operator()(const xRegisterSSE& dst, const xIndirectVoid& src, u8 imm) const { (*this)(dst, dst, src, imm); }
		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src1, const xRegisterSSE& src2,  u8 imm) const;
		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src1, const xIndirectVoid& src2, u8 imm) const;
	};

	// ------------------------------------------------------------------------
	// For implementing SSE/AVX logic operations that have three arguments AVX and two in SSE
	// like SHUFPS, INSERTPS, etc
	//
	struct xImplSimd_3ArgCmp
	{
		SIMDInstructionInfo info;

		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src,  SSE2_ComparisonType imm) const { (*this)(dst, dst, src, imm); }
		void operator()(const xRegisterSSE& dst, const xIndirectVoid& src, SSE2_ComparisonType imm) const { (*this)(dst, dst, src, imm); }
		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src1, const xRegisterSSE& src2,  SSE2_ComparisonType imm) const;
		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src1, const xIndirectVoid& src2, SSE2_ComparisonType imm) const;
	};

	// ------------------------------------------------------------------------
	// For implementing SSE/AVX logic operations that have four arguments AVX and two in SSE (with an implicit xmm0)
	// like PBLENDVB, BLENDVPS, etc
	//
	struct xImplSimd_4ArgBlend
	{
		SIMDInstructionInfo sse;
		SIMDInstructionInfo avx;

		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src)  const { (*this)(dst, dst, src, xmm0); }
		void operator()(const xRegisterSSE& dst, const xIndirectVoid& src) const { (*this)(dst, dst, src, xmm0); }
		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src1, const xRegisterSSE& src2,  const xRegisterSSE& src3) const;
		void operator()(const xRegisterSSE& dst, const xRegisterSSE& src1, const xIndirectVoid& src2, const xRegisterSSE& src3) const;
	};
} // end namespace x86Emitter
