/*****************************************************************************\
  Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
                This file is licensed under the Snes9x License.
   For further information, consult the LICENSE file in the root directory.
\*****************************************************************************/

#pragma once

template<typename Fa, typename Ff> inline void rOP8(Fa addr, Ff func)
{
	uint8_t val = OpenBus = S9xGetByte(addr(READ));
	func(val);
}

template<typename Fa, typename Ff> inline void rOP16(Fa addr, Ff func, s9xwrap_t wrap = WRAP_NONE)
{
	uint16_t val = S9xGetWord(addr(READ), wrap);
	OpenBus = static_cast<uint8_t>(val >> 8);
	func(val);
}

template<typename Fa, typename Ff8, typename Ff16> inline void rOPC(bool cond, Fa addr, Ff8 func8, Ff16 func16, s9xwrap_t wrap = WRAP_NONE)
{
	if (cond)
		rOP8(addr, func8);
	else
		rOP16(addr, func16, wrap);
}

template<typename Fa, typename Ff8, typename Ff16> inline void rOPM(Fa addr, Ff8 func8, Ff16 func16, s9xwrap_t wrap = WRAP_NONE)
{
	rOPC(CheckMemory(), addr, func8, func16, wrap);
}

template<typename Fa, typename Ff8, typename Ff16> inline void rOPX(Fa addr, Ff8 func8, Ff16 func16, s9xwrap_t wrap = WRAP_NONE)
{
	rOPC(CheckIndex(), addr, func8, func16, wrap);
}

template<typename Fa, typename Ff> inline void wOP8(Fa addr, Ff func)
{
	func(addr(WRITE));
}

template<typename Fa, typename Ff> inline void wOP16(Fa addr, Ff func, s9xwrap_t wrap = WRAP_NONE)
{
	func(addr(WRITE), wrap);
}

template<typename Fa, typename Ff8, typename Ff16> inline void wOPC(bool cond, Fa addr, Ff8 func8, Ff16 func16, s9xwrap_t wrap = WRAP_NONE)
{
	if (cond)
		wOP8(addr, func8);
	else
		wOP16(addr, func16, wrap);
}

template<typename Fa, typename Ff8, typename Ff16> inline void wOPM(Fa addr, Ff8 func8, Ff16 func16, s9xwrap_t wrap = WRAP_NONE)
{
	wOPC(CheckMemory(), addr, func8, func16, wrap);
}

template<typename Fa, typename Ff8, typename Ff16> inline void wOPX(Fa addr, Ff8 func8, Ff16 func16, s9xwrap_t wrap = WRAP_NONE)
{
	wOPC(CheckIndex(), addr, func8, func16, wrap);
}

template<typename Fa, typename Ff> inline void mOP8(Fa addr, Ff func)
{
	func(addr(MODIFY));
}

template<typename Fa, typename Ff> inline void mOP16(Fa addr, Ff func, s9xwrap_t wrap = WRAP_NONE)
{
	func(addr(MODIFY), wrap);
}

template<typename Fa, typename Ff8, typename Ff16> inline void mOPC(bool cond, Fa addr, Ff8 func8, Ff16 func16, s9xwrap_t wrap = WRAP_NONE)
{
	if (cond)
		mOP8(addr, func8);
	else
		mOP16(addr, func16, wrap);
}

template<typename Fa, typename Ff8, typename Ff16> inline void mOPM(Fa addr, Ff8 func8, Ff16 func16, s9xwrap_t wrap = WRAP_NONE)
{
	mOPC(CheckMemory(), addr, func8, func16, wrap);
}

template<typename F> inline void bOP(F rel, bool cond, bool e)
{
	pair newPC;
	newPC.W = rel(JUMP);
	if (cond)
	{
		AddCycles(ONE_CYCLE);
		if (e && Registers.PC.B.xPCh != newPC.B.h)
			AddCycles(ONE_CYCLE);
		if ((Registers.PC.W.xPC & ~MEMMAP_MASK) != (newPC.W & ~MEMMAP_MASK))
			S9xSetPCBase(ICPU.ShiftedPB + newPC.W);
		else
			Registers.PC.W.xPC = newPC.W;
	}
}

inline void SetZN(uint16_t Work16)
{
	ICPU._Zero = !!Work16;
	ICPU._Negative = static_cast<uint8_t>(Work16 >> 8);
}

inline void SetZN(uint8_t Work8)
{
	ICPU._Zero = Work8;
	ICPU._Negative = Work8;
}

