# ifndef CYTHON
from __future__ import annotations

from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from pyboy_advance.cpu.cpu import CPU

from pyboy_advance.cpu.arm.alu import (
    arm_alu_sub,
    arm_alu_add,
    arm_alu_mov,
    arm_alu_adc,
    arm_alu_sbc,
    arm_alu_tst,
    arm_alu_cmp,
    arm_alu_cmn,
    arm_alu_orr,
    arm_alu_bic,
    arm_alu_mvn,
    arm_alu_and,
    arm_alu_eor,
)
from pyboy_advance.cpu.arm.mul import arm_multiply_idle
from pyboy_advance.cpu.constants import ShiftType
from pyboy_advance.cpu.thumb.constants import ThumbALUOpcode
from pyboy_advance.memory.constants import MemoryAccess
from pyboy_advance.utils import (
    get_bits,
    get_bit,
    sign_32,
    add_32,
)
# endif


def thumb_move_shifted_register(cpu: CPU, instr: int):
    """Execute a THUMB.1 instruction (move shifted register)"""

    rs = get_bits(instr, 3, 5)  # Source reg
    rd = get_bits(instr, 0, 2)  # Destination reg

    opcode = get_bits(instr, 11, 12)
    offset = get_bits(instr, 6, 10)

    result, carry = cpu.compute_shift(cpu.regs.get(rs), opcode, offset, True)

    cpu.regs.set(rd, result)
    cpu.regs.cpsr.sign_flag = sign_32(result)
    cpu.regs.cpsr.zero_flag = result == 0
    cpu.regs.cpsr.carry_flag = carry

    cpu.advance_pc_thumb()
    cpu.next_fetch_access = MemoryAccess.SEQUENTIAL


def thumb_add_subtract(cpu: CPU, instr: int):
    """Execute a THUMB.2 instruction (add/subtract)"""

    rs = get_bits(instr, 3, 5)  # Source reg
    rd = get_bits(instr, 0, 2)  # Destination reg

    opcode = get_bits(instr, 9, 10)
    immediate_bit = get_bit(instr, 10)

    op1 = cpu.regs.get(rs)
    op2 = get_bits(instr, 6, 8) if immediate_bit else cpu.regs.get(get_bits(instr, 6, 8))

    if opcode == 0:  # ADD (Register)
        arm_alu_add(cpu, op1, op2, rd, True)
    elif opcode == 1:  # SUB (Register)
        arm_alu_sub(cpu, op1, op2, rd, True)
    elif opcode == 2:  # ADD (Immediate)
        arm_alu_add(cpu, op1, op2, rd, True)
    elif opcode == 3:  # SUB (Immediate)
        arm_alu_sub(cpu, op1, op2, rd, True)

    cpu.advance_pc_thumb()
    cpu.next_fetch_access = MemoryAccess.SEQUENTIAL


def thumb_move_compare_add_subtract(cpu: CPU, instr: int):
    """Execute a THUMB.3 instruction (move/compare/add/subtract immediate)"""

    rd = get_bits(instr, 8, 10)  # Destination reg

    opcode = get_bits(instr, 11, 12)
    value = get_bits(instr, 0, 7)

    if opcode == 0:  # MOV
        arm_alu_mov(cpu, value, rd, True, cpu.regs.cpsr.carry_flag)
    elif opcode == 1:  # CMP
        arm_alu_cmp(cpu, cpu.regs.get(rd), value)
    elif opcode == 2:  # ADD
        arm_alu_add(cpu, cpu.regs.get(rd), value, rd, True)
    elif opcode == 3:  # SUB
        arm_alu_sub(cpu, cpu.regs.get(rd), value, rd, True)

    cpu.advance_pc_thumb()
    cpu.next_fetch_access = MemoryAccess.SEQUENTIAL


