using System;
using System.Collections.Generic;

using BizHawk.Common.StringExtensions;

namespace BizHawk.Emulation.Cores.Components.M68000
{
	partial class MC68000
	{
		void BuildOpcodeTable()
		{
			// NOTE: Do not change the order of these assigns without testing. There is
			// some overwriting of less-specific opcodes with more-specific opcodes.
			// TODO: should really come up with means of only assigning to applicable addressing modes,
			// instead of this lame overwriting business.

			Assign("move", MOVE, "00", "Size2_0", "XnAm", "AmXn");
			Assign("movea", MOVEA, "00", "Size2_0", "Xn", "001", "AmXn");
			Assign("moveq", MOVEQ, "0111", "Xn", "0", "Data8");
			Assign("movem", MOVEM0, "010010001", "Size1", "AmXn");
			Assign("movem", MOVEM1, "010011001", "Size1", "AmXn");
			Assign("lea", LEA, "0100", "Xn", "111", "AmXn");
			Assign("clr", CLR, "01000010", "Size2_1", "AmXn");
			Assign("ext", EXT, "010010001", "Size1", "000", "Xn");
			Assign("pea", PEA, "0100100001", "AmXn");

			Assign("andi", ANDI, "00000010", "Size2_1", "AmXn");
			Assign("eori", EORI, "00001010", "Size2_1", "AmXn");
			Assign("ori", ORI, "00000000", "Size2_1", "AmXn");
			Assign("asl", ASLd, "1110", "Data3", "1", "Size2_1", "Data1", "00", "Xn");
			Assign("asr", ASRd, "1110", "Data3", "0", "Size2_1", "Data1", "00", "Xn");
			Assign("lsl", LSLd, "1110", "Data3", "1", "Size2_1", "Data1", "01", "Xn");
			Assign("lsr", LSRd, "1110", "Data3", "0", "Size2_1", "Data1", "01", "Xn");
			Assign("roxl", ROXLd, "1110", "Data3", "1", "Size2_1", "Data1", "10", "Xn");
			Assign("roxr", ROXRd, "1110", "Data3", "0", "Size2_1", "Data1", "10", "Xn");
			Assign("rol", ROLd, "1110", "Data3", "1", "Size2_1", "Data1", "11", "Xn");
			Assign("ror", RORd, "1110", "Data3", "0", "Size2_1", "Data1", "11", "Xn");
			Assign("swap", SWAP, "0100100001000", "Xn");
			Assign("and", AND0, "1100", "Xn", "0", "Size2_1", "AmXn");
			Assign("and", AND1, "1100", "Xn", "1", "Size2_1", "AmXn");
			Assign("eor", EOR, "1011", "Xn", "1", "Size2_1", "AmXn");
			Assign("or", OR0, "1000", "Xn", "0", "Size2_1", "AmXn");
			Assign("or", OR1, "1000", "Xn", "1", "Size2_1", "AmXn");
			Assign("not", NOT, "01000110", "Size2_1", "AmXn");
			Assign("neg", NEG, "01000100", "Size2_1", "AmXn");

			Assign("jmp", JMP, "0100111011", "AmXn");
			Assign("jsr", JSR, "0100111010", "AmXn");
			Assign("bcc", Bcc, "0110", "CondMain", "Data8");
			Assign("bra", BRA, "01100000", "Data8");
			Assign("bsr", BSR, "01100001", "Data8");
			Assign("scc", Scc, "0101", "CondAll", "11", "AmXn");
			Assign("dbcc", DBcc, "0101", "CondAll", "11001", "Xn");
			Assign("rte", RTE, "0100111001110011");
			Assign("rts", RTS, "0100111001110101");
			Assign("rtr", RTR, "0100111001110111");
			Assign("tst", TST, "01001010", "Size2_1", "AmXn");
			Assign("btst", BTSTi, "0000100000", "AmXn");
			Assign("btst", BTSTr, "0000", "Xn", "100", "AmXn");
			Assign("bchg", BCHGi, "0000100001", "AmXn");
			Assign("bchg", BCHGr, "0000", "Xn", "101", "AmXn");
			Assign("bclr", BCLRi, "0000100010", "AmXn");
			Assign("bclr", BCLRr, "0000", "Xn", "110", "AmXn");
			Assign("bset", BSETi, "0000100011", "AmXn");
			Assign("bset", BSETr, "0000", "Xn", "111", "AmXn");
			Assign("link", LINK, "0100111001010", "Xn");
			Assign("unlk", UNLK, "0100111001011", "Xn");
			Assign("nop", NOP, "0100111001110001");

			Assign("add", ADD0, "1101", "Xn", "0", "Size2_1", "AmXn");
			Assign("add", ADD1, "1101", "Xn", "1", "Size2_1", "AmXn");
			Assign("adda", ADDA, "1101", "Xn", "Size1", "11", "AmXn");
			Assign("addi", ADDI, "00000110", "Size2_1", "AmXn");
			Assign("addq", ADDQ, "0101", "Data3", "0", "Size2_1", "AmXn");
			Assign("sub", SUB0, "1001", "Xn", "0", "Size2_1", "AmXn");
			Assign("sub", SUB1, "1001", "Xn", "1", "Size2_1", "AmXn");
			Assign("suba", SUBA, "1001", "Xn", "Size1", "11", "AmXn");
			Assign("subi", SUBI, "00000100", "Size2_1", "AmXn");
			Assign("subq", SUBQ, "0101", "Data3", "1", "Size2_1", "AmXn");
			Assign("cmp", CMP, "1011", "Xn", "0", "Size2_1", "AmXn");
			Assign("cmpm", CMPM, "1011", "Xn", "1", "Size2_1", "001", "Xn");
			Assign("cmpa", CMPA, "1011", "Xn", "Size1", "11", "AmXn");
			Assign("cmpi", CMPI, "00001100", "Size2_1", "AmXn");
			Assign("mulu", MULU, "1100", "Xn", "011", "AmXn");  // TODO accurate timing
			Assign("muls", MULS, "1100", "Xn", "111", "AmXn");  // TODO accurate timing
			Assign("divu", DIVU, "1000", "Xn", "011", "AmXn");  // TODO accurate timing
			Assign("divs", DIVS, "1000", "Xn", "111", "AmXn");  // TODO accurate timing

			Assign("move2sr", MOVEtSR, "0100011011", "AmXn");
			Assign("movefsr", MOVEfSR, "0100000011", "AmXn");
			Assign("moveusp", MOVEUSP, "010011100110", "Data1", "Xn");
			Assign("andi2sr", ANDI_SR, "0000001001111100");
			Assign("eori2sr", EORI_SR, "0000101001111100");
			Assign("ori2sr", ORI_SR, "0000000001111100");
			Assign("moveccr", MOVECCR, "0100010011", "AmXn");
			Assign("trap", TRAP, "010011100100", "Data4");
		}

