#ifndef __I386_H_
#define __I386_H_

#define HAS_I486 		0
#define HAS_PENTIUM		0
#define HAS_MEDIAGX		0

#define I386OP(XX)		i386_##XX
#define I486OP(XX)		i486_##XX
#define PENTIUMOP(XX)	pentium_##XX
#define MMXOP(XX)		mmx_##XX

#define INPUT_LINE_A20		1

#ifdef MAME_DEBUG
extern int i386_dasm_one(char *buffer, UINT32 pc, UINT8 *oprom, int addr_size, int op_size);
#endif

typedef enum { ES, CS, SS, DS, FS, GS } SREGS;

#ifdef LSB_FIRST
typedef enum {
	AL = 0,
	AH = 1,
	CL = 4,
	CH = 5,
	DL = 8,
	DH = 9,
	BL = 12,
	BH = 13
} BREGS;
#else
typedef enum {
	AL = 3,
	AH = 2,
	CL = 7,
	CH = 6,
	DL = 11,
	DH = 10,
	BL = 15,
	BH = 14
} BREGS;
#endif

#ifdef LSB_FIRST
typedef enum {
	AX = 0,
	CX = 2,
	DX = 4,
	BX = 6,
	SP = 8,
	BP = 10,
	SI = 12,
	DI = 14
} WREGS;
#else
typedef enum {
	AX = 1,
	CX = 3,
	DX = 5,
	BX = 7,
	SP = 9,
	BP = 11,
	SI = 13,
	DI = 15
} WREGS;
#endif

typedef enum { EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI } DREGS;

enum
{
	I386_PC = 0,
	I386_EAX,
	I386_ECX,
	I386_EDX,
	I386_EBX,
	I386_EBP,
	I386_ESP,
	I386_ESI,
	I386_EDI,
	I386_CS,
	I386_SS,
	I386_DS,
	I386_ES,
	I386_FS,
	I386_GS,
	I386_EIP,
	I386_EFLAGS,
	I386_CR0,
	I386_CR1,
	I386_CR2,
	I386_CR3,
	I386_DR0,
	I386_DR1,
	I386_DR2,
	I386_DR3,
	I386_DR4,
	I386_DR5,
	I386_DR6,
	I386_DR7,
	I386_TR6,
	I386_TR7,
	X87_CTRL,
	X87_STATUS,
	X87_ST0,
	X87_ST1,
	X87_ST2,
	X87_ST3,
	X87_ST4,
	X87_ST5,
	X87_ST6,
	X87_ST7
};

typedef struct {
	UINT16 selector;
	UINT32 base;
	UINT32 limit;
	int d;		// Operand size
} I386_SREG;

typedef struct {
	UINT32 base;
	UINT16 limit;
} I386_SYS_TABLE;

typedef struct {
	UINT16 segment;
	UINT32 base;
	UINT32 limit;
} I386_SEG_DESC;

typedef union {
	UINT32 d[8];
	UINT16 w[16];
	UINT8 b[32];
} I386_GPR;

typedef union {
	UINT64 i;
	double f;
} X87_REG;

typedef struct {
	I386_GPR reg;
	I386_SREG sreg[6];
	UINT32 eip;
	UINT32 pc;
	UINT32 prev_eip;
	UINT32 eflags;
	UINT8 CF;
	UINT8 DF;
	UINT8 SF;
	UINT8 OF;
	UINT8 ZF;
	UINT8 PF;
	UINT8 AF;
	UINT8 IF;
	UINT8 TF;

	UINT8 performed_intersegment_jump;

	UINT32 cr[4];		// Control registers
	UINT32 dr[8];		// Debug registers
	UINT32 tr[8];		// Test registers

	I386_SYS_TABLE gdtr;	// Global Descriptor Table Register
	I386_SYS_TABLE idtr;	// Interrupt Descriptor Table Register
	I386_SEG_DESC task;		// Task register
	I386_SEG_DESC ldtr;		// Local Descriptor Table Register

	int halted;

	int operand_size;
	int address_size;

	int segment_prefix;
	int segment_override;

	int cycles;
	int base_cycles;
	UINT8 opcode;

	int irq_line;
	int irq_hold;
	UINT32 a20_mask;

	int cpuid_max_input_value_eax;
	UINT32 cpuid_id0, cpuid_id1, cpuid_id2;
	UINT32 cpu_version;
	UINT32 feature_flags;
	UINT64 tsc;

	// FPU
	X87_REG fpu_reg[8];
	UINT16 fpu_control_word;
	UINT16 fpu_status_word;
	UINT16 fpu_tag_word;
	UINT64 fpu_data_ptr;
	UINT64 fpu_inst_ptr;
	UINT16 fpu_opcode;
	int fpu_top;



	int (*irq_callback)(int);

	void (*opcode_table1_16[256])(void);
	void (*opcode_table1_32[256])(void);
	void (*opcode_table2_16[256])(void);
	void (*opcode_table2_32[256])(void);

	UINT8 *cycle_table_pm;
	UINT8 *cycle_table_rm;
} I386_REGS;