def thumb_alu(cpu: CPU, instr: int):
    """Execute a THUMB.4 instruction (miscellaneous ALU operations)"""

    rs = get_bits(instr, 3, 5)
    rd = get_bits(instr, 0, 2)

    op1 = cpu.regs.get(rd)
    op2 = cpu.regs.get(rs)

    opcode = get_bits(instr, 6, 9)
    if opcode == ThumbALUOpcode.AND:
        arm_alu_and(cpu, op1, op2, rd, True, cpu.regs.cpsr.carry_flag)
    elif opcode == ThumbALUOpcode.EOR:
        arm_alu_eor(cpu, op1, op2, rd, True, cpu.regs.cpsr.carry_flag)
    elif opcode == ThumbALUOpcode.LSL:
        thumb_alu_shift(cpu, op1, op2, rd, ShiftType.LSL)
    elif opcode == ThumbALUOpcode.LSR:
        thumb_alu_shift(cpu, op1, op2, rd, ShiftType.LSR)
    elif opcode == ThumbALUOpcode.ASR:
        thumb_alu_shift(cpu, op1, op2, rd, ShiftType.ASR)
    elif opcode == ThumbALUOpcode.ADC:
        arm_alu_adc(cpu, op1, op2, rd, True)
    elif opcode == ThumbALUOpcode.SBC:
        arm_alu_sbc(cpu, op1, op2, rd, True)
    elif opcode == ThumbALUOpcode.ROR:
        thumb_alu_shift(cpu, op1, op2, rd, ShiftType.ROR)
    elif opcode == ThumbALUOpcode.TST:
        arm_alu_tst(cpu, op1, op2, cpu.regs.cpsr.carry_flag)
    elif opcode == ThumbALUOpcode.NEG:
        arm_alu_sub(cpu, 0, op2, rd, True)
    elif opcode == ThumbALUOpcode.CMP:
        arm_alu_cmp(cpu, op1, op2)
    elif opcode == ThumbALUOpcode.CMN:
        arm_alu_cmn(cpu, op1, op2)
    elif opcode == ThumbALUOpcode.ORR:
        arm_alu_orr(cpu, op1, op2, rd, True, cpu.regs.cpsr.carry_flag)
    elif opcode == ThumbALUOpcode.MUL:
        thumb_alu_multiply(cpu, op1, op2, rd)
    elif opcode == ThumbALUOpcode.BIC:
        arm_alu_bic(cpu, op1, op2, rd, True, cpu.regs.cpsr.carry_flag)
    elif opcode == ThumbALUOpcode.MVN:
        arm_alu_mvn(cpu, op2, rd, True, cpu.regs.cpsr.carry_flag)

    cpu.advance_pc_thumb()
    cpu.next_fetch_access = MemoryAccess.SEQUENTIAL


def thumb_alu_shift(cpu: CPU, value: int, shift: int, rd: int, shift_type: ShiftType):
    shift &= 0xFF
    result, carry = cpu.compute_shift(value, shift_type, shift, False)

    cpu.regs.set(rd, result)
    cpu.regs.cpsr.sign_flag = sign_32(result)
    cpu.regs.cpsr.zero_flag = result == 0
    cpu.regs.cpsr.carry_flag = carry

    cpu.scheduler.idle(1)


def thumb_alu_multiply(cpu: CPU, op1: int, op2: int, rd: int):
    mask = 0xFFFFFFFF
    result = (op1 * op2) & mask

    cpu.regs.set(rd, result)
    cpu.regs.cpsr.sign_flag = sign_32(result)
    cpu.regs.cpsr.zero_flag = result == 0

    arm_multiply_idle(cpu, op1, True)


def thumb_high_reg_branch_exchange(cpu: CPU, instr: int):
    """Execute a THUMB.5 instruction (high register operations or branch exchange)"""

    rs = (get_bit(instr, 6) << 3) | get_bits(instr, 3, 5)
    rd = (get_bit(instr, 7) << 3) | get_bits(instr, 0, 2)

    opcode = get_bits(instr, 8, 9)

    if opcode == 0:  # ADD
        cpu.regs.set(rd, add_32(cpu.regs.get(rd), cpu.regs.get(rs)))
        if rd == cpu.regs.PC:
            cpu.flush_pipeline()
            return

    elif opcode == 1:  # CMP
        arm_alu_cmp(cpu, cpu.regs.get(rd), cpu.regs.get(rs))

    elif opcode == 2:  # MOV
        cpu.regs.set(rd, cpu.regs.get(rs))
        if rd == cpu.regs.PC:
            cpu.flush_pipeline()
            return

    elif opcode == 3:  # BX
        address = cpu.regs.get(rs)

        # Mask out the last bit indicating whether to switch to ARM mode
        cpu.regs.pc = address & ~1
        cpu.regs.cpsr.state = get_bit(address, 0)

        cpu.flush_pipeline()
        return

    cpu.advance_pc_thumb()
    cpu.next_fetch_access = MemoryAccess.SEQUENTIAL


def thumb_get_address(cpu: CPU, instr: int):
    """Execute a THUMB.12 instruction (get address relative to SP/PC)"""

    opcode = get_bit(instr, 11)
    rd = get_bits(instr, 8, 10)
    offset = get_bits(instr, 0, 7) * 4

    if opcode:
        # Load from SP
        cpu.regs.set(rd, add_32(cpu.regs.sp, offset))
    else:
        # Load from PC
        cpu.regs.set(rd, add_32(cpu.regs.pc & ~2, offset))

    cpu.advance_pc_thumb()
    cpu.next_fetch_access = MemoryAccess.SEQUENTIAL


def thumb_add_offset_to_stack_pointer(cpu: CPU, instr: int):
    """Execute a THUMB.13 instruction (add offset to SP)"""

    sign = -1 if get_bit(instr, 7) else 1
    offset = sign * get_bits(instr, 0, 6) * 4

    cpu.regs.sp = add_32(cpu.regs.sp, offset)

    cpu.advance_pc_thumb()
    cpu.next_fetch_access = MemoryAccess.SEQUENTIAL
