"""
Motorola 68k chip definition
"""

from .memory import Memory
from ..core.enum.register import Register, FULL_SIZE_REGISTERS, ALL_ADDRESS_REGISTERS
from ..core.enum.condition_status_code import ConditionStatusCode
from ..core.models.list_file import ListFile
import typing
import binascii
from ..core.models.memory_value import MemoryValue
from ..core.enum.op_size import OpSize

MAX_MEMORY_LOCATION = 16777216  # 2^24

class M68K:
    def __init__(self):
        """
        Constructor
        """
        self.memory = Memory()

        # has the simulation been halted using SIMHALT or .halt()
        self.halted = False

        # should the clock automatically cycle?
        self.clock_auto_cycle = True
        self._clock_cycles = 0

        # todo add events for each clock cycle
        # this is necessary for implementing breakpoints
        # and watches for value changes

        # set up the registers to their default values
        self.registers = {}
        self.__init_registers()

    def __init_registers(self):
        """
        Set the registers to their default values
        :return:
        """

        # loop through all of the full size registers which are just 32 bits / 4 bytes long
        for register in FULL_SIZE_REGISTERS:
            self.registers[register] = MemoryValue(OpSize.LONG)

        # set up all of the odd registers (in this case, just the Condition Code Register)
        # which just uses 5 bits out of the lowermost byte (do we want to allocate it an entire word instead?)
        self.registers[Register.ConditionCodeRegister] = MemoryValue(OpSize.BYTE)

        # Easy68k initializes the step counter (A7) to 0x1000000 by default, so do the same
        self.set_register(Register.A7, MemoryValue(OpSize.LONG, unsigned_int=0x1000000))

    def get_register(self, register: Register) -> MemoryValue:
        """
        Gets the entire value of a register
        :param register:
        :return:
        """
        return self.registers[register]

    def set_register(self, register: Register, val: MemoryValue):
        """
        Sets the value of a register using a 32-bit int
        :param register:
        :param val:
        :return:
        """
        # if the register is the CCR, use that method to handle setting it
        # because of its different size
        if register == Register.ConditionCodeRegister:
            self._set_condition_code_register_value(val)
            return

        # if the register is an address register that is limited to fit in the bounds of memory
        if register in ALL_ADDRESS_REGISTERS:
            self.set_address_register_value(register, val)
            return

        # now for all other registers
        # ensure that the value is within bounds
        # actual negative numbers will need to be converted into 32-bit numbers
        assert 0 <= val.get_value_unsigned() <= 0xFFFFFFFF, 'The value for registers must fit into 4 bytes!'

        # set the value
        self.registers[register] = val

    def _set_condition_code_register_value(self, val: MemoryValue):
        """
        Sets the value for the condition code register
        :param val:
        :return:
        """
        # ensure that the value is within bounds
        # since the CCR is just a single byte
        assert 0 <= val.get_value_unsigned() <= 0xFF, 'The value for the CCR must fit in a single byte!'

        # now set the value
        self.registers[Register.ConditionCodeRegister] = val


    def get_program_counter_value(self) -> int:
        """
        Gets the 32-bit unsigned integer value for the program counter value
        :return:
        """
        mv = self.get_register(Register.ProgramCounter)
        ret = mv.get_value_unsigned()
        return ret


    def set_address_register_value(self, reg: Register, new_value: MemoryValue):
        """
        Sets the value of an address register, so the PC or A0-A7
        :param reg:
        :param new_value:
        :return:
        """
        # no longer assert that the address register value is a pointer to memory
        # since address register direct modes don't consider the amount of memory
        assert reg in ALL_ADDRESS_REGISTERS, 'The register given is not an address register!'

        # now set the value of the register
        self.registers[reg].set_value_unsigned_int(new_value.get_value_unsigned())


    def set_program_counter_value(self, new_value: int):
        """
        Sets the value of the program counter
        Must be a non negative integer that is less than the maximum location size
        :param new_value:
        :return:
        """
        self.set_address_register_value(Register.ProgramCounter, MemoryValue(OpSize.LONG, unsigned_int=new_value))

    def increment_program_counter(self, inc: int):
        """
        Increments the program counter by the given value
        :param inc:
        :return:
        """
        self.set_program_counter_value(
            self.get_program_counter_value() + inc)

    def get_condition_status_code(self, code: ConditionStatusCode) -> bool:
        """
        Gets the status of a code from the Condition Code Register
        :param code:
        :return:
        """
        ccr = self.get_register(Register.CCR).get_value_unsigned()
        # ccr is only 1 byte, bit mask away the bit being looked for
        return (ccr & code) > 0

    def set_condition_status_code(self, code: ConditionStatusCode, value: bool):
        """
        Sets the status of a code from the Condition Code Register to value
        :param code:
        :return:
        """
        ccr = self.get_register(Register.CCR)
        v = ccr.get_value_unsigned()

        if value:
            v |= code
        else:
            v &= ~code

        self._set_condition_code_register_value(MemoryValue(OpSize.BYTE, unsigned_int=v))

    def run(self):
        """
        Starts the automatic execution
        :return:
        """
        if not self.halted:
            if not self.clock_auto_cycle:
                # run a single instruction
                self.step_instruction()
            else:
                while self.clock_auto_cycle:
                    self.step_instruction()

    def halt(self):
        """
        Halts the auto simulation execution
        :return:
        """
        self.clock_auto_cycle = False
        self.halted = True

    def step_instruction(self):
        """
        Increments the clock until the program
        counter increments
        :return:
        """
        if not self.halted:
            # must be here or we get circular dependency issues
            from ..core.util.find_module import find_opcode_cls, valid_opcodes

            for op_str in valid_opcodes:
                op_class = find_opcode_cls(op_str)

                # We don't know this opcode, there's no module for it
                if op_class is None:
                    print('Opcode {} is not known: skipping and continuing'.format(op_str))
                    assert False
                    continue

                # 10 comes from 2 bytes for the op and max 2 longs which are each 4 bytes
                # note: this currently has the edge case that it will fail unintelligibly
                # if encountered at the end of memory
                pc_val = self.get_program_counter_value()
                op = op_class.disassemble_instruction(self.memory.memory[pc_val:pc_val+10])
                if op is not None:
                    op.execute(self)
                    # done exeucting after doing an operation
                    return

    def reload_execution(self):
        """
        restarts execution of the program
        up to the current program counter location
        :return:
        """
        # get the current PC
        current_pc = self.get_program_counter_value()

        # reset the PC value
        # todo, need to store the starting location

        # set the starting PC value

        # run until hits that PC value

    def get_cycles(self):
        """
        Returns how many clock cycles have been performed
        :return:
        """
        return self._clock_cycles

    def clear_cycles(self):
        """
        Resets the count of clock cycles
        :return:
        """
        self._clock_cycles = 0

    def load_list_file(self, list_file: ListFile):
        """
        Load List File

        load the contents of a list file into memory
        using the locations specified inside of the list file
        :param list_file:
        :return:
        """
        self.memory.load_list_file(list_file)
        self.set_program_counter_value(int(list_file.starting_execution_address))

    def load_memory(self, file : typing.BinaryIO):
        """
        saves the raw memory into the designated file
        NOTE: file must be opened as binary or this won't work
        """
        self.memory.load_memory(file)

    def save_memory(self, file : typing.BinaryIO):
        """
        Loads the raw memory from the designated file
        This includes programs
        NOTE: file must be opened as binary or this won't work
        """
        self.memory.save_memory(file)


    def set_ccr_reg(self, extend, negative, zero, overflow, carry): 
        """
        Accepts Boolean values for X,N,Z,V, and C, respectively and sets the CCR accordingly.
        Passing None in for any argument will cause it to ignore that bit.
        Returns nothing.
        :param extend:
        :param negative:
        :param zero:
        :param overflow:
        :param carry:
        :return:
        """
        if extend is not None:
            extend = bool(extend)
            self.set_condition_status_code(ConditionStatusCode.X, extend)
            
        if negative is not None:
            negative = bool(negative)
            self.set_condition_status_code(ConditionStatusCode.N, negative)
            
        if zero is not None:
            zero = bool(zero)
            self.set_condition_status_code(ConditionStatusCode.Z, zero)
            
        if overflow is not None:
            overflow = bool(overflow)
            self.set_condition_status_code(ConditionStatusCode.V, overflow)
            
        if carry is not None:
            carry = bool(carry)
            self.set_condition_status_code(ConditionStatusCode.C, carry)
    

