#ifndef SoftWire_Operand_hpp
#define SoftWire_Operand_hpp

#include "Encoding.hpp"
#include "Error.hpp"

namespace SoftWire
{
	struct Specifier
	{
		enum Type
		{
			TYPE_UNKNOWN = 0,

			TYPE_NEAR,
			TYPE_SHORT = TYPE_NEAR,
		//	TYPE_FAR,
			
			TYPE_BYTE,
			TYPE_WORD,
			TYPE_DWORD,
		//	TYPE_TWORD,   // 80-bit long double not supported
			TYPE_QWORD,
			TYPE_MMWORD = TYPE_QWORD,
			TYPE_XMMWORD,
			TYPE_XWORD = TYPE_XMMWORD,
			TYPE_OWORD = TYPE_XMMWORD,

			TYPE_PTR
		};

		Type type;
		union
		{
			const char *reference;
			const char *notation;
		};

		static Specifier::Type scan(const char *string);

		static const Specifier specifierSet[];
	};

	struct OperandREG;

	struct Operand
	{
		enum Type
		{
			OPERAND_UNKNOWN	= 0,

			OPERAND_VOID	= 0x00000001,

			OPERAND_ONE		= 0x00000002,
			OPERAND_EXT8	= 0x00000004 | OPERAND_ONE,   // Sign extended
			OPERAND_REF		= 0x00000008,
			OPERAND_IMM8	= 0x00000010 | OPERAND_EXT8 | OPERAND_ONE,
			OPERAND_IMM16	= 0x00000020 | OPERAND_IMM8 | OPERAND_EXT8 | OPERAND_ONE,
			OPERAND_IMM32	= 0x00000040 | OPERAND_REF | OPERAND_IMM16 | OPERAND_IMM8 | OPERAND_EXT8 | OPERAND_ONE,
			OPERAND_IMM		= OPERAND_IMM32 | OPERAND_IMM16 | OPERAND_IMM8 | OPERAND_EXT8 | OPERAND_ONE,

			OPERAND_AL		= 0x00000080,
			OPERAND_CL		= 0x00000100,
			OPERAND_REG8	= OPERAND_CL | OPERAND_AL,

			OPERAND_AX		= 0x00000200,
			OPERAND_DX		= 0x00000400,
			OPERAND_CX		= 0x00000800,
			OPERAND_REG16	= OPERAND_CX | OPERAND_DX | OPERAND_AX,

			OPERAND_EAX		= 0x00001000,
			OPERAND_ECX		= 0x00002000,
			OPERAND_REG32	= OPERAND_ECX | OPERAND_EAX,

			OPERAND_CS		= 0,   // No need to touch these in 32-bit protected mode
			OPERAND_DS		= 0,
			OPERAND_ES		= 0,
			OPERAND_SS		= 0,
			OPERAND_FS		= 0,
			OPERAND_GS		= 0,
			OPERAND_SEGREG	= OPERAND_GS | OPERAND_FS | OPERAND_SS | OPERAND_ES | OPERAND_DS | OPERAND_CS,

			OPERAND_ST0		= 0x00004000,
			OPERAND_FPUREG	= 0x00008000 | OPERAND_ST0,
			
			OPERAND_CR		= 0,   // You won't need these in a JIT assembler
			OPERAND_DR		= 0,
			OPERAND_TR		= 0,

			OPERAND_MMREG	= 0x00010000,
			OPERAND_XMMREG	= 0x00020000,

			OPERAND_REG		= OPERAND_XMMREG | OPERAND_MMREG | OPERAND_TR | OPERAND_DR | OPERAND_CR | OPERAND_FPUREG | OPERAND_SEGREG | OPERAND_REG32 | OPERAND_REG16 | OPERAND_REG8,

