class OpCollection:
    def __init__(self, cpu):
        self.cpu = cpu
        self.opcode_dict = {}
        self.collect_ops()

    def get_opcode_dict(self):
        return self.opcode_dict

    def collect_ops(self):
        # Get the members not from class instance, so that's possible to
        # exclude properties without "activate" them.
        cls = type(self.cpu)
        for name, cls_method in inspect.getmembers(cls):
            if name.startswith("_") or isinstance(cls_method, property):
                continue

            try:
                opcodes = getattr(cls_method, "_opcodes")
            except AttributeError:
                continue

            instr_func = getattr(self.cpu, name)
            self._add_ops(opcodes, instr_func)

    def _add_ops(self, opcodes, instr_func):
        #         log.debug("%20s: %s" % (
        #             instr_func.__name__, ",".join(["$%x" % c for c in opcodes])
        #         ))
        for op_code in opcodes:
            assert op_code not in self.opcode_dict, \
                f"Opcode ${op_code:x} ({instr_func.__name__}) defined more then one time!"

            op_code_data = MC6809OP_DATA_DICT[op_code]

            func_name = func_name_from_op_code(op_code)

            if self.cpu.cfg.trace:
                InstructionClass = InstructionTrace
            else:
                InstructionClass = PrepagedInstructions

            instrution_class = InstructionClass(self.cpu, instr_func)
            try:
                func = getattr(instrution_class, func_name)
            except AttributeError as err:
                raise AttributeError(f"{err} (op code: ${op_code:02x})")

            self.opcode_dict[op_code] = (op_code_data["cycles"], func) def opcode(*opcodes):
    """A decorator for opcodes"""
    def decorator(func):
        setattr(func, "_is_opcode", True)
        setattr(func, "_opcodes", opcodes)
        return func
    return decorator class ConcatenatedAccumulator:
    """
    6809 has register D - 16 bit concatenated reg. (A + B)
    """
    WIDTH = 16  # 16 Bit

    def __init__(self, name, a, b):
        self.name = name
        self._a = a
        self._b = b

    def set(self, value):
        self._a.set(value >> 8)
        self._b.set(value & 0xff)

    @property
    def value(self):
        return self._a.value * 256 + self._b.value

    def __str__(self):
        return f"{self.name}={self.value:04x}" class UndefinedRegister(ValueStorageBase):
    # used in TFR and EXG
    WIDTH = 16  # 16 Bit
    name = "undefined!"
    value = 0xffff

    def __init__(self):
        pass

    def set(self, v):
        log.warning("Set value to 'undefined' register!")

    def get(self):
        return 0xffff class ValueStorage8Bit(ValueStorageBase):
    WIDTH = 8  # 8 Bit

    def set(self, v):
        if v > 0xff:
            #            log.info(" **** Value $%x is to big for %s (8-bit)", v, self.name)
            v = v & 0xff
#            log.info(" ^^^^ Value %s (8-bit) wrap around to $%x", self.name, v)
        elif v < 0:
            #            log.info(" **** %s value $%x is negative", self.name, v)
            v = 0x100 + v
#            log.info(" **** Value %s (8-bit) wrap around to $%x", self.name, v)
        self.value = v

    def __str__(self):
        return f"{self.name}={self.value:02x}" class ValueStorage16Bit(ValueStorageBase):
    WIDTH = 16  # 16 Bit

    def set(self, v):
        if v > 0xffff:
            #            log.info(" **** Value $%x is to big for %s (16-bit)", v, self.name)
            v = v & 0xffff
#            log.info(" ^^^^ Value %s (16-bit) wrap around to $%x", self.name, v)
        elif v < 0:
            #            log.info(" **** %s value $%x is negative", self.name, v)
            v = 0x10000 + v
#            log.info(" **** Value %s (16-bit) wrap around to $%x", self.name, v)
        self.value = v

    def __str__(self):
        return f"{self.name}={self.value:04x}" def convert_differend_width(src_reg, dst_reg):
    """
    e.g.:
     8bit   $cd TFR into 16bit, results in: $ffcd
    16bit $1234 TFR into  8bit, results in:   $34

    >>> reg8 = ValueStorage8Bit(name="bar", initial_value=0xcd)
    >>> reg16 = ValueStorage16Bit(name="foo", initial_value=0x0000)
    >>> hex(convert_differend_width(src_reg=reg8, dst_reg=reg16))
    '0xffcd'

    >>> reg16 = ValueStorage16Bit(name="foo", initial_value=0x1234)
    >>> reg8 = ValueStorage8Bit(name="bar", initial_value=0xcd)
    >>> hex(convert_differend_width(src_reg=reg16, dst_reg=reg8))
    '0x34'

    TODO: verify this behaviour on real hardware
    see: http://archive.worldofdragon.org/phpBB3/viewtopic.php?f=8&t=4886
    """
    src_value = src_reg.value
    if src_reg.WIDTH == 8 and dst_reg.WIDTH == 16:
        # e.g.: $cd -> $ffcd
        src_value += 0xff00
    elif src_reg.WIDTH == 16 and dst_reg.WIDTH == 8:
        # This not not really needed, because all 8Bit register will
        # limit the value, too.
        # e.g.: $1234 -> $34
        src_value = src_value & 0xff
    return src_value def calc_new_count(min_value, value, max_value, trigger, target):
    """
    change 'value' between 'min_value' and 'max_value'
    so that 'trigger' will be match 'target'

    >>> calc_new_count(min_value=0, value=100, max_value=200, trigger=30, target=30)
    100

    >>> calc_new_count(min_value=0, value=100, max_value=200, trigger=50, target=5)
    55
    >>> calc_new_count(min_value=60, value=100, max_value=200, trigger=50, target=5)
    60

    >>> calc_new_count(min_value=0, value=100, max_value=200, trigger=20, target=40)
    150
    >>> calc_new_count(min_value=0, value=100, max_value=125, trigger=20, target=40)
    125
    """
    try:
        new_value = float(value) / float(trigger) * target
    except ZeroDivisionError:
        return value * 2

    if new_value > max_value:
        return max_value

    new_value = int((value + new_value) / 2)
    if new_value < min_value:
        return min_value
    return new_value REG_A = "A" REG_B = "B" REG_CC = "CC" REG_D = "D" REG_DP = "DP" REG_PC = "PC" REG_S = "S" REG_U = "U" REG_X = "X" REG_Y = "Y"