"""
Motorola 68k chip definition
"""

from .memory import Memory
from ..core.enum.register import Register, FULL_SIZE_REGISTERS, ALL_ADDRESS_REGISTERS
from ..core.enum.condition_status_code import ConditionStatusCode
from ..core.models.list_file import ListFile
import typing
import binascii
from ..core.models.memory_value import MemoryValue
from ..core.enum.op_size import OpSize

MAX_MEMORY_LOCATION = 16777216  # 2^24

class M68K:
    def __init__(self):
        """
        Constructor
        """
        self.memory = Memory()

        # has the simulation been halted using SIMHALT or .halt()
        self.halted = False

        # should the clock automatically cycle?
        self.clock_auto_cycle = True
        self._clock_cycles = 0

        # todo add events for each clock cycle
        # this is necessary for implementing breakpoints
        # and watches for value changes

        # set up the registers to their default values
        self.registers = {}
        self.__init_registers()

    def __init_registers(self):
        """
        Set the registers to their default values
        :return:
        """

        # loop through all of the full size registers which are just 32 bits / 4 bytes long
        for register in FULL_SIZE_REGISTERS:
            self.registers[register] = MemoryValue(OpSize.LONG)

        # set up all of the odd registers (in this case, just the Condition Code Register)
        # which just uses 5 bits out of the lowermost byte (do we want to allocate it an entire word instead?)
        self.registers[Register.ConditionCodeRegister] = MemoryValue(OpSize.BYTE)

        # Easy68k initializes the step counter (A7) to 0x1000000 by default, so do the same
        self.set_register(Register.A7, MemoryValue(OpSize.LONG, unsigned_int=0x1000000))

    def get_register(self, register: Register) -> MemoryValue:
        """
        Gets the entire value of a register
        :param register:
        :return:
        """
        return self.registers[register]

    def set_register(self, register: Register, val: MemoryValue):
        """
        Sets the value of a register using a 32-bit int
        :param register:
        :param val:
        :return:
        """
        # if the register is the CCR, use that method to handle setting it
        # because of its different size
        if register == Register.ConditionCodeRegister:
            self._set_condition_code_register_value(val)
            return

        # if the register is an address register that is limited to fit in the bounds of memory
        if register in ALL_ADDRESS_REGISTERS:
            self.set_address_register_value(register, val)
            return

        # now for all other registers
        # ensure that the value is within bounds
        # actual negative numbers will need to be converted into 32-bit numbers
        assert 0 <= val.get_value_unsigned() <= 0xFFFFFFFF, 'The value for registers must fit into 4 bytes!'

        # set the value
        self.registers[register] = val

    def _set_condition_code_register_value(self, val: MemoryValue):
        """
        Sets the value for the condition code register
        :param val:
        :return:
        """
        # ensure that the value is within bounds
        # since the CCR is just a single byte
        assert 0 <= val.get_value_unsigned() <= 0xFF, 'The value for the CCR must fit in a single byte!'

        # now set the value
        self.registers[Register.ConditionCodeRegister] = val


    def get_program_counter_value(self) -> int:
        """
        Gets the 32-bit unsigned integer value for the program counter value
        :return:
        """
        mv = self.get_register(Register.ProgramCounter)
        ret = mv.get_value_unsigned()
        return ret


    def set_address_register_value(self, reg: Register, new_value: MemoryValue):
        """
        Sets the value of an address register, so the PC or A0-A7
        :param reg:
        :param new_value:
        :return:
        """
        # no longer assert that the address register value is a pointer to memory
        # since address register direct modes don't consider the amount of memory
        assert reg in ALL_ADDRESS_REGISTERS, 'The register given is not an address register!'

        # now set the value of the register
        self.registers[reg].set_value_unsigned_int(new_value.get_value_unsigned())


    def set_program_counter_value(self, new_value: int):
        """
        Sets the value of the program counter
        Must be a non negative integer that is less than the maximum location size
        :param new_value:
        :return:
        """
        self.set_address_register_value(Register.ProgramCounter, MemoryValue(OpSize.LONG, unsigned_int=new_value))

    def increment_program_counter(self, inc: int):
        """
        Increments the program counter by the given value
        :param inc:
        :return:
        """
        self.set_program_counter_value(
            self.get_program_counter_value() + inc)

    def get_condition_status_code(self, code: ConditionStatusCode) -> bool:
        """
        Gets the status of a code from the Condition Code Register
        :param code:
        :return:
        """
        ccr = self.get_register(Register.CCR).get_value_unsigned()
        # ccr is only 1 byte, bit mask away the bit being looked for
        return (ccr & code) > 0

    def set_condition_status_code(self, code: ConditionStatusCode, value: bool):
        """
        Sets the status of a code from the Condition Code Register to value
        :param code:
        :return:
        """
        ccr = self.get_register(Register.CCR)
        v = ccr.get_value_unsigned()

        if value:
            v |= code
        else:
            v &= ~code

        self._set_condition_code_register_value(MemoryValue(OpSize.BYTE, unsigned_int=v))

    def run(self):
        """
        Starts the automatic execution
        :return:
        """
        if not self.halted:
            if not self.clock_auto_cycle:
                # run a single instruction
                self.step_instruction()
            else:
                while self.clock_auto_cycle:
                    self.step_instruction()

    def halt(self):
        """
        Halts the auto simulation execution
        :return:
        """
        self.clock_auto_cycle = False
        self.halted = True

    def step_instruction(self):
        """
        Increments the clock until the program
        counter increments
        :return:
        """
        if not self.halted:
            # must be here or we get circular dependency issues
            from ..core.util.find_module import find_opcode_cls, valid_opcodes

            for op_str in valid_opcodes:
                op_class = find_opcode_cls(op_str)

                # We don't know this opcode, there's no module for it
                if op_class is None:
                    print('Opcode {} is not known: skipping and continuing'.format(op_str))
                    assert False
                    continue

                # 10 comes from 2 bytes for the op and max 2 longs which are each 4 bytes
                # note: this currently has the edge case that it will fail unintelligibly
                # if encountered at the end of memory
                pc_val = self.get_program_counter_value()
                op = op_class.disassemble_instruction(self.memory.memory[pc_val:pc_val+10])
                if op is not None:
                    op.execute(self)
                    # done exeucting after doing an operation
                    return

    def reload_execution(self):
        """
        restarts execution of the program
        up to the current program counter location
        :return:
        """
        # get the current PC
        current_pc = self.get_program_counter_value()

        # reset the PC value
        # todo, need to store the starting location

        # set the starting PC value

        # run until hits that PC value

    def get_cycles(self):
        """
        Returns how many clock cycles have been performed
        :return:
        """
        return self._clock_cycles

    def clear_cycles(self):
        """
        Resets the count of clock cycles
        :return:
        """
        self._clock_cycles = 0

    def load_list_file(self, list_file: ListFile):
        """
        Load List File

        load the contents of a list file into memory
        using the locations specified inside of the list file
        :param list_file:
        :return:
        """
        self.memory.load_list_file(list_file)
        self.set_program_counter_value(int(list_file.starting_execution_address))

    def load_memory(self, file : typing.BinaryIO):
        """
        saves the raw memory into the designated file
        NOTE: file must be opened as binary or this won't work
        """
        self.memory.load_memory(file)

    def save_memory(self, file : typing.BinaryIO):
        """
        Loads the raw memory from the designated file
        This includes programs
        NOTE: file must be opened as binary or this won't work
        """
        self.memory.save_memory(file)


    def set_ccr_reg(self, extend, negative, zero, overflow, carry): 
        """
        Accepts Boolean values for X,N,Z,V, and C, respectively and sets the CCR accordingly.
        Passing None in for any argument will cause it to ignore that bit.
        Returns nothing.
        :param extend:
        :param negative:
        :param zero:
        :param overflow:
        :param carry:
        :return:
        """
        if extend is not None:
            extend = bool(extend)
            self.set_condition_status_code(ConditionStatusCode.X, extend)
            
        if negative is not None:
            negative = bool(negative)
            self.set_condition_status_code(ConditionStatusCode.N, negative)
            
        if zero is not None:
            zero = bool(zero)
            self.set_condition_status_code(ConditionStatusCode.Z, zero)
            
        if overflow is not None:
            overflow = bool(overflow)
            self.set_condition_status_code(ConditionStatusCode.V, overflow)
            
        if carry is not None:
            carry = bool(carry)
            self.set_condition_status_code(ConditionStatusCode.C, carry)
    