			OPERAND_MEM8	= 0x00040000,
			OPERAND_MEM16	= 0x00080000,
			OPERAND_MEM32	= 0x00100000,
			OPERAND_MEM64	= 0x00200000,
			OPERAND_MEM80	= 0,   // Extended double not supported by NT
			OPERAND_MEM128	= 0x00400000,
			OPERAND_MEM		= OPERAND_MEM128 | OPERAND_MEM80 | OPERAND_MEM64 | OPERAND_MEM32 | OPERAND_MEM16 | OPERAND_MEM8,
		
			OPERAND_XMM32	= OPERAND_MEM32 | OPERAND_XMMREG,
			OPERAND_XMM64	= OPERAND_MEM64 | OPERAND_XMMREG,

			OPERAND_R_M8	= OPERAND_MEM8 | OPERAND_REG8,
			OPERAND_R_M16	= OPERAND_MEM16 | OPERAND_REG16,
			OPERAND_R_M32	= OPERAND_MEM32 | OPERAND_REG32,
			OPERAND_R_M64	= OPERAND_MEM64 | OPERAND_MMREG,
			OPERAND_R_M128	= OPERAND_MEM128 | OPERAND_XMMREG,
			OPERAND_R_M		= OPERAND_MEM | OPERAND_REG,

			OPERAND_MOFFS8	= 0,   // Not supported, equivalent available
			OPERAND_MOFFS16	= 0,   // Not supported, equivalent available
			OPERAND_MOFFS32	= 0,   // Not supported, equivalent available

			OPERAND_STR		= 0x00800000 | OPERAND_REF
		};

		Operand(Type type = OPERAND_VOID)
		{
			this->type = type;
		}

		Type type;
		union
		{
			const char *reference;
			const char *notation;
		};

		union
		{
			int value;     // For immediates
			int reg;       // For registers
			int baseReg;   // For memory references;
		};

		int indexReg;
		int scale;
		int displacement;

		bool operator==(Operand &op);
		bool operator!=(Operand &op);

		static bool isSubtypeOf(Type type, Type baseType);
		bool isSubtypeOf(Type baseType) const;

		const char *string() const;

		static bool isVoid(Type type);
		static bool isImm(Type type);
		static bool isReg(Type type);
		static bool isMem(Type type);
		static bool isR_M(Type type);
		static bool isStr(Type type);

		static bool isVoid(const Operand &operand);
		static bool isImm(const Operand &operand);
		static bool isReg(const Operand &operand);
		static bool isMem(const Operand &operand);
		static bool isR_M(const Operand &operand);
		static bool isStr(const Operand &operand);

		static OperandREG scanReg(const char *string);

		const char *regName() const;
		const char *indexName() const;

		static Operand::Type scanSyntax(const char *string);

		struct Register
		{
			Type type;
			const char *notation;
			int reg;   // Index
		};

		static const Register registerSet[];
		static const Register syntaxSet[];
	};

	struct OperandVOID : virtual Operand
	{
		OperandVOID()
		{
			type = OPERAND_VOID;
		}
	};

	struct OperandIMM : virtual Operand
	{
		OperandIMM(int imm = 0)
		{
			type = OPERAND_IMM;
			value = imm;
			reference = 0;
		}
	};

	struct OperandMEM : virtual Operand
	{
		OperandMEM()
		{
			type = OPERAND_MEM;
			baseReg = Encoding::REG_UNKNOWN;
			indexReg = Encoding::REG_UNKNOWN;
			scale = 0;
			displacement = 0;
			reference = 0;
		}
	};

	struct OperandREG : virtual Operand
	{
		OperandREG()
		{
			type = OPERAND_VOID;
		}

		OperandREG(const Operand &reg)
		{
			type = reg.type;
			this->reg = reg.reg;
			reference = reg.reference;
		}
	};

	struct OperandSTR : virtual Operand
	{
		OperandSTR(const char *string)
		{
			type = OPERAND_STR;
			reference = string;
		}
	};
}

#endif   // SoftWire_Operand_hpp
