"""
Meta code that generates the run_instr function for the MOS6502 from the instruction tables.  This replaced a possibly
nicer pythonic way of doing the instruction decoding that turned out to be slow and not very compatible with Cython
optimization.

Run this file and copy its output to the end of mos6502.pyx
"""

from .instructions import INSTRUCTION_SET, AddressModes


def generate_cpu_instruction_table(tab_level = 1, spaces_per_tab=4):
    first_code = True
    indent = " " * tab_level * spaces_per_tab
    print(indent + "cdef int run_instr(self, unsigned char opcode, unsigned char data[2]):")
    tab_level += 1
    indent = " " * tab_level * spaces_per_tab
    print(indent + "# *********************************************************************** #")
    print(indent + "# **          Autogenerated by meta.py - Do not edit directly          ** #")
    print(indent + "# *********************************************************************** #")
    print(indent)
    print(indent + "cdef int cycles=0, arg, immediate=False, m")

    for _, instr_set in INSTRUCTION_SET.items():
        for mode, instr in instr_set.modes.items():
            if type(instr.bytecode) == list:
                opcodes = instr.bytecode
            else:
                opcodes = [instr.bytecode]

            print(indent + "{}if opcode==".format("" if first_code else "el"), end="")
            for i, opcode in enumerate(opcodes):
                print("0x{:02X}".format(opcode), end="")
                if i < len(opcodes) - 1:
                    print(" or opcode==", end="")
                else:
                    print(":")
            if first_code:
                first_code = False
            tab_level += 1
            indent = " " * tab_level * spaces_per_tab
            print(indent + "cycles = {}".format(int(instr.cycles)))
            print(generate_addressing(indent, mode, instr.cycles))
            print(indent + "cycles += self._{}(arg, immediate)".format(instr_set.name))
            tab_level -= 1
            indent = " " * tab_level * spaces_per_tab
    print(indent)
    print(indent + "return cycles")


def generate_addressing(indent, mode, cycles):
    cde = indent
    if mode == AddressModes.IMPLIED:
        # all implied instructions are self-contained 1-byte instructions
        cde += "arg = ARG_NONE"
    elif mode == AddressModes.ACCUMULATOR:
        # the instructions with accumulator targets usually have a memory address as a target, if
        # None is supplied, they target the accumulator
        cde += "arg = ARG_NONE"
    elif mode == AddressModes.RELATIVE:
        # all relative instructions are control flow instructions that take a relative address
        print(indent + "arg = data[0]")
    elif mode == AddressModes.IMMEDIATE:
        cde += "arg = data[0]\n"
        cde += indent + "immediate = True"
    else:
        # memory loading modes:
        #cde += "address = ARG_NONE\n" + indent
        # arg is an address
        if mode == AddressModes.ZEROPAGE:
            cde += "arg = data[0]"
        elif mode == AddressModes.ZEROPAGE_X:
            cde += "arg = (data[0] + self.X) & 0xFF"
        elif mode == AddressModes.ZEROPAGE_Y:
            cde += "arg = (data[0] + self.Y) & 0xFF"
        elif mode == AddressModes.ABSOLUTE:
            cde += "arg = self._from_le(data)"
        elif mode == AddressModes.ABSOLUTE_X:
            cde += "arg = (self._from_le(data) + self.X) & 0xFFFF"
            if cycles > int(cycles):
                cde += "\n" + indent + "cycles += 1 if (data[LO_BYTE] + self.X) > 0xFF else 0"
        elif mode == AddressModes.ABSOLUTE_Y:
            cde += "arg = (self._from_le(data) + self.Y) & 0xFFFF"
            if cycles > int(cycles):
                cde += "\n" + indent + "cycles += 1 if (data[LO_BYTE] + self.Y) > 0xFF else 0"
        elif mode == AddressModes.INDIRECT:
            # only used for jmp instructions
            # has the jump indirect bug [12] which means it cannot cross page boundaries and instead
            # wraps around, e.g. read from 0x12ff reads 0x12ff and 0x1200
            # address =
            cde += "arg = self._read_word(self._from_le(data), wrap_at_page=True)"
            # address = self._from_le(self.memory.read_block(self._from_le(data), bytes=2))
        elif mode == AddressModes.INDIRECT_X:
            cde += "arg = self._read_word((data[0] + self.X) & 0xFF, wrap_at_page=True)"
        elif mode == AddressModes.INDIRECT_Y:
            cde += "m = self._read_word(data[0], wrap_at_page=True)\n"
            cde += indent + "arg = (m + self.Y) & 0xFFFF"
            if cycles > int(cycles):
                cde += "\n" + indent + "cycles += 1 if ((m & 0xFF) + self.Y) > 0xFF else 0"
    return cde


generate_cpu_instruction_table(tab_level=1, spaces_per_tab=4)