		void Assign(string instr, Action exec, string root, params string[] bitfield)
		{
			List<string> opList = new List<string>();
			opList.Add(root);

			foreach (var component in bitfield)
			{
				if (component.IsBinary()) AppendConstant(opList, component);
				else if (component == "Size1") opList = AppendPermutations(opList, Size1);
				else if (component == "Size2_0") opList = AppendPermutations(opList, Size2_0);
				else if (component == "Size2_1") opList = AppendPermutations(opList, Size2_1);
				else if (component == "XnAm") opList = AppendPermutations(opList, Xn3Am3);
				else if (component == "AmXn") opList = AppendPermutations(opList, Am3Xn3);
				else if (component == "Xn") opList = AppendPermutations(opList, Xn3);
				else if (component == "CondMain") opList = AppendPermutations(opList, ConditionMain);
				else if (component == "CondAll") opList = AppendPermutations(opList, ConditionAll);
				else if (component == "Data1") opList = AppendData(opList, 1);
				else if (component == "Data4") opList = AppendData(opList, 4);
				else if (component == "Data3") opList = AppendData(opList, 3);
				else if (component == "Data8") opList = AppendData(opList, 8);
			}

			foreach (var opcode in opList)
			{
				int opc = Convert.ToInt32(opcode, 2);
				if (Opcodes[opc] != null && instr.NotIn("movea", "andi2sr", "eori2sr", "ori2sr", "ext", "dbcc", "swap", "cmpm"))
					Console.WriteLine("Setting opcode for {0}, a handler is already set. overwriting. {1:X4}", instr, opc);
				Opcodes[opc] = exec;
			}
		}

		void AppendConstant(List<string> ops, string constant)
		{
			for (int i = 0; i < ops.Count; i++)
				ops[i] = ops[i] + constant;
		}

		List<string> AppendPermutations(List<string> ops, string[] permutations)
		{
			List<string> output = new List<string>();

			foreach (var input in ops)
				foreach (var perm in permutations)
					output.Add(input + perm);

			return output;
		}

		List<string> AppendData(List<string> ops, int bits)
		{
			List<string> output = new List<string>();

			foreach (var input in ops)
				for (int i = 0; i < BinaryExp(bits); i++)
					output.Add(input + Convert.ToString(i, 2).PadLeft(bits, '0'));

			return output;
		}