static I386_REGS I;


/* Forward declarations */
static void I386OP(decode_opcode)(void);
static void I386OP(decode_two_byte)(void);


extern int parity_table[256];

#define PROTECTED_MODE		(I.cr[0] & 0x1)
#define STACK_32BIT			(I.sreg[SS].d)
#define V8086_MODE			(I.eflags & 0x00020000)

#define SetOF_Add32(r,s,d)	(I.OF = (((r) ^ (s)) & ((r) ^ (d)) & 0x80000000) ? 1: 0)
#define SetOF_Add16(r,s,d)	(I.OF = (((r) ^ (s)) & ((r) ^ (d)) & 0x8000) ? 1 : 0)
#define SetOF_Add8(r,s,d)	(I.OF = (((r) ^ (s)) & ((r) ^ (d)) & 0x80) ? 1 : 0)

#define SetOF_Sub32(r,s,d)	(I.OF = (((d) ^ (s)) & ((d) ^ (r)) & 0x80000000) ? 1 : 0)
#define SetOF_Sub16(r,s,d)	(I.OF = (((d) ^ (s)) & ((d) ^ (r)) & 0x8000) ? 1 : 0)
#define SetOF_Sub8(r,s,d)	(I.OF = (((d) ^ (s)) & ((d) ^ (r)) & 0x80) ? 1 : 0)

#define SetCF8(x)			{I.CF = ((x) & 0x100) ? 1 : 0; }
#define SetCF16(x)			{I.CF = ((x) & 0x10000) ? 1 : 0; }
#define SetCF32(x)			{I.CF = ((x) & (((UINT64)1) << 32)) ? 1 : 0; }

#define SetSF(x)			(I.SF = (x))
#define SetZF(x)			(I.ZF = (x))
#define SetAF(x,y,z)		(I.AF = (((x) ^ ((y) ^ (z))) & 0x10) ? 1 : 0)
#define SetPF(x)			(I.PF = parity_table[(x) & 0xFF])

#define SetSZPF8(x)			{I.ZF = ((UINT8)(x)==0);  I.SF = ((x)&0x80) ? 1 : 0; I.PF = parity_table[x & 0xFF]; }
#define SetSZPF16(x)		{I.ZF = ((UINT16)(x)==0);  I.SF = ((x)&0x8000) ? 1 : 0; I.PF = parity_table[x & 0xFF]; }
#define SetSZPF32(x)		{I.ZF = ((UINT32)(x)==0);  I.SF = ((x)&0x80000000) ? 1 : 0; I.PF = parity_table[x & 0xFF]; }

/***********************************************************************************/

typedef struct {
	struct {
		int b;
		int w;
		int d;
	} reg;
	struct {
		int b;
		int w;
		int d;
	} rm;
} MODRM_TABLE;

extern MODRM_TABLE MODRM_table[256];

#define REG8(x)			(I.reg.b[x])
#define REG16(x)		(I.reg.w[x])
#define REG32(x)		(I.reg.d[x])

#define LOAD_REG8(x)	(REG8(MODRM_table[x].reg.b))
#define LOAD_REG16(x)	(REG16(MODRM_table[x].reg.w))
#define LOAD_REG32(x)	(REG32(MODRM_table[x].reg.d))
#define LOAD_RM8(x)		(REG8(MODRM_table[x].rm.b))
#define LOAD_RM16(x)	(REG16(MODRM_table[x].rm.w))
#define LOAD_RM32(x)	(REG32(MODRM_table[x].rm.d))

#define STORE_REG8(x, value)	(REG8(MODRM_table[x].reg.b) = value)
#define STORE_REG16(x, value)	(REG16(MODRM_table[x].reg.w) = value)
#define STORE_REG32(x, value)	(REG32(MODRM_table[x].reg.d) = value)
#define STORE_RM8(x, value)		(REG8(MODRM_table[x].rm.b) = value)
#define STORE_RM16(x, value)	(REG16(MODRM_table[x].rm.w) = value)
#define STORE_RM32(x, value)	(REG32(MODRM_table[x].rm.d) = value)

/***********************************************************************************/

INLINE UINT32 i386_translate(int segment, UINT32 ip)
{
	// TODO: segment limit
	return I.sreg[segment].base + ip;
}

INLINE int translate_address(UINT32 *address)
{
	UINT32 a = *address;
	UINT32 pdbr = I.cr[3] & 0xfffff000;
	UINT32 directory = (a >> 22) & 0x3ff;
	UINT32 table = (a >> 12) & 0x3ff;
	UINT32 offset = a & 0xfff;

	// TODO: 4MB pages
	UINT32 page_dir = program_read_dword_32le(pdbr + directory * 4);
	UINT32 page_entry = program_read_dword_32le((page_dir & 0xfffff000) + (table * 4));

	*address = (page_entry & 0xfffff000) | offset;
	return 1;
}

INLINE void CHANGE_PC(UINT32 pc)
{
	UINT32 address;
	I.pc = i386_translate( CS, pc );

	address = I.pc;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	change_pc(address & I.a20_mask);
}

INLINE void NEAR_BRANCH(INT32 offs)
{
	UINT32 address;
	/* TODO: limit */
	I.eip += offs;
	I.pc += offs;

	address = I.pc;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	change_pc(address & I.a20_mask);
}

INLINE UINT8 FETCH(void)
{
	UINT8 value;
	UINT32 address = I.pc;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	value = cpu_readop(address & I.a20_mask);
	I.eip++;
	I.pc++;
	return value;
}
INLINE UINT16 FETCH16(void)
{
	UINT16 value;
	UINT32 address = I.pc;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	if( address & 0x1 ) {		/* Unaligned read */
		address &= I.a20_mask;
		value = (cpu_readop(address+0) << 0) |
				(cpu_readop(address+1) << 8);
	} else {
		address &= I.a20_mask;
		value = cpu_readop16(address);
	}
	I.eip += 2;
	I.pc += 2;
	return value;
}
INLINE UINT32 FETCH32(void)
{
	UINT32 value;
	UINT32 address = I.pc;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	if( I.pc & 0x3 ) {		/* Unaligned read */
		address &= I.a20_mask;
		value = (cpu_readop(address+0) << 0) |
				(cpu_readop(address+1) << 8) |
				(cpu_readop(address+2) << 16) |
				(cpu_readop(address+3) << 24);
	} else {
		address &= I.a20_mask;
		value = cpu_readop32(address);
	}
	I.eip += 4;
	I.pc += 4;
	return value;
}

INLINE UINT8 READ8(UINT32 ea)
{
	UINT32 address = ea;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	address &= I.a20_mask;
	return program_read_byte_32le(address);
}
INLINE UINT16 READ16(UINT32 ea)
{
	UINT16 value;
	UINT32 address = ea;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	address &= I.a20_mask;
	if( ea & 0x1 ) {		/* Unaligned read */
		value = (program_read_byte_32le( address+0 ) << 0) |
				(program_read_byte_32le( address+1 ) << 8);
	} else {
		value = program_read_word_32le( address );
	}
	return value;
}
INLINE UINT32 READ32(UINT32 ea)
{
	UINT32 value;
	UINT32 address = ea;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	address &= I.a20_mask;
	if( ea & 0x3 ) {		/* Unaligned read */
		value = (program_read_byte_32le( address+0 ) << 0) |
				(program_read_byte_32le( address+1 ) << 8) |
				(program_read_byte_32le( address+2 ) << 16) |
				(program_read_byte_32le( address+3 ) << 24);
	} else {
		value = program_read_dword_32le( address );
	}
	return value;
}

INLINE void WRITE8(UINT32 ea, UINT8 value)
{
	UINT32 address = ea;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	address &= I.a20_mask;
	program_write_byte_32le(address, value);
}
INLINE void WRITE16(UINT32 ea, UINT16 value)
{
	UINT32 address = ea;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	address &= I.a20_mask;
	if( ea & 0x1 ) {		/* Unaligned write */
		program_write_byte_32le( address+0, value & 0xff );
		program_write_byte_32le( address+1, (value >> 8) & 0xff );
	} else {
		program_write_word_32le(address, value);
	}
}
INLINE void WRITE32(UINT32 ea, UINT32 value)
{
	UINT32 address = ea;

	if (I.cr[0] & 0x80000000)		// page translation enabled
	{
		translate_address(&address);
	}

	ea &= I.a20_mask;
	if( ea & 0x3 ) {		/* Unaligned write */
		program_write_byte_32le( address+0, value & 0xff );
		program_write_byte_32le( address+1, (value >> 8) & 0xff );
		program_write_byte_32le( address+2, (value >> 16) & 0xff );
		program_write_byte_32le( address+3, (value >> 24) & 0xff );
	} else {
		program_write_dword_32le(address, value);
	}
}