inline void ADC16(uint16_t Work16)
{
	if (CheckDecimal())
	{
		uint32_t carry = CheckCarry();

		uint32_t result = (Registers.A.W & 0x000F) + (Work16 & 0x000F) + carry;
		if (result > 0x0009)
			result += 0x0006;
		carry = result > 0x000F;

		result = (Registers.A.W & 0x00F0) + (Work16 & 0x00F0) + (result & 0x000F) + carry * 0x10;
		if (result > 0x009F)
			result += 0x0060;
		carry = result > 0x00FF;

		result = (Registers.A.W & 0x0F00) + (Work16 & 0x0F00) + (result & 0x00FF) + carry * 0x100;
		if (result > 0x09FF)
			result += 0x0600;
		carry = result > 0x0FFF;

		result = (Registers.A.W & 0xF000) + (Work16 & 0xF000) + (result & 0x0FFF) + carry * 0x1000;

		if ((Registers.A.W & 0x8000) == (Work16 & 0x8000) && (Registers.A.W & 0x8000) != (result & 0x8000))
			SetOverflow();
		else
			ClearOverflow();

		if (result > 0x9FFF)
			result += 0x6000;

		if (result > 0xFFFF)
			SetCarry();
		else
			ClearCarry();

		Registers.A.W = result & 0xFFFF;
		SetZN(Registers.A.W);
	}
	else
	{
		uint32_t Ans32 = Registers.A.W + Work16 + CheckCarry();

		ICPU._Carry = Ans32 >= 0x10000;

		if (~(Registers.A.W ^ Work16) & (Work16 ^ static_cast<uint16_t>(Ans32)) & 0x8000)
			SetOverflow();
		else
			ClearOverflow();

		Registers.A.W = static_cast<uint16_t>(Ans32);
		SetZN(Registers.A.W);
	}
}

inline void ADC8(uint8_t Work8)
{
	if (CheckDecimal())
	{
		uint32_t carry = CheckCarry();

		uint32_t result = (Registers.A.B.l & 0x0F) + (Work8 & 0x0F) + carry;
		if (result > 0x09)
			result += 0x06;
		carry = result > 0x0F;

		result = (Registers.A.B.l & 0xF0) + (Work8 & 0xF0) + (result & 0x0F) + (carry * 0x10);

		if ((Registers.A.B.l & 0x80) == (Work8 & 0x80) && (Registers.A.B.l & 0x80) != (result & 0x80))
			SetOverflow();
		else
			ClearOverflow();

		if (result > 0x9F)
			result += 0x60;

		if (result > 0xFF)
			SetCarry();
		else
			ClearCarry();

		Registers.A.B.l = result & 0xFF;
		SetZN(Registers.A.B.l);
	}
	else
	{
		uint16_t Ans16 = Registers.A.B.l + Work8 + CheckCarry();

		ICPU._Carry = Ans16 >= 0x100;

		if (~(Registers.A.B.l ^ Work8) & (Work8 ^ static_cast<uint8_t>(Ans16)) & 0x80)
			SetOverflow();
		else
			ClearOverflow();

		Registers.A.B.l = static_cast<uint8_t>(Ans16);
		SetZN(Registers.A.B.l);
	}
}

inline void AND16(uint16_t Work16)
{
	Registers.A.W &= Work16;
	SetZN(Registers.A.W);
}

inline void AND8(uint8_t Work8)
{
	Registers.A.B.l &= Work8;
	SetZN(Registers.A.B.l);
}

inline void ASL16(uint32_t OpAddress, s9xwrap_t w)
{
	uint16_t Work16 = S9xGetWord(OpAddress, w);
	ICPU._Carry = !!(Work16 & 0x8000);
	Work16 <<= 1;
	AddCycles(ONE_CYCLE);
	S9xSetWord(Work16, OpAddress, w, WRITE_10);
	OpenBus = Work16 & 0xff;
	SetZN(Work16);
}

inline void ASL8(uint32_t OpAddress)
{
	uint8_t Work8 = S9xGetByte(OpAddress);
	ICPU._Carry = !!(Work8 & 0x80);
	Work8 <<= 1;
	AddCycles(ONE_CYCLE);
	S9xSetByte(Work8, OpAddress);
	OpenBus = Work8;
	SetZN(Work8);
}

inline void BIT16(uint16_t Work16)
{
	ICPU._Overflow = !!(Work16 & 0x4000);
	ICPU._Negative = static_cast<uint8_t>(Work16 >> 8);
	ICPU._Zero = !!(Work16 & Registers.A.W);
}

