namespace BizHawk.Emulation.Cores.Components.Z80
{
	public partial class Z80A
	{
		private void InitialiseTables()
		{
			InitTableInc();
			InitTableDec();
			InitTableParity();
			InitTableALU();
			InitTableRotShift();
			InitTableHalfBorrow();
			InitTableHalfCarry();
			InitTableNeg();
			InitTableDaa();
		}

		private byte[] TableInc;
		private void InitTableInc()
		{
			TableInc = new byte[256];
			for (int i = 0; i < 256; ++i)
				TableInc[i] = FlagByte(false, false, i == 0x80, UndocumentedX(i), (i & 0xF) == 0x0, UndocumentedY(i), i == 0, i > 127);
		}

		private byte[] TableDec;
		private void InitTableDec()
		{
			TableDec = new byte[256];
			for (int i = 0; i < 256; ++i)
				TableDec[i] = FlagByte(false, true, i == 0x7F, UndocumentedX(i), (i & 0xF) == 0xF, UndocumentedY(i), i == 0, i > 127);
		}

		private bool[] TableParity;
		private void InitTableParity()
		{
			TableParity = new bool[256];
			for (int i = 0; i < 256; ++i)
			{
				int Bits = 0;
				for (int j = 0; j < 8; ++j)
				{
					Bits += (i >> j) & 1;
				}
				TableParity[i] = (Bits & 1) == 0;
			}
		}

		private ushort[, , ,] TableALU;
		private void InitTableALU()
		{
			TableALU = new ushort[8, 256, 256, 2]; // Class, OP1, OP2, Carry

			for (int i = 0; i < 8; ++i)
			{
				for (int op1 = 0; op1 < 256; ++op1)
				{
					for (int op2 = 0; op2 < 256; ++op2)
					{
						for (int c = 0; c < 2; ++c)
						{

							int ac = (i == 1 || i == 3) ? c : 0;

							bool S = false;
							bool Z = false;
							bool C = false;
							bool H = false;
							bool N = false;
							bool P = false;

							byte result_b = 0;
							int result_si = 0;
							int result_ui = 0;

							// Fetch result
							switch (i)
							{
								case 0:
								case 1:
									result_si = (sbyte)op1 + (sbyte)op2 + ac;
									result_ui = op1 + op2 + ac;
									break;
								case 2:
								case 3:
								case 7:
									result_si = (sbyte)op1 - (sbyte)op2 - ac;
									result_ui = op1 - op2 - ac;
									break;
								case 4:
									result_si = op1 & op2;
									break;
								case 5:
									result_si = op1 ^ op2;
									break;
								case 6:
									result_si = op1 | op2;
									break;
							}

							result_b = (byte)result_si;

							// Parity/Carry

							switch (i)
							{
								case 0:
								case 1:
								case 2:
								case 3:
								case 7:
									P = result_si < -128 || result_si > 127;
									C = result_ui < 0 || result_ui > 255;
									break;
								case 4:
								case 5:
								case 6:
									P = TableParity[result_b];
									C = false;
									break;
							}

							// Subtraction
							N = i == 2 || i == 3 || i == 7;

							// Half carry
							switch (i)
							{
								case 0:
								case 1:
									H = ((op1 & 0xF) + (op2 & 0xF) + (ac & 0xF)) > 0xF;
									break;
								case 2:
								case 3:
								case 7:
									H = ((op1 & 0xF) - (op2 & 0xF) - (ac & 0xF)) < 0x0;
									break;
								case 4:
									H = true;
									break;
								case 5:
								case 6:
									H = false;
									break;
							}

							// Undocumented
							byte UndocumentedFlags = (byte)(result_b & 0x28);
							if (i == 7) UndocumentedFlags = (byte)(op2 & 0x28);

							S = result_b > 127;
							Z = result_b == 0;

							if (i == 7) result_b = (byte)op1;

							TableALU[i, op1, op2, c] = (ushort)(
								result_b * 256 +
								((C ? 0x01 : 0) + (N ? 0x02 : 0) + (P ? 0x04 : 0) + (H ? 0x10 : 0) + (Z ? 0x40 : 0) + (S ? 0x80 : 0)) +
								(UndocumentedFlags));

						}
					}
				}
			}
		}

		private bool[,] TableHalfBorrow;
		private void InitTableHalfBorrow()
		{
			TableHalfBorrow = new bool[256, 256];
			for (int i = 0; i < 256; i++)
			{
				for (int j = 0; j < 256; j++)
				{
					TableHalfBorrow[i, j] = ((i & 0xF) - (j & 0xF)) < 0;
				}
			}
		}