/***********************************************************************************/

INLINE UINT8 OR8(UINT8 dst, UINT8 src)
{
	UINT8 res = dst | src;
	I.CF = I.OF = 0;
	SetSZPF8(res);
	return res;
}
INLINE UINT16 OR16(UINT16 dst, UINT16 src)
{
	UINT16 res = dst | src;
	I.CF = I.OF = 0;
	SetSZPF16(res);
	return res;
}
INLINE UINT32 OR32(UINT32 dst, UINT32 src)
{
	UINT32 res = dst | src;
	I.CF = I.OF = 0;
	SetSZPF32(res);
	return res;
}

INLINE UINT8 AND8(UINT8 dst, UINT8 src)
{
	UINT8 res = dst & src;
	I.CF = I.OF = 0;
	SetSZPF8(res);
	return res;
}
INLINE UINT16 AND16(UINT16 dst, UINT16 src)
{
	UINT16 res = dst & src;
	I.CF = I.OF = 0;
	SetSZPF16(res);
	return res;
}
INLINE UINT32 AND32(UINT32 dst, UINT32 src)
{
	UINT32 res = dst & src;
	I.CF = I.OF = 0;
	SetSZPF32(res);
	return res;
}

INLINE UINT8 XOR8(UINT8 dst, UINT8 src)
{
	UINT8 res = dst ^ src;
	I.CF = I.OF = 0;
	SetSZPF8(res);
	return res;
}
INLINE UINT16 XOR16(UINT16 dst, UINT16 src)
{
	UINT16 res = dst ^ src;
	I.CF = I.OF = 0;
	SetSZPF16(res);
	return res;
}
INLINE UINT32 XOR32(UINT32 dst, UINT32 src)
{
	UINT32 res = dst ^ src;
	I.CF = I.OF = 0;
	SetSZPF32(res);
	return res;
}

INLINE UINT8 SUB8(UINT8 dst, UINT8 src)
{
	UINT16 res = (UINT16)dst - (UINT16)src;
	SetCF8(res);
	SetOF_Sub8(res,src,dst);
	SetAF(res,src,dst);
	SetSZPF8(res);
	return (UINT8)res;
}
INLINE UINT16 SUB16(UINT16 dst, UINT16 src)
{
	UINT32 res = (UINT32)dst - (UINT32)src;
	SetCF16(res);
	SetOF_Sub16(res,src,dst);
	SetAF(res,src,dst);
	SetSZPF16(res);
	return (UINT16)res;
}
INLINE UINT32 SUB32(UINT32 dst, UINT32 src)
{
	UINT64 res = (UINT64)dst - (UINT64)src;
	SetCF32(res);
	SetOF_Sub32(res,src,dst);
	SetAF(res,src,dst);
	SetSZPF32(res);
	return (UINT32)res;
}

INLINE UINT8 ADD8(UINT8 dst, UINT8 src)
{
	UINT16 res = (UINT16)dst + (UINT16)src;
	SetCF8(res);
	SetOF_Add8(res,src,dst);
	SetAF(res,src,dst);
	SetSZPF8(res);
	return (UINT8)res;
}
INLINE UINT16 ADD16(UINT16 dst, UINT16 src)
{
	UINT32 res = (UINT32)dst + (UINT32)src;
	SetCF16(res);
	SetOF_Add16(res,src,dst);
	SetAF(res,src,dst);
	SetSZPF16(res);
	return (UINT16)res;
}
INLINE UINT32 ADD32(UINT32 dst, UINT32 src)
{
	UINT64 res = (UINT64)dst + (UINT64)src;
	SetCF32(res);
	SetOF_Add32(res,src,dst);
	SetAF(res,src,dst);
	SetSZPF32(res);
	return (UINT32)res;
}

