using System;

namespace BizHawk.Emulation.Cores.Components.M68000
{
	partial class MC68000
	{
		bool TestCondition(int condition)
		{
			switch (condition)
			{
				case 0x00: return true;     // True
				case 0x01: return false;    // False
				case 0x02: return !C && !Z; // High (Unsigned)
				case 0x03: return C || Z;   // Less or Same (Unsigned)
				case 0x04: return !C;       // Carry Clear (High or Same)
				case 0x05: return C;        // Carry Set (Lower)
				case 0x06: return !Z;       // Not Equal
				case 0x07: return Z;        // Equal
				case 0x08: return !V;       // Overflow Clear
				case 0x09: return V;        // Overflow Set
				case 0x0A: return !N;       // Plus (Positive)
				case 0x0B: return N;        // Minus (Negative)
				case 0x0C: return N && V || !N && !V;             // Greater or Equal
				case 0x0D: return N && !V || !N && V;             // Less Than
				case 0x0E: return N && V && !Z || !N && !V && !Z; // Greater Than
				case 0x0F: return Z || N && !V || !N && V;        // Less or Equal
				default:
					throw new Exception("Invalid condition " + condition);
			}
		}

		string DisassembleCondition(int condition)
		{
			switch (condition)
			{
				case 0x00: return "t";  // True
				case 0x01: return "f";  // False
				case 0x02: return "hi"; // High (Unsigned)
				case 0x03: return "ls"; // Less or Same (Unsigned)
				case 0x04: return "cc"; // Carry Clear (High or Same)
				case 0x05: return "cs"; // Carry Set (Lower)
				case 0x06: return "ne"; // Not Equal
				case 0x07: return "eq"; // Equal
				case 0x08: return "vc"; // Overflow Clear
				case 0x09: return "vs"; // Overflow Set
				case 0x0A: return "pl"; // Plus (Positive)
				case 0x0B: return "mi"; // Minus (Negative)
				case 0x0C: return "ge"; // Greater or Equal
				case 0x0D: return "lt"; // Less Than
				case 0x0E: return "gt"; // Greater Than
				case 0x0F: return "le"; // Less or Equal
				default: return "??"; // Invalid condition
			}
		}

		void Bcc() // Branch on condition
		{
			sbyte displacement8 = (sbyte)op;
			int cond = (op >> 8) & 0x0F;

			if (TestCondition(cond) == true)
			{
				if (displacement8 != 0)
				{
					// use opcode-embedded displacement
					PC += displacement8;
					PendingCycles -= 10;
				}
				else
				{
					// use extension word displacement
					PC += ReadWord(PC);
					PendingCycles -= 10;
				}
			}
			else
			{ // false
				if (displacement8 != 0)
					PendingCycles -= 8;
				else
				{
					PC += 2;
					PendingCycles -= 12;
				}
			}
		}

		void Bcc_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			sbyte displacement8 = (sbyte)op;
			int cond = (op >> 8) & 0x0F;

			info.Mnemonic = "b" + DisassembleCondition(cond);
			if (displacement8 != 0)
			{
				info.Args = string.Format("${0:X}", pc + displacement8);
			}
			else
			{
				info.Args = string.Format("${0:X}", pc + ReadWord(pc));
				pc += 2;
			}
			info.Length = pc - info.PC;
		}

		void BRA()
		{
			sbyte displacement8 = (sbyte)op;

			if (displacement8 != 0)
				PC += displacement8;
			else
				PC += ReadWord(PC);
			PendingCycles -= 10;
		}

		void BRA_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			info.Mnemonic = "bra";