Motorola 68k chip definition # 2^24 Constructor # has the simulation been halted using SIMHALT or .halt() # should the clock automatically cycle? # todo add events for each clock cycle # this is necessary for implementing breakpoints # and watches for value changes # set up the registers to their default values Set the registers to their default values         :return: # loop through all of the full size registers which are just 32 bits / 4 bytes long # set up all of the odd registers (in this case, just the Condition Code Register) # which just uses 5 bits out of the lowermost byte (do we want to allocate it an entire word instead?) # Easy68k initializes the step counter (A7) to 0x1000000 by default, so do the same Gets the entire value of a register         :param register:         :return: Sets the value of a register using a 32-bit int         :param register:         :param val:         :return: # if the register is the CCR, use that method to handle setting it # because of its different size # if the register is an address register that is limited to fit in the bounds of memory # now for all other registers # ensure that the value is within bounds # actual negative numbers will need to be converted into 32-bit numbers # set the value Sets the value for the condition code register         :param val:         :return: # ensure that the value is within bounds # since the CCR is just a single byte # now set the value Gets the 32-bit unsigned integer value for the program counter value         :return: Sets the value of an address register, so the PC or A0-A7         :param reg:         :param new_value:         :return: # no longer assert that the address register value is a pointer to memory # since address register direct modes don't consider the amount of memory # now set the value of the register Sets the value of the program counter         Must be a non negative integer that is less than the maximum location size         :param new_value:         :return: Increments the program counter by the given value         :param inc:         :return: Gets the status of a code from the Condition Code Register         :param code:         :return: # ccr is only 1 byte, bit mask away the bit being looked for Sets the status of a code from the Condition Code Register to value         :param code:         :return: Starts the automatic execution         :return: # run a single instruction Halts the auto simulation execution         :return: Increments the clock until the program         counter increments         :return: # must be here or we get circular dependency issues # We don't know this opcode, there's no module for it # 10 comes from 2 bytes for the op and max 2 longs which are each 4 bytes # note: this currently has the edge case that it will fail unintelligibly # if encountered at the end of memory # done exeucting after doing an operation restarts execution of the program         up to the current program counter location         :return: # get the current PC # reset the PC value # todo, need to store the starting location # set the starting PC value # run until hits that PC value Returns how many clock cycles have been performed         :return: Resets the count of clock cycles         :return: Load List File          load the contents of a list file into memory         using the locations specified inside of the list file         :param list_file:         :return: saves the raw memory into the designated file         NOTE: file must be opened as binary or this won't work Loads the raw memory from the designated file         This includes programs         NOTE: file must be opened as binary or this won't work Accepts Boolean values for X,N,Z,V, and C, respectively and sets the CCR accordingly.         Passing None in for any argument will cause it to ignore that bit.         Returns nothing.         :param extend:         :param negative:         :param zero:         :param overflow:         :param carry:         :return: