from typing import Optional, Tuple

from mtkclient.Library.Exploit.exptools.arch import ArchTools

class ArmTools(ArchTools):

    def is_pointer_auth(self, instr: int) -> bool:
        _ = instr
        return False

    def is_movt_imm(self, instr: int, imm16: int) -> bool:
        """
        Check if the instruction is MOVT with the specific imm16.
        Exact port of Rust is_movt_imm logic.
        """
        decoded = self.decode_movt(instr)
        if decoded is None:
            return False
        _, decoded_imm16 = decoded
        return decoded_imm16 == imm16

    def force_return(self, data: bytearray, offset: int, value: int, thumb: bool = False) -> bytearray:
        if thumb:
            mov_r0 = 0x2000 | (value & 0xFF)
            bx_lr = 0x4770
            data[offset:offset + 2] = mov_r0.to_bytes(2, byteorder="little")
            data[offset + 2:offset + 4] = bx_lr.to_bytes(2, byteorder="little")
            return data
        mov_r0 = 0xE3A00000 | (value & 0xFF) | ((value << 4) & 0xF00)
        bx_lr = 0xE12FFF1E
        data[offset:offset + 4] = mov_r0.to_bytes(4, byteorder="little")
        data[offset + 4:offset + 8] = bx_lr.to_bytes(4, byteorder="little")
        return data

    def decode_bl(self, instr: int, pc: int) -> Optional[int]:
        """
        Decode BL or B (unconditional) in ARM32.
        Opcode 0xEB000000 for BL, 0xEA000000 for B.
        Computes target = pc + 8 + (signed_imm24 * 4)
        Exact port of Rust decode_bl logic.
        """
        opcode = instr & 0xFF000000
        if opcode not in (0xEB000000, 0xEA000000):  # BL or B
            return None
        imm24 = instr & 0x00FFFFFF
        if (imm24 & 0x00800000) != 0:
            imm24 -= 0x01000000  # Sign extend
        arm_pc = pc + 8
        return arm_pc + (imm24 * 4)

    def is_prologue(self, instr: int) -> bool:
        """
        Check if the instruction is a common ARM32 function prologue (PUSH {regs}).
        Specifically, STMDB SP!, {regs} with LR included (bit 14 set).
        Exact port of Rust is_prologue logic.
        """
        if (instr & 0xFFFF0000) == 0xE92D0000 and (instr & (1 << 14)) != 0:
            return True
        return False

    def get_movw_reg(self, instr: int) -> int:
        """
        Get the destination register (rd) from a MOVW instruction.
        Assumes the instruction is already known to be MOVW.
        Exact port of Rust get_movw_reg.
        """
        return (instr >> 12) & 0xF

    def is_movw_imm(self, instr: int, imm16: int) -> bool:
        """
        Check if the instruction is MOVW with the specific imm16.
        Exact port of Rust is_movw_imm logic.
        """
        decoded = self.decode_movw(instr)
        if decoded is None:
            return False
        _, decoded_imm16 = decoded
        return decoded_imm16 == imm16

    def get_movt_reg(self, instr: int) -> int:
        """
        Get the destination register (rd) from a MOVT instruction.
        Assumes the instruction is already known to be MOVT.
        Exact port of Rust get_movt_reg.
        """
        return (instr >> 12) & 0xF


    def find_string_xref(self, s: str) -> Optional[int]:
        str_off = self.find_string(s)
        if str_off is None:
            return None
        str_va = self.base_addr + str_off
        low16 = str_va & 0xFFFF
        high16 = (str_va >> 16) & 0xFFFF
        for off in range(0, len(self.data) - 8, 4):
            instr1 = self.read_u32(off)
            if instr1 is None:
                continue
            if self.is_movw_imm(instr1, low16):
                reg = self.get_movw_reg(instr1)
                found = False
                lookahead_start = off + 4
                lookahead_end = min(off + 80, len(self.data))  # 20 instructions
                for lookahead_off in range(lookahead_start, lookahead_end, 4):
                    instr2 = self.read_u32(lookahead_off)
                    if instr2 is None:
                        break
                    if self.is_movt_imm(instr2, high16) and self.get_movt_reg(instr2) == reg:
                        found = True
                        break
                if found:
                    return off  # MOVW offset as xref
        # Fallback for LDR PC-relative xrefs
        for off in range(0, len(self.data) - 4, 4):
            instr = self.read_u32(off)
            if instr is None:
                continue
            pc = self.offset_to_va(off)
            if pc is None:
                continue
            ldr = self.decode_ldr_pc(instr, pc)
            if ldr is not None:
                rd, addr = ldr
                _ = rd
                if addr == str_va:
                    return off  # LDR offset as xref
        return None

    def find_function_start_from_off(self, offset: int) -> Optional[int]:
        limit = 0x2000  # Rust default for backward search
        search_start = max(0, offset - limit)
        for off in range(offset, search_start - 1, -4):
            instr = self.read_u32(off)
            if instr is None:
                continue
            if self.is_prologue(instr):
                tmp = self.read_u32(off-4)
                tmp2 = self.read_u32(off-8)
                if tmp2==0xE3510000 and tmp==0x012FFF1E:
                    return off-8
                return off
        return None

    def decode_sub_imm(self, instr: int) -> Optional[Tuple[int, int, int]]:
        """
        Decode SUB (immediate) in ARM32: rd = rn - imm12
        Opcode mask: 0x0FF00000 == 0x02400000 (SUB immediate, set flags; adjust for no-S if needed)
        """
        if (instr & 0x0FF00000) == 0x02400000:  # SUBS rd, rn, #imm12 (common form; S=1 for flags)
            rn = (instr >> 16) & 0xF
            rd = (instr >> 12) & 0xF
            imm12 = instr & 0xFFF
            return rn, rd, imm12
        if (instr & 0x0FF00000) == 0x00400000:  # SUB rd, rn, #imm12 (no flags)
            rn = (instr >> 16) & 0xF
            rd = (instr >> 12) & 0xF
            imm12 = instr & 0xFFF
            return rn, rd, imm12
        return None

    def decode_mov(self, instr: int) -> Optional[Tuple[int, int]]:
        """
        Decode MOV (register): rd = rm
        Opcode mask: 0x0FE00FF0 == 0x01A00000
        """
        if (instr & 0x0FE00FF0) == 0x01A00000:
            rd = (instr >> 12) & 0xF
            rm = instr & 0xF
            return rm, rd  # source rm, dest rd
        return None

    def decode_movt(self, instr: int) -> Optional[Tuple[int, int]]:
        """
        Decode MOVT (move top): rd = (rd & 0xFFFF) | (imm16 << 16)
        Opcode mask: 0x0FF00000 == 0x03400000
        """
        if (instr & 0x0FF00000) == 0x03400000:
            rd = (instr >> 12) & 0xF
            imm4 = (instr >> 16) & 0xF
            imm12 = instr & 0xFFF
            imm16 = (imm4 << 12) | imm12
            return rd, imm16
        return None

    def decode_movw(self, instr: int) -> Optional[Tuple[int, int]]:
        """
        Decode MOVW (move wide): rd = imm16
        Opcode mask: 0x0FF00000 == 0x03000000
        """
        if (instr & 0x0FF00000) == 0x03000000:
            rd = (instr >> 12) & 0xF
            imm4 = (instr >> 16) & 0xF
            imm12 = instr & 0xFFF
            imm16 = (imm4 << 12) | imm12
            return rd, imm16
        return None

    def decode_ldr_pc(self, instr: int, pc: int) -> Optional[Tuple[int, int]]:
        """
        Decode LDR (literal/PC-relative): rd = [pc +/- imm12]
        Opcode mask: 0x0C5F0000 == 0x041F0000 (LDR rd, [PC, #+/-imm12])
        pc is the current PC value (instr address + 8 for ARM)
        """
        if (instr & 0x0C5F0000) == 0x041F0000:
            u_bit = (instr >> 23) & 1  # 1 = add, 0 = subtract
            rd = (instr >> 12) & 0xF
            imm12 = instr & 0xFFF
            arm_pc = pc + 8  # ARM PC bias
            target_addr = arm_pc + imm12 if u_bit == 1 else arm_pc - imm12
            return rd, target_addr  # rd, literal pool address
        return None

    def decode_sub_reg(self, instr: int) -> Optional[Tuple[int, int, int]]:
        """
        Decode SUB (register): rd = rn - rm
        Opcode mask: 0x0FE00FF0 == 0x00400000 (SUB rd, rn, rm; no shift)
        """
        if (instr & 0x0FE00FF0) == 0x00400000:
            rn = (instr >> 16) & 0xF
            rd = (instr >> 12) & 0xF
            rm = instr & 0xF
            return rn, rm, rd  # rn - rm -> rd
        return None

    def find_call_arg_from_string(self, s: str, arg_idx: int) -> Optional[int]:
        start = self.find_string_xref(s)
        if start is None:
            return None

        off = start
        while off < len(self.data):
            if self.get_bl_target(off) is not None:
                return self.resolve_register_value(off, arg_idx, 50)

            off += 4

        return None


    def decode_add_imm(self, instr: int) -> Optional[Tuple[int, int, int]]:
        """
        Decode ADD (immediate) in ARM32: rd = rn + imm12
        Opcode mask: 0x0FF00000 == 0x02800000 (ADDS rd, rn, #imm12; S=1)
        or 0x00800000 for ADD (no flags).
        """
        if (instr & 0x0FF00000) == 0x02800000:  # ADDS rd, rn, #imm12
            rn = (instr >> 16) & 0xF
            rd = (instr >> 12) & 0xF
            imm12 = instr & 0xFFF
            return rn, rd, imm12
        if (instr & 0x0FF00000) == 0x00800000:  # ADD rd, rn, #imm12 (no S)
            rn = (instr >> 16) & 0xF
            rd = (instr >> 12) & 0xF
            imm12 = instr & 0xFFF
            return rn, rd, imm12
        return None

    def decode_add_reg(self, instr: int) -> Optional[Tuple[int, int, int]]:
        """
        Decode ADD (register, no shift) in ARM32: rd = rn + rm
        Opcode mask: 0x0FE00FF0 == 0x00800000
        Common form without shift or rotate.
        """
        if (instr & 0x0FE00FF0) == 0x00800000:
            rn = (instr >> 16) & 0xF
            rd = (instr >> 12) & 0xF
            rm = instr & 0xF
            return rn, rm, rd  # rn + rm -> rd
        return None

    def find_function_from_string(self, s: str) -> Optional[int]:
        xref = self.find_string_xref(s)
        if xref is None:
            return None
        return self.find_function_start_from_off(xref)

    def is_bx_lr(self, instr: int) -> bool:
        return (instr & 0x0FFFFFFF) == 0x012FFF1E

    def resolve_register_value(self, at: int, reg: int, lookahead: int) -> Optional[int]:
        """
        Resolve a register value by simulating forward from the call target entry (heap_init entry for constants).
        This handles stub functions that compute constants via MOVW/MOVT/SUB and tail-call (B or BX LR).
        Tracks register updates and returns the value of 'reg' at the stub exit (B or BX LR).
        """
        start_off = at
        if start_off is None:
            return None

        registers = [0] * 16  # r0 - r15 (PC not used)

        current_off = start_off
        max_instructions = lookahead  # Use lookahead as max instruction count

        for _ in range(max_instructions):
            instr = self.read_u32(current_off)
            if instr is None:
                return None

            # MOVW rd, imm16
            movw = self.decode_movw(instr)
            if movw is not None:
                rd, imm16 = movw
                registers[rd] = imm16

            # MOVT rd, imm16
            movt = self.decode_movt(instr)
            if movt is not None:
                rd, imm16 = movt
                registers[rd] |= (imm16 << 16)

            # SUB rd, rn, rm
            sub_reg = self.decode_sub_reg(instr)
            if sub_reg is not None:
                rn, rm, rd = sub_reg
                registers[rd] = (registers[rn] - registers[rm]) & 0xFFFFFFFF

            # ADD rd, rn, #imm (if needed)
            add_imm = self.decode_add_imm(instr)
            if add_imm is not None:
                rn, rd, imm = add_imm
                registers[rd] = (registers[rn] + imm) & 0xFFFFFFFF

            add_reg = self.decode_add_reg(instr)
            if add_reg is not None:
                rn, rm, rd = add_reg
                registers[rd] = (registers[rn] + registers[rm]) & 0xFFFFFFFF

            # SUB rd, rn, #imm (if needed)
            sub_imm = self.decode_sub_imm(instr)
            if sub_imm is not None:
                rn, rd, imm = sub_imm
                registers[rd] = (registers[rn] - imm) & 0xFFFFFFFF

            # MOV rd, rm
            mov = self.decode_mov(instr)
            if mov is not None:
                rm, rd = mov
                registers[rd] = registers[rm]

            # LDR rd, [pc, #imm]
            ldr = self.decode_ldr_pc(instr, current_off + 8)  # pc + 8
            if ldr is not None:
                rd, addr = ldr
                pool_off = self.va_to_offset(addr)
                if pool_off is not None:
                    val = self.read_u32(pool_off)
                    if val is not None:
                        registers[rd] = val

            # Unconditional B: tail-call, return current reg value
            if (instr & 0xFF000000) == 0xEA000000:  # B (AL)
                return registers[reg]

            # BX LR: return current reg value
            if self.is_bx_lr(instr):
                return registers[reg]

            current_off += 4

        return None  # Did not hit exit within lookahead

    def resolve_register_value_back(self, at: int, reg: int, lookback: int) -> Optional[int]:
        """
        Resolve a register value by scanning backwards from the instruction at 'at'.
        Handles common ARM32 constant loading patterns: MOVW/MOVT, LDR PC-relative, MOV, SUB (reg/imm).
        Tracks register aliases via MOV and computes SUB results recursively.
        """
        start = max(0, at - lookback * 4)
        cur_reg = reg

        # Start from just before 'at' (skip the instruction at 'at' if it's not relevant)
        for off in range(at - 4, start - 1, -4):
            instr = self.read_u32(off)
            if instr is None:
                continue

            pc = self.offset_to_va(off)
            if pc is None:
                continue

            # LDR PC-relative: load literal from pool
            ldr = self.decode_ldr_pc(instr, pc)
            if ldr is not None:
                rd, addr = ldr
                if rd == cur_reg:
                    pool_off = self.va_to_offset(addr)
                    if pool_off is not None:
                        return self.read_u32(pool_off)

            # SUB (register): rd = rn - rm
            sub_reg = self.decode_sub_reg(instr)
            if sub_reg is not None:
                rn, rm, rd = sub_reg
                if rd == cur_reg:
                    rn_val = self.resolve_register_value(off, rn, lookback)
                    rm_val = self.resolve_register_value(off, rm, lookback)
                    if rn_val is not None and rm_val is not None:
                        return rn_val - rm_val

            # SUB (immediate) would require decode_sub_imm if needed (similar to add_imm, but for subtract)
            # Add if common in DA: def decode_sub_imm(self, instr): similar to sub_reg but with imm

            # MOV rd, rm: alias, track source
            mov = self.decode_mov(instr)
            if mov is not None:
                rm, rd = mov
                if rd == cur_reg:
                    cur_reg = rm  # Continue with source reg

            # MOVW rd, imm16: set low 16 bits
            movw = self.decode_movw(instr)
            if movw is not None:
                rd, imm16 = movw
                if rd == cur_reg:
                    # MOVW sets low; continue to find if MOVT precedes for high bits
                    continue  # But since backward, it will hit MOVT later if present

            # MOVT rd, imm16: set high 16 bits (combine with prior low if MOVW found)
            movt = self.decode_movt(instr)
            if movt is not None:
                rd, imm16 = movt
                if rd == cur_reg:
                    # Assume low bits already resolved in prior scan (backward accumulates)
                    # To handle properly, may need to resolve low separately if needed
                    low_val = self.resolve_register_value(off, rd, lookback)  # Recurse for low (MOVW)
                    if low_val is not None:
                        return (imm16 << 16) | (low_val & 0xFFFF)
                    return imm16 << 16  # If no low, assume 0
        return None