			sbyte displacement8 = (sbyte)op;
			if (displacement8 != 0)
				info.Args = String.Format("${0:X}", pc + displacement8);
			else
			{
				info.Args = String.Format("${0:X}", pc + ReadWord(pc));
				pc += 2;
			}
			info.Length = pc - info.PC;
		}

		void BSR()
		{
			sbyte displacement8 = (sbyte)op;

			A[7].s32 -= 4;
			if (displacement8 != 0)
			{
				// use embedded displacement
				WriteLong(A[7].s32, PC);
				PC += displacement8;
			}
			else
			{
				// use extension word displacement
				WriteLong(A[7].s32, PC + 2);
				PC += ReadWord(PC);
			}
			PendingCycles -= 18;
		}

		void BSR_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			info.Mnemonic = "bsr";

			sbyte displacement8 = (sbyte)op;
			if (displacement8 != 0)
				info.Args = String.Format("${0:X}", pc + displacement8);
			else
			{
				info.Args = String.Format("${0:X}", pc + ReadWord(pc));
				pc += 2;
			}
			info.Length = pc - info.PC;
		}

		void DBcc()
		{
			if (TestCondition((op >> 8) & 0x0F) == true)
			{
				PC += 2; // condition met, break out of loop
				PendingCycles -= 12;
			}
			else
			{
				int reg = op & 7;
				D[reg].u16--;

				if (D[reg].u16 == 0xFFFF)
				{
					PC += 2; // counter underflowed, break out of loop
					PendingCycles -= 14;
				}
				else
				{
					PC += ReadWord(PC); // condition false and counter not exhausted, so branch.
					PendingCycles -= 10;
				}
			}
		}

		void DBcc_Disasm(DisassemblyInfo info)
		{
			int cond = (op >> 8) & 0x0F;
			if (cond == 1)
				info.Mnemonic = "dbra";
			else
				info.Mnemonic = "db" + DisassembleCondition(cond);

			int pc = info.PC + 2;
			info.Args = String.Format("D{0}, ${1:X}", op & 7, pc + ReadWord(pc));
			info.Length = 4;
		}

		void RTS()
		{
			PC = ReadLong(A[7].s32);
			A[7].s32 += 4;
			PendingCycles -= 16;
		}

		void RTS_Disasm(DisassemblyInfo info)
		{
			info.Mnemonic = "rts";
			info.Args = "";
		}

		void RTR()
		{
			ushort sr = (ushort)ReadWord(A[7].s32);
			A[7].s32 += 2;
			C = (sr & 0x0001) != 0;
			V = (sr & 0x0002) != 0;
			Z = (sr & 0x0004) != 0;
			N = (sr & 0x0008) != 0;
			X = (sr & 0x0010) != 0;

			PC = ReadLong(A[7].s32);
			A[7].s32 += 4;
			PendingCycles -= 20;
		}

		void RTR_Disasm(DisassemblyInfo info)
		{
			info.Mnemonic = "rtr";
			info.Args = "";
		}

		void RTE()
		{
			short newSR = ReadWord(A[7].s32);
			A[7].s32 += 2;
			PC = ReadLong(A[7].s32);
			A[7].s32 += 4;
			SR = newSR;
			PendingCycles -= 20;
		}

		void RTE_Disasm(DisassemblyInfo info)
		{
			info.Mnemonic = "rte";
			info.Args = "";
		}

		void TST()
		{
			int size = (op >> 6) & 3;
			int mode = (op >> 3) & 7;
			int reg = (op >> 0) & 7;

			int value;
			switch (size)
			{
				case 0: value = ReadValueB(mode, reg); PendingCycles -= 4 + EACyclesBW[mode, reg]; N = (value & 0x80) != 0; break;
				case 1: value = ReadValueW(mode, reg); PendingCycles -= 4 + EACyclesBW[mode, reg]; N = (value & 0x8000) != 0; break;
				default: value = ReadValueL(mode, reg); PendingCycles -= 4 + EACyclesL[mode, reg]; N = (value & 0x80000000) != 0; break;
			}
			V = false;
			C = false;
			Z = (value == 0);
		}

		void TST_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int size = (op >> 6) & 3;
			int mode = (op >> 3) & 7;
			int reg = (op >> 0) & 7;

			switch (size)
			{
				case 0: info.Mnemonic = "tst.b"; info.Args = DisassembleValue(mode, reg, 1, ref pc); break;
				case 1: info.Mnemonic = "tst.w"; info.Args = DisassembleValue(mode, reg, 2, ref pc); break;
				case 2: info.Mnemonic = "tst.l"; info.Args = DisassembleValue(mode, reg, 4, ref pc); break;
			}
			info.Length = pc - info.PC;
		}

		void BTSTi()
		{
			int bit = ReadWord(PC); PC += 2;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			if (mode == 0)
			{
				bit &= 31;
				int mask = 1 << bit;
				Z = (D[reg].s32 & mask) == 0;
				PendingCycles -= 10;
			}
			else
			{
				bit &= 7;
				int mask = 1 << bit;
				Z = (ReadValueB(mode, reg) & mask) == 0;
				PendingCycles -= 8 + EACyclesBW[mode, reg];
			}
		}

		void BTSTi_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int bit = ReadWord(pc); pc += 2;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			info.Mnemonic = "btst";
			info.Args = String.Format("${0:X}, {1}", bit, DisassembleValue(mode, reg, 1, ref pc));
			info.Length = pc - info.PC;
		}

		void BTSTr()
		{
			int dReg = (op >> 9) & 7;
			int mode = (op >> 3) & 7;
			int reg = op & 7;
			int bit = D[dReg].s32;

			if (mode == 0)
			{
				bit &= 31;
				int mask = 1 << bit;
				Z = (D[reg].s32 & mask) == 0;
				PendingCycles -= 6;
			}
			else
			{
				bit &= 7;
				int mask = 1 << bit;
				Z = (ReadValueB(mode, reg) & mask) == 0;
				PendingCycles -= 4 + EACyclesBW[mode, reg];
			}
		}

		void BTSTr_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int dReg = (op >> 9) & 7;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			info.Mnemonic = "btst";
			info.Args = String.Format("D{0}, {1}", dReg, DisassembleValue(mode, reg, 1, ref pc));
			info.Length = pc - info.PC;
		}

		void BCHGi()
		{
			int bit = ReadWord(PC); PC += 2;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			if (mode == 0)
			{
				bit &= 31;
				int mask = 1 << bit;
				Z = (D[reg].s32 & mask) == 0;
				D[reg].s32 ^= mask;
				PendingCycles -= 10;
			}
			else
			{
				bit &= 7;
				int mask = 1 << bit;
				sbyte value = PeekValueB(mode, reg);
				Z = (value & mask) == 0;
				value ^= (sbyte)mask;
				WriteValueB(mode, reg, value);
				PendingCycles -= 8 + EACyclesBW[mode, reg];
			}
		}

		void BCHGi_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int bit = ReadWord(pc); pc += 2;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			info.Mnemonic = "bchg";
			info.Args = String.Format("${0:X}, {1}", bit, DisassembleValue(mode, reg, 1, ref pc));
			info.Length = pc - info.PC;
		}

		void BCHGr()
		{
			int dReg = (op >> 9) & 7;
			int mode = (op >> 3) & 7;
			int reg = op & 7;
			int bit = D[dReg].s32;

			if (mode == 0)
			{
				bit &= 31;
				int mask = 1 << bit;
				Z = (D[reg].s32 & mask) == 0;
				D[reg].s32 ^= mask;
				PendingCycles -= 6;
			}
			else
			{
				bit &= 7;
				int mask = 1 << bit;
				sbyte value = PeekValueB(mode, reg);
				Z = (value & mask) == 0;
				value ^= (sbyte)mask;
				WriteValueB(mode, reg, value);
				PendingCycles -= 4 + EACyclesBW[mode, reg];
			}
		}

		void BCHGr_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int dReg = (op >> 9) & 7;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			info.Mnemonic = "bchg";
			info.Args = String.Format("D{0}, {1}", dReg, DisassembleValue(mode, reg, 1, ref pc));
			info.Length = pc - info.PC;
		}

		void BCLRi()
		{
			int bit = ReadWord(PC); PC += 2;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			if (mode == 0)
			{
				bit &= 31;
				int mask = 1 << bit;
				Z = (D[reg].s32 & mask) == 0;
				D[reg].s32 &= ~mask;
				PendingCycles -= 10;
			}
			else
			{
				bit &= 7;
				int mask = 1 << bit;
				sbyte value = PeekValueB(mode, reg);
				Z = (value & mask) == 0;
				value &= (sbyte)~mask;
				WriteValueB(mode, reg, value);
				PendingCycles -= 8 + EACyclesBW[mode, reg];
			}
		}

		void BCLRi_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int bit = ReadWord(pc); pc += 2;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			info.Mnemonic = "bclr";
			info.Args = String.Format("${0:X}, {1}", bit, DisassembleValue(mode, reg, 1, ref pc));
			info.Length = pc - info.PC;
		}

		void BCLRr()
		{
			int dReg = (op >> 9) & 7;
			int mode = (op >> 3) & 7;
			int reg = op & 7;
			int bit = D[dReg].s32;

			if (mode == 0)
			{
				bit &= 31;
				int mask = 1 << bit;
				Z = (D[reg].s32 & mask) == 0;
				D[reg].s32 &= ~mask;
				PendingCycles -= 6;
			}
			else
			{
				bit &= 7;
				int mask = 1 << bit;
				sbyte value = PeekValueB(mode, reg);
				Z = (value & mask) == 0;
				value &= (sbyte)~mask;
				WriteValueB(mode, reg, value);
				PendingCycles -= 4 + EACyclesBW[mode, reg];
			}
		}

		void BCLRr_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int dReg = (op >> 9) & 7;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			info.Mnemonic = "bclr";
			info.Args = String.Format("D{0}, {1}", dReg, DisassembleValue(mode, reg, 1, ref pc));
			info.Length = pc - info.PC;
		}

		void BSETi()
		{
			int bit = ReadWord(PC); PC += 2;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			if (mode == 0)
			{
				bit &= 31;
				int mask = 1 << bit;
				Z = (D[reg].s32 & mask) == 0;
				D[reg].s32 |= mask;
				PendingCycles -= 10;
			}
			else
			{
				bit &= 7;
				int mask = 1 << bit;
				sbyte value = PeekValueB(mode, reg);
				Z = (value & mask) == 0;
				value |= (sbyte)mask;
				WriteValueB(mode, reg, value);
				PendingCycles -= 8 + EACyclesBW[mode, reg];
			}
		}

		void BSETi_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int bit = ReadWord(pc); pc += 2;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			info.Mnemonic = "bset";
			info.Args = String.Format("${0:X}, {1}", bit, DisassembleValue(mode, reg, 1, ref pc));
			info.Length = pc - info.PC;
		}

		void BSETr()
		{
			int dReg = (op >> 9) & 7;
			int mode = (op >> 3) & 7;
			int reg = op & 7;
			int bit = D[dReg].s32;

			if (mode == 0)
			{
				bit &= 31;
				int mask = 1 << bit;
				Z = (D[reg].s32 & mask) == 0;
				D[reg].s32 |= mask;
				PendingCycles -= 6;
			}
			else
			{
				bit &= 7;
				int mask = 1 << bit;
				sbyte value = PeekValueB(mode, reg);
				Z = (value & mask) == 0;
				value |= (sbyte)mask;
				WriteValueB(mode, reg, value);
				PendingCycles -= 4 + EACyclesBW[mode, reg];
			}
		}

		void BSETr_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int dReg = (op >> 9) & 7;
			int mode = (op >> 3) & 7;
			int reg = op & 7;

			info.Mnemonic = "bset";
			info.Args = String.Format("D{0}, {1}", dReg, DisassembleValue(mode, reg, 1, ref pc));
			info.Length = pc - info.PC;
		}

		void JMP()
		{
			int mode = (op >> 3) & 7;
			int reg = (op >> 0) & 7;
			PC = ReadAddress(mode, reg);

			switch (mode)
			{
				case 2: PendingCycles -= 8; break;
				case 5: PendingCycles -= 10; break;
				case 6: PendingCycles -= 14; break;
				case 7:
					switch (reg)
					{
						case 0: PendingCycles -= 10; break;
						case 1: PendingCycles -= 12; break;
						case 2: PendingCycles -= 10; break;
						case 3: PendingCycles -= 14; break;
					}
					break;
			}
		}

		void JMP_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int mode = (op >> 3) & 7;
			int reg = (op >> 0) & 7;
			info.Mnemonic = "jmp";
			info.Args = DisassembleValue(mode, reg, 1, ref pc);
			info.Length = pc - info.PC;
		}

		void JSR()
		{
			int mode = (op >> 3) & 7;
			int reg = (op >> 0) & 7;
			int addr = ReadAddress(mode, reg);

			A[7].s32 -= 4;
			WriteLong(A[7].s32, PC);
			PC = addr;

			switch (mode)
			{
				case 2: PendingCycles -= 16; break;
				case 5: PendingCycles -= 18; break;
				case 6: PendingCycles -= 22; break;
				case 7:
					switch (reg)
					{
						case 0: PendingCycles -= 18; break;
						case 1: PendingCycles -= 20; break;
						case 2: PendingCycles -= 18; break;
						case 3: PendingCycles -= 22; break;
					}
					break;
			}
		}

		void JSR_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int mode = (op >> 3) & 7;
			int reg = (op >> 0) & 7;
			info.Mnemonic = "jsr";
			info.Args = DisassembleAddress(mode, reg, ref pc);
			info.Length = pc - info.PC;
		}

		void LINK()
		{
			int reg = op & 7;
			A[7].s32 -= 4;
			short offset = ReadWord(PC); PC += 2;
			WriteLong(A[7].s32, A[reg].s32);
			A[reg].s32 = A[7].s32;
			A[7].s32 += offset;
			PendingCycles -= 16;
		}

		void LINK_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int reg = op & 7;
			info.Mnemonic = "link";
			info.Args = "A" + reg + ", " + DisassembleImmediate(2, ref pc); // TODO need a DisassembleSigned or something
			info.Length = pc - info.PC;
		}

		void UNLK()
		{
			int reg = op & 7;
			A[7].s32 = A[reg].s32;
			A[reg].s32 = ReadLong(A[7].s32);
			A[7].s32 += 4;
			PendingCycles -= 12;
		}

		void UNLK_Disasm(DisassemblyInfo info)
		{
			int reg = op & 7;
			info.Mnemonic = "unlk";
			info.Args = "A" + reg;
			info.Length = 2;
		}

		void NOP()
		{
			PendingCycles -= 4;
		}

		void NOP_Disasm(DisassemblyInfo info)
		{
			info.Mnemonic = "nop";
		}

		void Scc() // Set on condition
		{
			int cond = (op >> 8) & 0x0F;
			int mode = (op >> 3) & 7;
			int reg = (op >> 0) & 7;

			if (TestCondition(cond) == true)
			{
				WriteValueB(mode, reg, -1);
				if (mode == 0) PendingCycles -= 6;
				else PendingCycles -= 8 + EACyclesBW[mode, reg];
			}
			else
			{
				WriteValueB(mode, reg, 0);
				if (mode == 0) PendingCycles -= 4;
				else PendingCycles -= 8 + EACyclesBW[mode, reg];
			}
		}

		void Scc_Disasm(DisassemblyInfo info)
		{
			int pc = info.PC + 2;
			int cond = (op >> 8) & 0x0F;
			int mode = (op >> 3) & 7;
			int reg = (op >> 0) & 7;

			info.Mnemonic = "s" + DisassembleCondition(cond);
			info.Args = DisassembleValue(mode, reg, 1, ref pc);
			info.Length = pc - info.PC;
		}
	}
}