inline void BIT8(uint8_t Work8)
{
	ICPU._Overflow = !!(Work8 & 0x40);
	ICPU._Negative = Work8;
	ICPU._Zero = !!(Work8 & Registers.A.B.l);
}

inline void CMP16(uint16_t val)
{
	int32_t Int32 = static_cast<int32_t>(Registers.A.W) - static_cast<int32_t>(val);
	ICPU._Carry = Int32 >= 0;
	SetZN(static_cast<uint16_t>(Int32));
}

inline void CMP8(uint8_t val)
{
	int16_t Int16 = static_cast<int16_t>(Registers.A.B.l) - static_cast<int16_t>(val);
	ICPU._Carry = Int16 >= 0;
	SetZN(static_cast<uint8_t>(Int16));
}

inline void CPX16(uint16_t val)
{
	int32_t Int32 = static_cast<int32_t>(Registers.X.W) - static_cast<int32_t>(val);
	ICPU._Carry = Int32 >= 0;
	SetZN(static_cast<uint16_t>(Int32));
}

inline void CPX8(uint8_t val)
{
	int16_t Int16 = static_cast<int16_t>(Registers.X.B.l) - static_cast<int16_t>(val);
	ICPU._Carry = Int16 >= 0;
	SetZN(static_cast<uint8_t>(Int16));
}

inline void CPY16(uint16_t val)
{
	int32_t Int32 = static_cast<int32_t>(Registers.Y.W) - static_cast<int32_t>(val);
	ICPU._Carry = Int32 >= 0;
	SetZN(static_cast<uint16_t>(Int32));
}

inline void CPY8(uint8_t val)
{
	int16_t Int16 = static_cast<int16_t>(Registers.Y.B.l) - static_cast<int16_t>(val);
	ICPU._Carry = Int16 >= 0;
	SetZN(static_cast<uint8_t>(Int16));
}

inline void DEC16(uint32_t OpAddress, s9xwrap_t w)
{
	uint16_t Work16 = S9xGetWord(OpAddress, w) - 1;
	AddCycles(ONE_CYCLE);
	S9xSetWord(Work16, OpAddress, w, WRITE_10);
	OpenBus = Work16 & 0xff;
	SetZN(Work16);
}

inline void DEC8(uint32_t OpAddress)
{
	uint8_t Work8 = S9xGetByte(OpAddress) - 1;
	AddCycles(ONE_CYCLE);
	S9xSetByte(Work8, OpAddress);
	OpenBus = Work8;
	SetZN(Work8);
}

inline void EOR16(uint16_t val)
{
	Registers.A.W ^= val;
	SetZN(Registers.A.W);
}

inline void EOR8(uint8_t val)
{
	Registers.A.B.l ^= val;
	SetZN(Registers.A.B.l);
}

inline void INC16(uint32_t OpAddress, s9xwrap_t w)
{
	uint16_t Work16 = S9xGetWord(OpAddress, w) + 1;
	AddCycles(ONE_CYCLE);
	S9xSetWord(Work16, OpAddress, w, WRITE_10);
	OpenBus = Work16 & 0xff;
	SetZN(Work16);
}

inline void INC8(uint32_t OpAddress)
{
	uint8_t Work8 = S9xGetByte(OpAddress) + 1;
	AddCycles(ONE_CYCLE);
	S9xSetByte(Work8, OpAddress);
	OpenBus = Work8;
	SetZN(Work8);
}

inline void LDA16(uint16_t val)
{
	Registers.A.W = val;
	SetZN(Registers.A.W);
}

inline void LDA8(uint8_t val)
{
	Registers.A.B.l = val;
	SetZN(Registers.A.B.l);
}

inline void LDX16(uint16_t val)
{
	Registers.X.W = val;
	SetZN(Registers.X.W);
}

inline void LDX8(uint8_t val)
{
	Registers.X.B.l = val;
	SetZN(Registers.X.B.l);
}

inline void LDY16(uint16_t val)
{
	Registers.Y.W = val;
	SetZN(Registers.Y.W);
}

inline void LDY8(uint8_t val)
{
	Registers.Y.B.l = val;
	SetZN(Registers.Y.B.l);
}

inline void LSR16(uint32_t OpAddress, s9xwrap_t w)
{
	uint16_t Work16 = S9xGetWord(OpAddress, w);
	ICPU._Carry = Work16 & 1;
	Work16 >>= 1;
	AddCycles(ONE_CYCLE);
	S9xSetWord(Work16, OpAddress, w, WRITE_10);
	OpenBus = Work16 & 0xff;
	SetZN(Work16);
}