		private bool[,] TableHalfCarry;
		private void InitTableHalfCarry()
		{
			TableHalfCarry = new bool[256, 256];
			for (int i = 0; i < 256; i++)
			{
				for (int j = 0; j < 256; j++)
				{
					TableHalfCarry[i, j] = ((i & 0xF) + (j & 0xF)) > 0xF;
				}
			}
		}

		private ushort[, ,] TableRotShift;
		private void InitTableRotShift()
		{
			TableRotShift = new ushort[2, 8, 65536]; // All, operation, AF
			for (int all = 0; all < 2; all++)
			{
				for (int y = 0; y < 8; ++y)
				{
					for (int af = 0; af < 65536; af++)
					{
						byte Old = (byte)(af >> 8);
						bool OldCarry = (af & 0x01) != 0;

						ushort newAf = (ushort)(af & ~(0x13)); // Clear HALF-CARRY, SUBTRACT and CARRY flags

						byte New = Old;
						if ((y & 1) == 0)
						{
							if ((Old & 0x80) != 0) ++newAf;

							New <<= 1;

							if ((y & 0x04) == 0)
							{
								if (((y & 0x02) == 0) ? ((newAf & 0x01) != 0) : OldCarry) New |= 0x01;
							}
							else
							{
								if ((y & 0x02) != 0) New |= 0x01;
							}

						}
						else
						{

							if ((Old & 0x01) != 0) ++newAf;

							New >>= 1;

							if ((y & 0x04) == 0)
							{
								if (((y & 0x02) == 0) ? ((newAf & 0x01) != 0) : OldCarry) New |= 0x80;
							}
							else
							{
								if ((y & 0x02) == 0) New |= (byte)(Old & 0x80);
							}
						}

						newAf &= 0xFF;
						newAf |= (ushort)(New * 256);

						if (all == 1)
						{
							newAf &= unchecked((ushort)~0xC4); // Clear S, Z, P
							if (New > 127) newAf |= 0x80;
							if (New == 0) newAf |= 0x40;
							if (TableParity[New]) newAf |= 0x04;
						}

						TableRotShift[all, y, af] = (ushort)((newAf & ~0x28) | ((newAf >> 8) & 0x28));
					}
				}
			}
		}

		private ushort[] TableNeg;
		private void InitTableNeg()
		{
			TableNeg = new ushort[65536];
			for (int af = 0; af < 65536; af++)
			{
				ushort raf = 0;
				byte b = (byte)(af >> 8);
				byte a = (byte)-b;
				raf |= (ushort)(a * 256);
				raf |= FlagByte(b != 0x00, true, b == 0x80, UndocumentedX(a), TableHalfCarry[a, b], UndocumentedY(a), a == 0, a > 127);
				TableNeg[af] = raf;
			}
		}

		private ushort[] TableDaa;
		private void InitTableDaa()
		{
			TableDaa = new ushort[65536];
			for (int af = 0; af < 65536; ++af)
			{
				byte a = (byte)(af >> 8);
				byte tmp = a;

				if (IsN(af))
				{
					if (IsH(af) || ((a & 0x0F) > 0x09)) tmp -= 0x06;
					if (IsC(af) || a > 0x99) tmp -= 0x60;
				}
				else
				{
					if (IsH(af) || ((a & 0x0F) > 0x09)) tmp += 0x06;
					if (IsC(af) || a > 0x99) tmp += 0x60;
				}

				TableDaa[af] = (ushort)((tmp * 256) + FlagByte(IsC(af) || a > 0x99, IsN(af), TableParity[tmp], UndocumentedX(tmp), ((a ^ tmp) & 0x10) != 0, UndocumentedY(tmp), tmp == 0, tmp > 127));
			}
		}

		private byte FlagByte(bool C, bool N, bool P, bool X, bool H, bool Y, bool Z, bool S)
		{
			return (byte)(
				(C ? 0x01 : 0) +
				(N ? 0x02 : 0) +
				(P ? 0x04 : 0) +
				(X ? 0x08 : 0) +
				(H ? 0x10 : 0) +
				(Y ? 0x20 : 0) +
				(Z ? 0x40 : 0) +
				(S ? 0x80 : 0)
			 );
		}

		private bool UndocumentedX(int value)
		{
			return (value & 0x08) != 0;
		}

		private bool UndocumentedY(int value)
		{
			return (value & 0x20) != 0;
		}

		private bool IsC(int value) { return (value & 0x01) != 0; }
		private bool IsN(int value) { return (value & 0x02) != 0; }
		private bool IsP(int value) { return (value & 0x04) != 0; }
		private bool IsX(int value) { return (value & 0x08) != 0; }
		private bool IsH(int value) { return (value & 0x10) != 0; }
		private bool IsY(int value) { return (value & 0x20) != 0; }
		private bool IsZ(int value) { return (value & 0x40) != 0; }
		private bool IsS(int value) { return (value & 0x80) != 0; }
	}
}