		int BinaryExp(int bits)
		{
			int res = 1;
			for (int i = 0; i < bits; i++)
				res *= 2;
			return res;
		}

		#region Tables

		static readonly string[] Size2_0 = { "01", "11", "10" };
		static readonly string[] Size2_1 = { "00", "01", "10" };
		static readonly string[] Size1 = { "0", "1" };
		static readonly string[] Xn3 = { "000", "001", "010", "011", "100", "101", "110", "111" };

		static readonly string[] Xn3Am3 = {
            "000000", // Dn   Data register
            "001000",
            "010000",
            "011000",
            "100000",
            "101000",
            "110000",
            "111000",

            "000001", // An    Address register
            "001001",
            "010001",
            "011001",
            "100001",
            "101001",
            "110001",
            "111001",

            "000010", // (An) Address
            "001010",
            "010010",
            "011010",
            "100010",
            "101010",
            "110010",
            "111010",

            "000011", // (An)+ Address with Postincrement
            "001011",
            "010011",
            "011011",
            "100011",
            "101011",
            "110011",
            "111011",

            "000100", // -(An) Address with Predecrement
            "001100",
            "010100",
            "011100",
            "100100",
            "101100",
            "110100",
            "111100",

            "000101", // (d16, An) Address with Displacement
            "001101",
            "010101",
            "011101",
            "100101",
            "101101",
            "110101",
            "111101",

            "000110", // (d8, An, Xn) Address with Index
            "001110",
            "010110",
            "011110",
            "100110",
            "101110",
            "110110",
            "111110",

            "010111", // (d16, PC)     PC with Displacement
            "011111", // (d8, PC, Xn)  PC with Index
            "000111", // (xxx).W       Absolute Short
            "001111", // (xxx).L       Absolute Long
            "100111", // #imm          Immediate            
        };

		static readonly string[] Am3Xn3 = {
            "000000", // Dn   Data register
            "000001",
            "000010",
            "000011",
            "000100",
            "000101",
            "000110",
            "000111",

            "001000", // An    Address register
            "001001",
            "001010",
            "001011",
            "001100",
            "001101",
            "001110",
            "001111",

            "010000", // (An) Address
            "010001",
            "010010",
            "010011",
            "010100",
            "010101",
            "010110",
            "010111",

            "011000", // (An)+ Address with Postincrement
            "011001",
            "011010",
            "011011",
            "011100",
            "011101",
            "011110",
            "011111",

            "100000", // -(An) Address with Predecrement
            "100001",
            "100010",
            "100011",
            "100100",
            "100101",
            "100110",
            "100111",

            "101000", // (d16, An) Address with Displacement
            "101001",
            "101010",
            "101011",
            "101100",
            "101101",
            "101110",
            "101111",

            "110000", // (d8, An, Xn) Address with Index
            "110001",
            "110010",
            "110011",
            "110100",
            "110101",
            "110110",
            "110111",

            "111010", // (d16, PC)     PC with Displacement
            "111011", // (d8, PC, Xn)  PC with Index
            "111000", // (xxx).W       Absolute Short
            "111001", // (xxx).L       Absolute Long
            "111100", // #imm          Immediate            
        };

		static readonly string[] ConditionMain = {
            "0010", // HI  Higher (unsigned)
            "0011", // LS  Lower or Same (unsigned)
            "0100", // CC  Carry Clear (aka Higher or Same, unsigned)
            "0101", // CS  Carry Set (aka Lower, unsigned)
            "0110", // NE  Not Equal
            "0111", // EQ  Equal
            "1000", // VC  Overflow Clear
            "1001", // VS  Overflow Set
            "1010", // PL  Plus
            "1011", // MI  Minus
            "1100", // GE  Greater or Equal (signed)
            "1101", // LT  Less Than (signed)
            "1110", // GT  Greater Than (signed)
            "1111"  // LE  Less or Equal (signed)
        };

		static readonly string[] ConditionAll = {
            "0000", // T   True 
            "0001", // F   False            
            "0010", // HI  Higher (unsigned)
            "0011", // LS  Lower or Same (unsigned)
            "0100", // CC  Carry Clear (aka Higher or Same, unsigned)
            "0101", // CS  Carry Set (aka Lower, unsigned)
            "0110", // NE  Not Equal
            "0111", // EQ  Equal
            "1000", // VC  Overflow Clear
            "1001", // VS  Overflow Set
            "1010", // PL  Plus
            "1011", // MI  Minus
            "1100", // GE  Greater or Equal (signed)
            "1101", // LT  Less Than (signed)
            "1110", // GT  Greater Than (signed)
            "1111"  // LE  Less or Equal (signed)
        };

		#endregion
	}
}