class CPU:
    """
    ARM7TDMI, a 32-bit RISC processor. It has a 3 stage instruction pipeline
    (fetch, decode, execute).

    The ARM7TDMI can operate in one of two different CPU states:
     - ARM, which uses a full instruction set with 32-bit opcodes
     - THUMB, which uses a cut-down instruction set with 16-bit opcodes

    https://problemkaputt.de/gbatek.htm#armcpureference
    """

    def __init__(self, scheduler: Scheduler, memory: Memory):
        self.scheduler = scheduler
        self.regs = Registers()
        self.regs.cpsr.set_mode(CPUMode.SYSTEM)
        self.regs.spsr.set_mode(CPUMode.SYSTEM)
        self.memory = memory
        self.memory.cpu = self
        self.pipeline = [4026531840, 4026531840]
        self.next_fetch_access = MemoryAccess.NON_SEQUENTIAL

    def step(self):
        irq_line = self.memory.io.interrupt_controller.irq_line
        if irq_line and (not self.regs.cpsr.get_irq_disable()):
            self.interrupt(ExceptionVector.ExceptionVector_IRQ)
        power_down_mode = self.memory.io.interrupt_controller.power_down_mode
        if power_down_mode == PowerDownMode.NONE:
            if self.regs.cpsr.get_state() == CPUState.ARM:
                self.step_arm()
            else:
                self.step_thumb()
        elif power_down_mode == PowerDownMode.HALT:
            self.scheduler.idle_until_next_event()

    def step_arm(self):
        instruction = self.pipeline[0]
        self.pipeline[0] = self.pipeline[1]
        self.pipeline[1] = self.memory.read_32(self.regs.get_pc(), self.next_fetch_access)
        instruction_handler = self.arm_decoder(instruction)
        cond = get_bits(instruction, 28, 31)
        if self.check_condition(cond):
            instruction_handler(self, instruction)
        else:
            self.advance_pc_arm()
            self.next_fetch_access = MemoryAccess.SEQUENTIAL

    def step_thumb(self):
        instruction = self.pipeline[0]
        self.pipeline[0] = self.pipeline[1]
        self.pipeline[1] = self.memory.read_16(self.regs.get_pc(), self.next_fetch_access)
        instruction_handler = self.thumb_decoder(instruction)
        instruction_handler(self, instruction)

    def advance_pc_arm(self):
        self.regs.set_pc(add_32(self.regs.get_pc(), ARM_PC_INCREMENT))

    def advance_pc_thumb(self):
        self.regs.set_pc(add_32(self.regs.get_pc(), THUMB_PC_INCREMENT))

    def flush_pipeline(self):
        if self.regs.cpsr.get_state() == CPUState.ARM:
            self.regs.set_pc(self.regs.get_pc() & ~3)
            self.pipeline[0] = self.memory.read_32(self.regs.get_pc(), MemoryAccess.NON_SEQUENTIAL)
            self.advance_pc_arm()
            self.pipeline[1] = self.memory.read_32(self.regs.get_pc(), MemoryAccess.SEQUENTIAL)
            self.advance_pc_arm()
        else:
            self.regs.set_pc(self.regs.get_pc() & ~1)
            self.pipeline[0] = self.memory.read_16(self.regs.get_pc(), MemoryAccess.NON_SEQUENTIAL)
            self.advance_pc_thumb()
            self.pipeline[1] = self.memory.read_16(self.regs.get_pc(), MemoryAccess.SEQUENTIAL)
            self.advance_pc_thumb()
        self.next_fetch_access = MemoryAccess.SEQUENTIAL

    def switch_mode(self, new_mode: CPUMode):
        self.regs.switch_mode(new_mode)

    def interrupt(self, vector: ExceptionVector):
        if vector == ExceptionVector.RESET:
            new_mode = CPUMode.SWI
        elif vector == ExceptionVector.UNDEFINED_INSTRUCTION:
            new_mode = CPUMode.UNDEFINED
        elif vector == ExceptionVector.ExceptionVector_SWI:
            new_mode = CPUMode.SWI
        elif vector == ExceptionVector.PREFETCH_ABORT:
            new_mode = CPUMode.ABORT
        elif vector == ExceptionVector.DATA_ABORT:
            new_mode = CPUMode.ABORT
        elif vector == ExceptionVector.ADDRESS_EXCEEDS_26_BITS:
            new_mode = CPUMode.SWI
        elif vector == ExceptionVector.ExceptionVector_IRQ:
            new_mode = CPUMode.IRQ
        elif vector == ExceptionVector.ExceptionVector_FIQ:
            new_mode = CPUMode.FIQ
        else:
            return
        cpsr_reg = self.regs.cpsr.reg
        self.switch_mode(new_mode)
        self.regs.spsr.reg = cpsr_reg
        if vector == ExceptionVector.ExceptionVector_SWI or vector == ExceptionVector.UNDEFINED_INSTRUCTION:
            self.regs.set_lr(add_32(self.regs.get_pc(), -4 if self.regs.cpsr.get_state() == CPUState.ARM else -2))
        elif vector != ExceptionVector.RESET:
            self.regs.set_lr(add_32(self.regs.get_pc(), -4 if self.regs.cpsr.get_state() == CPUState.ARM else 0))
        self.regs.cpsr.set_state(CPUState.ARM)
        self.regs.cpsr.set_irq_disable(True)
        if vector in [ExceptionVector.RESET, ExceptionVector.ExceptionVector_FIQ]:
            self.regs.cpsr.set_fiq_disable(True)
        self.regs.set_pc(vector)
        self.flush_pipeline()

    def check_condition(self, cond: Condition | int) -> bint:
        if cond == Condition.EQ:
            return self.regs.cpsr.get_zero_flag()
        elif cond == Condition.NE:
            return not self.regs.cpsr.get_zero_flag()
        elif cond == Condition.HS:
            return self.regs.cpsr.get_carry_flag()
        elif cond == Condition.LO:
            return not self.regs.cpsr.get_carry_flag()
        elif cond == Condition.MI:
            return self.regs.cpsr.get_sign_flag()
        elif cond == Condition.PL:
            return not self.regs.cpsr.get_sign_flag()
        elif cond == Condition.VS:
            return self.regs.cpsr.get_overflow_flag()
        elif cond == Condition.VC:
            return not self.regs.cpsr.get_overflow_flag()
        elif cond == Condition.HI:
            return self.regs.cpsr.get_carry_flag() and (not self.regs.cpsr.get_zero_flag())
        elif cond == Condition.LS:
            return not self.regs.cpsr.get_carry_flag() or self.regs.cpsr.get_zero_flag()
        elif cond == Condition.GE:
            return self.regs.cpsr.get_sign_flag() == self.regs.cpsr.get_overflow_flag()
        elif cond == Condition.LT:
            return self.regs.cpsr.get_sign_flag() != self.regs.cpsr.get_overflow_flag()
        elif cond == Condition.GT:
            return not self.regs.cpsr.get_zero_flag() and self.regs.cpsr.get_sign_flag() == self.regs.cpsr.get_overflow_flag()
        elif cond == Condition.LE:
            return self.regs.cpsr.get_zero_flag() or self.regs.cpsr.get_sign_flag() != self.regs.cpsr.get_overflow_flag()
        elif cond == Condition.AL:
            return True
        else:
            return False

    def decode_and_compute_shift(self, value: int, shift: int) -> tuple[int, bint]:
        immediate = not get_bit(shift, 0)
        if immediate:
            shift_amount = get_bits(shift, 3, 7)
        else:
            shift_reg = get_bits(shift, 4, 7)
            shift_amount = self.regs.get(shift_reg) & 255
        shift_type = get_bits(shift, 1, 2)
        return self.compute_shift(value, shift_type, shift_amount, immediate)

    def compute_shift(self, value: int, shift_type: ShiftType | int, shift_amount: int, immediate: bint) -> tuple[int, bint]:
        if not immediate and shift_amount == 0:
            return (value, self.regs.cpsr.get_carry_flag())
        result = value
        carry_out = False
        if shift_type == ShiftType.LSL:
            if shift_amount == 0:
                carry_out = self.regs.cpsr.get_carry_flag()
            elif shift_amount < 32:
                carry_out = get_bit(value, 32 - shift_amount)
                mask = 4294967295
                result = value << shift_amount & mask
            elif shift_amount == 32:
                carry_out = get_bit(value, 0)
                result = 0
            else:
                carry_out = False
                result = 0
        elif shift_type == ShiftType.LSR:
            if shift_amount == 0:
                carry_out = get_bit(value, 31)
                result = 0
            elif shift_amount < 32:
                carry_out = get_bit(value, shift_amount - 1)
                result = value >> shift_amount
            elif shift_amount == 32:
                carry_out = get_bit(value, 31)
                result = 0
            else:
                carry_out = False
                result = 0
        elif shift_type == ShiftType.ASR:
            if shift_amount == 0 or shift_amount >= 32:
                carry_out = get_bit(value, 31)
                result = 4294967295 if carry_out else 0
            else:
                carry_out = get_bit(value, shift_amount - 1)
                mask = 4294967295
                result = extend_sign_32(value) >> shift_amount & mask
        elif shift_type == ShiftType.ROR:
            if shift_amount > 32:
                shift_amount = (shift_amount - 1) % 32 + 1
            if shift_amount == 0:
                carry_out = get_bit(value, 0)
                result = value >> 1 | self.regs.cpsr.get_carry_flag() << 31
            else:
                carry_out = get_bit(value, shift_amount - 1)
                result = ror_32(value, get_bits(shift_amount, 0, 4))
        return (result, carry_out)