INLINE UINT8 INC8(UINT8 dst)
{
	UINT16 res = (UINT16)dst + 1;
	SetOF_Add8(res,1,dst);
	SetAF(res,1,dst);
	SetSZPF8(res);
	return (UINT8)res;
}
INLINE UINT16 INC16(UINT16 dst)
{
	UINT32 res = (UINT32)dst + 1;
	SetOF_Add16(res,1,dst);
	SetAF(res,1,dst);
	SetSZPF16(res);
	return (UINT16)res;
}
INLINE UINT32 INC32(UINT32 dst)
{
	UINT64 res = (UINT64)dst + 1;
	SetOF_Add32(res,1,dst);
	SetAF(res,1,dst);
	SetSZPF32(res);
	return (UINT32)res;
}

INLINE UINT8 DEC8(UINT8 dst)
{
	UINT16 res = (UINT16)dst - 1;
	SetOF_Sub8(res,1,dst);
	SetAF(res,1,dst);
	SetSZPF8(res);
	return (UINT8)res;
}
INLINE UINT16 DEC16(UINT16 dst)
{
	UINT32 res = (UINT32)dst - 1;
	SetOF_Sub16(res,1,dst);
	SetAF(res,1,dst);
	SetSZPF16(res);
	return (UINT16)res;
}
INLINE UINT32 DEC32(UINT32 dst)
{
	UINT64 res = (UINT64)dst - 1;
	SetOF_Sub32(res,1,dst);
	SetAF(res,1,dst);
	SetSZPF32(res);
	return (UINT32)res;
}



INLINE void PUSH16(UINT16 value)
{
	UINT32 ea;
	if( STACK_32BIT ) {
		REG32(ESP) -= 2;
		ea = i386_translate( SS, REG32(ESP) );
		WRITE16( ea, value );
	} else {
		REG16(SP) -= 2;
		ea = i386_translate( SS, REG16(SP) );
		WRITE16( ea, value );
	}
}
INLINE void PUSH32(UINT32 value)
{
	UINT32 ea;
	if( STACK_32BIT ) {
		REG32(ESP) -= 4;
		ea = i386_translate( SS, REG32(ESP) );
		WRITE32( ea, value );
	} else {
		REG16(SP) -= 4;
		ea = i386_translate( SS, REG16(SP) );
		WRITE32( ea, value );
	}
}
INLINE void PUSH8(UINT8 value)
{
	if( I.operand_size ) {
		PUSH32((INT32)(INT8)value);
	} else {
		PUSH16((INT16)(INT8)value);
	}
}

INLINE UINT8 POP8(void)
{
	UINT8 value;
	UINT32 ea;
	if( STACK_32BIT ) {
		ea = i386_translate( SS, REG32(ESP) );
		value = READ8( ea );
		REG32(ESP) += 1;
	} else {
		ea = i386_translate( SS, REG16(SP) );
		value = READ8( ea );
		REG16(SP) += 1;
	}
	return value;
}
INLINE UINT16 POP16(void)
{
	UINT16 value;
	UINT32 ea;
	if( STACK_32BIT ) {
		ea = i386_translate( SS, REG32(ESP) );
		value = READ16( ea );
		REG32(ESP) += 2;
	} else {
		ea = i386_translate( SS, REG16(SP) );
		value = READ16( ea );
		REG16(SP) += 2;
	}
	return value;
}
INLINE UINT32 POP32(void)
{
	UINT32 value;
	UINT32 ea;
	if( STACK_32BIT ) {
		ea = i386_translate( SS, REG32(ESP) );
		value = READ32( ea );
		REG32(ESP) += 4;
	} else {
		ea = i386_translate( SS, REG16(SP) );
		value = READ32( ea );
		REG16(SP) += 4;
	}
	return value;
}

INLINE void BUMP_SI(int adjustment)
{
	if ( I.address_size )
		REG32(ESI) += ((I.DF) ? -adjustment : +adjustment);
	else
		REG16(SI) += ((I.DF) ? -adjustment : +adjustment);
}

INLINE void BUMP_DI(int adjustment)
{
	if ( I.address_size )
		REG32(EDI) += ((I.DF) ? -adjustment : +adjustment);
	else
		REG16(DI) += ((I.DF) ? -adjustment : +adjustment);
}



/***********************************************************************************/

#define READPORT8(port)		       	(io_read_byte_32le(port))
#define READPORT16(port)	       	(io_read_word_32le(port))
#define READPORT32(port)	       	(io_read_dword_32le(port))
#define WRITEPORT8(port, value)		(io_write_byte_32le(port, value))
#define WRITEPORT16(port, value)	(io_write_word_32le(port, value))
#define WRITEPORT32(port, value)	(io_write_dword_32le(port, value))

#endif