import logging
import sys
import time
from MC6809.components.cpu_utils.instruction_caller import OpCollection, opcode
from MC6809.components.cpu_utils.MC6809_registers import (
    ConcatenatedAccumulator,
    UndefinedRegister,
    ValueStorage8Bit,
    ValueStorage16Bit,
    convert_differend_width,
)
from MC6809.components.mc6809_tools import calc_new_count
from MC6809.components.MC6809data.MC6809_op_data import (
    REG_A,
    REG_B,
    REG_CC,
    REG_D,
    REG_DP,
    REG_PC,
    REG_S,
    REG_U,
    REG_X,
    REG_Y,
)

log = logging.getLogger("MC6809")


# HTML_TRACE = True
HTML_TRACE = False

undefined_reg = UndefinedRegister()


class CPUBase:

    SWI3_VECTOR = 0xfff2
    SWI2_VECTOR = 0xfff4
    FIRQ_VECTOR = 0xfff6
    IRQ_VECTOR = 0xfff8
    SWI_VECTOR = 0xfffa
    NMI_VECTOR = 0xfffc
    RESET_VECTOR = 0xfffe

    STARTUP_BURST_COUNT = 100
    min_burst_count = 10  # minimum outer op count per burst
    max_burst_count = 10000  # maximum outer op count per burst

    def __init__(self, memory, cfg):
        self.memory = memory
        self.memory.cpu = self  # FIXME
        self.cfg = cfg

        self.running = True
        self.cycles = 0
        self.last_op_address = 0  # Store the current run opcode memory address
        self.outer_burst_op_count = self.STARTUP_BURST_COUNT

        # start_http_control_server(self, cfg) # TODO: Move into seperate Class

        self.index_x = ValueStorage16Bit(REG_X, 0)  # X - 16 bit index register
        self.index_y = ValueStorage16Bit(REG_Y, 0)  # Y - 16 bit index register

        self.user_stack_pointer = ValueStorage16Bit(REG_U, 0)  # U - 16 bit user-stack pointer
        self.user_stack_pointer.counter = 0

        # S - 16 bit system-stack pointer:
        # Position will be set by ROM code after detection of total installed RAM
        self.system_stack_pointer = ValueStorage16Bit(REG_S, 0)

        # PC - 16 bit program counter register
        self.program_counter = ValueStorage16Bit(REG_PC, 0)

        self.accu_a = ValueStorage8Bit(REG_A, 0)  # A - 8 bit accumulator
        self.accu_b = ValueStorage8Bit(REG_B, 0)  # B - 8 bit accumulator

        # D - 16 bit concatenated reg. (A + B)
        self.accu_d = ConcatenatedAccumulator(REG_D, self.accu_a, self.accu_b)

        # DP - 8 bit direct page register
        self.direct_page = ValueStorage8Bit(REG_DP, 0)

        super().__init__()

        self.register_str2object = {
            REG_X: self.index_x,
            REG_Y: self.index_y,

            REG_U: self.user_stack_pointer,
            REG_S: self.system_stack_pointer,

            REG_PC: self.program_counter,

            REG_A: self.accu_a,
            REG_B: self.accu_b,
            REG_D: self.accu_d,

            REG_DP: self.direct_page,
            REG_CC: self.cc_register,

            undefined_reg.name: undefined_reg,  # for TFR, EXG
        }

#         log.debug("Add opcode functions:")
        self.opcode_dict = OpCollection(self).get_opcode_dict()

#         log.debug("illegal ops: %s" % ",".join(["$%x" % c for c in ILLEGAL_OPS]))
        # add illegal instruction
#         for opcode in ILLEGAL_OPS:
#             self.opcode_dict[opcode] = IllegalInstruction(self, opcode)

    def get_state(self):
        """
        used in unittests
        """
        return {
            REG_X: self.index_x.value,
            REG_Y: self.index_y.value,

            REG_U: self.user_stack_pointer.value,
            REG_S: self.system_stack_pointer.value,

            REG_PC: self.program_counter.value,

            REG_A: self.accu_a.value,
            REG_B: self.accu_b.value,

            REG_DP: self.direct_page.value,
            REG_CC: self.get_cc_value(),

            "cycles": self.cycles,
            "RAM": tuple(self.memory._mem)  # copy of array.array() values,
        }

    def set_state(self, state):
        """
        used in unittests
        """
        self.index_x.set(state[REG_X])
        self.index_y.set(state[REG_Y])

        self.user_stack_pointer.set(state[REG_U])
        self.system_stack_pointer.set(state[REG_S])


        self.program_counter.set(state[REG_PC])