inline void LSR8(uint32_t OpAddress)
{
	uint8_t Work8 = S9xGetByte(OpAddress);
	ICPU._Carry = Work8 & 1;
	Work8 >>= 1;
	AddCycles(ONE_CYCLE);
	S9xSetByte(Work8, OpAddress);
	OpenBus = Work8;
	SetZN(Work8);
}

inline void ORA16(uint16_t val)
{
	Registers.A.W |= val;
	SetZN(Registers.A.W);
}

inline void ORA8(uint8_t val)
{
	Registers.A.B.l |= val;
	SetZN(Registers.A.B.l);
}

inline void ROL16(uint32_t OpAddress, s9xwrap_t w)
{
	uint32_t Work32 = (static_cast<uint32_t>(S9xGetWord(OpAddress, w)) << 1) | static_cast<uint32_t>(CheckCarry());
	ICPU._Carry = Work32 >= 0x10000;
	AddCycles(ONE_CYCLE);
	S9xSetWord(static_cast<uint16_t>(Work32), OpAddress, w, WRITE_10);
	OpenBus = Work32 & 0xff;
	SetZN(static_cast<uint16_t>(Work32));
}

inline void ROL8(uint32_t OpAddress)
{
	uint16_t Work16 = (static_cast<uint16_t>(S9xGetByte(OpAddress)) << 1) | static_cast<uint16_t>(CheckCarry());
	ICPU._Carry = Work16 >= 0x100;
	AddCycles(ONE_CYCLE);
	S9xSetByte(static_cast<uint8_t>(Work16), OpAddress);
	OpenBus = Work16 & 0xff;
	SetZN(static_cast<uint8_t>(Work16));
}

inline void ROR16(uint32_t OpAddress, s9xwrap_t w)
{
	uint32_t Work32 = static_cast<uint32_t>(S9xGetWord(OpAddress, w)) | (static_cast<uint32_t>(CheckCarry()) << 16);
	ICPU._Carry = Work32 & 1;
	Work32 >>= 1;
	AddCycles(ONE_CYCLE);
	S9xSetWord(static_cast<uint16_t>(Work32), OpAddress, w, WRITE_10);
	OpenBus = Work32 & 0xff;
	SetZN(static_cast<uint16_t>(Work32));
}

inline void ROR8(uint32_t OpAddress)
{
	uint16_t Work16 = static_cast<uint16_t>(S9xGetByte(OpAddress)) | (static_cast<uint16_t>(CheckCarry()) << 8);
	ICPU._Carry = Work16 & 1;
	Work16 >>= 1;
	AddCycles(ONE_CYCLE);
	S9xSetByte(static_cast<uint8_t>(Work16), OpAddress);
	OpenBus = Work16 & 0xff;
	SetZN(static_cast<uint8_t>(Work16));
}

inline void SBC16(uint16_t Work16)
{
	if (CheckDecimal())
	{
		int carry = CheckCarry();

		Work16 ^= 0xFFFF;

		int result = (Registers.A.W & 0x000F) + (Work16 & 0x000F) + carry;
		if (result < 0x0010)
			result -= 0x0006;
		carry = result > 0x000F;

		result = (Registers.A.W & 0x00F0) + (Work16 & 0x00F0) + (result & 0x000F) + carry * 0x10;
		if (result < 0x0100)
			result -= 0x0060;
		carry = result > 0x00FF;

		result = (Registers.A.W & 0x0F00) + (Work16 & 0x0F00) + (result & 0x00FF) + carry * 0x100;
		if (result < 0x1000)
			result -= 0x0600;
		carry = result > 0x0FFF;

		result = (Registers.A.W & 0xF000) + (Work16 & 0xF000) + (result & 0x0FFF) + carry * 0x1000;

		if (!((Registers.A.W ^ Work16) & 0x8000) && ((Registers.A.W ^ result) & 0x8000))
			SetOverflow();
		else
			ClearOverflow();

		if (result < 0x10000)
			result -= 0x6000;

		if (result > 0xFFFF)
			SetCarry();
		else
			ClearCarry();

		Registers.A.W = result & 0xFFFF;
		SetZN(Registers.A.W);
	}
	else
	{
		int32_t Int32 = static_cast<int32_t>(Registers.A.W) - static_cast<int32_t>(Work16) + static_cast<int32_t>(CheckCarry()) - 1;

		ICPU._Carry = Int32 >= 0;

		if ((Registers.A.W ^ Work16) & (Registers.A.W ^ static_cast<uint16_t>(Int32)) & 0x8000)
			SetOverflow();
		else
			ClearOverflow();

		Registers.A.W = static_cast<uint16_t>(Int32);
		SetZN(Registers.A.W);
	}
}

inline void SBC8(uint8_t Work8)
{
	if (CheckDecimal())
	{
		int carry = CheckCarry();

		Work8 ^= 0xFF;

		int result = (Registers.A.B.l & 0x0F) + (Work8 & 0x0F) + carry;
		if (result < 0x10)
			result -= 0x06;
		carry = (result > 0x0F);

		result = (Registers.A.B.l & 0xF0) + (Work8 & 0xF0) + (result & 0x0F) + carry * 0x10;

		if ((Registers.A.B.l & 0x80) == (Work8 & 0x80) && (Registers.A.B.l & 0x80) != (result & 0x80))
			SetOverflow();
		else
			ClearOverflow();

		if (result < 0x100)
			result -= 0x60;

		if (result > 0xFF)
			SetCarry();
		else
			ClearCarry();

		Registers.A.B.l = result & 0xFF;
		SetZN(Registers.A.B.l);
	}
	else
	{
		int16_t Int16 = static_cast<int16_t>(Registers.A.B.l) - static_cast<int16_t>(Work8) + static_cast<int16_t>(CheckCarry()) - 1;

		ICPU._Carry = Int16 >= 0;

		if ((Registers.A.B.l ^ Work8) & (Registers.A.B.l ^ static_cast<uint8_t>(Int16)) & 0x80)
			SetOverflow();
		else
			ClearOverflow();

		Registers.A.B.l = static_cast<uint8_t>(Int16);
		SetZN(Registers.A.B.l);
	}
}

inline void STA16(uint32_t OpAddress, s9xwrap_t w)
{
	S9xSetWord(Registers.A.W, OpAddress, w);
	OpenBus = Registers.A.B.h;
}

inline void STA8(uint32_t OpAddress)
{
	S9xSetByte(Registers.A.B.l, OpAddress);
	OpenBus = Registers.A.B.l;
}

inline void STX16(uint32_t OpAddress, s9xwrap_t w)
{
	S9xSetWord(Registers.X.W, OpAddress, w);
	OpenBus = Registers.X.B.h;
}

inline void STX8(uint32_t OpAddress)
{
	S9xSetByte(Registers.X.B.l, OpAddress);
	OpenBus = Registers.X.B.l;
}

inline void STY16(uint32_t OpAddress, s9xwrap_t w)
{
	S9xSetWord(Registers.Y.W, OpAddress, w);
	OpenBus = Registers.Y.B.h;
}

inline void STY8(uint32_t OpAddress)
{
	S9xSetByte(Registers.Y.B.l, OpAddress);
	OpenBus = Registers.Y.B.l;
}

inline void STZ16(uint32_t OpAddress, s9xwrap_t w)
{
	S9xSetWord(0, OpAddress, w);
	OpenBus = 0;
}

inline void STZ8(uint32_t OpAddress)
{
	S9xSetByte(0, OpAddress);
	OpenBus = 0;
}

inline void TRB16(uint32_t OpAddress, s9xwrap_t w)
{
	uint16_t Work16 = S9xGetWord(OpAddress, w);
	ICPU._Zero = !!(Work16 & Registers.A.W);
	Work16 &= ~Registers.A.W;
	AddCycles(ONE_CYCLE);
	S9xSetWord(Work16, OpAddress, w, WRITE_10);
	OpenBus = Work16 & 0xff;
}

inline void TRB8(uint32_t OpAddress)
{
	uint8_t Work8 = S9xGetByte(OpAddress);
	ICPU._Zero = Work8 & Registers.A.B.l;
	Work8 &= ~Registers.A.B.l;
	AddCycles(ONE_CYCLE);
	S9xSetByte(Work8, OpAddress);
	OpenBus = Work8;
}

inline void TSB16(uint32_t OpAddress, s9xwrap_t w)
{
	uint16_t Work16 = S9xGetWord(OpAddress, w);
	ICPU._Zero = !!(Work16 & Registers.A.W);
	Work16 |= Registers.A.W;
	AddCycles(ONE_CYCLE);
	S9xSetWord(Work16, OpAddress, w, WRITE_10);
	OpenBus = Work16 & 0xff;
}

inline void TSB8(uint32_t OpAddress)
{
	uint8_t Work8 = S9xGetByte(OpAddress);
	ICPU._Zero = Work8 & Registers.A.B.l;
	Work8 |= Registers.A.B.l;
	AddCycles(ONE_CYCLE);
	S9xSetByte(Work8, OpAddress);
	OpenBus = Work8;
}