use alloc::{borrow::Cow, collections::BTreeMap, vec::Vec};
use core::fmt::Write;

use anyhow::{Result, bail};
use arm_attr::{BuildAttrs, enums::CpuArch, tag::Tag};
use object::{Endian as _, Object as _, ObjectSection as _, ObjectSymbol as _, elf};
use unarm::FormatValue as _;

use crate::{
    arch::{Arch, OPCODE_DATA, OPCODE_INVALID, RelocationOverride, RelocationOverrideTarget},
    diff::{ArmArchVersion, ArmR9Usage, DiffObjConfig, display::InstructionPart},
    obj::{
        InstructionRef, Relocation, RelocationFlags, ResolvedInstructionRef, Section, SectionKind,
        Symbol, SymbolFlag, SymbolFlagSet, SymbolKind,
    },
};

#[derive(Debug)]
pub struct ArchArm {
    /// Maps section index, to list of disasm modes (arm, thumb or data) sorted by address
    disasm_modes: BTreeMap<usize, Vec<DisasmMode>>,
    detected_version: Option<unarm::Version>,
    endianness: object::Endianness,
}

impl ArchArm {
    pub fn new(file: &object::File) -> Result<Self> {
        let endianness = file.endianness();
        match file {
            object::File::Elf32(_) => {
                // The disasm_modes mapping is populated later in the post_init step so that we have access to merged sections.
                let disasm_modes = BTreeMap::new();
                let detected_version = Self::elf_detect_arm_version(file)?;
                Ok(Self { disasm_modes, detected_version, endianness })
            }
            _ => bail!("Unsupported file format {:?}", file.format()),
        }
    }

    fn elf_detect_arm_version(file: &object::File) -> Result<Option<unarm::Version>> {
        // Check ARM attributes
        if let Some(arm_attrs) = file.sections().find(|s| {
            s.kind() == object::SectionKind::Elf(elf::SHT_ARM_ATTRIBUTES)
                && s.name() == Ok(".ARM.attributes")
        }) {
            let attr_data = arm_attrs.uncompressed_data()?;
            let build_attrs = BuildAttrs::new(&attr_data, match file.endianness() {
                object::Endianness::Little => arm_attr::Endian::Little,
                object::Endianness::Big => arm_attr::Endian::Big,
            })?;
            for subsection in build_attrs.subsections() {
                let subsection = subsection?;
                if !subsection.is_aeabi() {
                    continue;
                }
                // Only checking first CpuArch tag. Others may exist, but that's very unlikely.
                let cpu_arch = subsection.into_public_tag_iter()?.find_map(|(_, tag)| {
                    if let Tag::CpuArch(cpu_arch) = tag { Some(cpu_arch) } else { None }
                });
                match cpu_arch {
                    Some(CpuArch::V4) => return Ok(Some(unarm::Version::V4)),
                    Some(CpuArch::V4T) => return Ok(Some(unarm::Version::V4T)),
                    Some(CpuArch::V5TE) => return Ok(Some(unarm::Version::V5Te)),
                    Some(CpuArch::V5TEJ) => return Ok(Some(unarm::Version::V5Tej)),
                    Some(CpuArch::V6) => return Ok(Some(unarm::Version::V6)),
                    Some(CpuArch::V6K) => return Ok(Some(unarm::Version::V6K)),
                    Some(arch) => bail!("ARM arch {} not supported", arch),
                    None => {}
                };
            }
        }

        Ok(None)
    }

    fn get_mapping_symbols(
        sections: &[Section],
        symbols: &[Symbol],
    ) -> BTreeMap<usize, Vec<DisasmMode>> {
        sections
            .iter()
            .enumerate()
            .filter(|(_, section)| section.kind == SectionKind::Code)
            .map(|(index, _)| {
                let mut mapping_symbols: Vec<_> = symbols
                    .iter()
                    .filter(|s| s.section.map(|i| i == index).unwrap_or(false))
                    .filter_map(DisasmMode::from_symbol)
                    .collect();
                mapping_symbols.sort_unstable_by_key(|x| x.address);
                (index, mapping_symbols)
            })
            .collect()
    }

    fn unarm_options(&self, diff_config: &DiffObjConfig) -> unarm::Options {
        let mut extensions = unarm::Extensions::none();
        if diff_config.arm_vfp_v2 {
            extensions = extensions.with(unarm::Extension::VfpV2);
        }
        unarm::Options {
            version: match diff_config.arm_arch_version {
                ArmArchVersion::Auto => self.detected_version.unwrap_or(unarm::Version::V5Te),
                ArmArchVersion::V4 => unarm::Version::V4,
                ArmArchVersion::V4t => unarm::Version::V4T,
                ArmArchVersion::V5t => unarm::Version::V5T,
                ArmArchVersion::V5te => unarm::Version::V5Te,
                ArmArchVersion::V5tej => unarm::Version::V5Tej,
                ArmArchVersion::V6 => unarm::Version::V6,
                ArmArchVersion::V6k => unarm::Version::V6K,
            },
            extensions,
            av: diff_config.arm_av_registers,
            r9_use: match diff_config.arm_r9_usage {
                ArmR9Usage::GeneralPurpose => unarm::R9Use::R9,
                ArmR9Usage::Sb => unarm::R9Use::Sb,
                ArmR9Usage::Tr => unarm::R9Use::Tr,
            },
            sl: diff_config.arm_sl_usage,
            fp: diff_config.arm_fp_usage,
            ip: diff_config.arm_ip_usage,
            ual: diff_config.arm_unified_syntax,
        }
    }

    fn parse_ins_ref(
        &self,
        ins_ref: InstructionRef,
        code: &[u8],
        diff_config: &DiffObjConfig,
    ) -> Result<unarm::Ins> {
        let code = match (self.endianness, ins_ref.size) {
            (object::Endianness::Little, 2) => u16::from_le_bytes([code[0], code[1]]) as u32,
            (object::Endianness::Little, 4) => {
                u32::from_le_bytes([code[0], code[1], code[2], code[3]])
            }
            (object::Endianness::Big, 2) => u16::from_be_bytes([code[0], code[1]]) as u32,
            (object::Endianness::Big, 4) => {
                u32::from_be_bytes([code[0], code[1], code[2], code[3]])
            }
            _ => bail!("Invalid instruction size {}", ins_ref.size),
        };

        let thumb = ins_ref.opcode & (1 << 15) == 0;
        let discriminant = ins_ref.opcode & !(1 << 15);
        let pc = ins_ref.address as u32;
        let options = self.unarm_options(diff_config);

        let ins = if ins_ref.opcode == OPCODE_DATA {
            match ins_ref.size {
                4 => unarm::Ins::Word(code),
                2 => unarm::Ins::HalfWord(code as u16),
                _ => bail!("Invalid data size {}", ins_ref.size),
            }
        } else if thumb {
            unarm::parse_thumb_with_discriminant(code, discriminant, pc, &options)
        } else {
            unarm::parse_arm_with_discriminant(code, discriminant, pc, &options)
        };
        Ok(ins)
    }
}

impl Arch for ArchArm {
    fn post_init(&mut self, sections: &[Section], symbols: &[Symbol], _symbol_indices: &[usize]) {
        self.disasm_modes = Self::get_mapping_symbols(sections, symbols);
    }

    fn scan_instructions_internal(
        &self,
        address: u64,
        code: &[u8],
        section_index: usize,
        _relocations: &[Relocation],
        diff_config: &DiffObjConfig,
    ) -> Result<Vec<InstructionRef>> {
        let start_addr = address as u32;
        let end_addr = start_addr + code.len() as u32;

        // Mapping symbols decide what kind of data comes after it. $a for ARM code, $t for Thumb code and $d for data.
        let fallback_mappings =
            [DisasmMode { address: start_addr, mapping: unarm::ParseMode::Arm }];
        let mapping_symbols = self
            .disasm_modes
            .get(&section_index)
            .map(|x| if x.is_empty() { &fallback_mappings } else { x.as_slice() })
            .unwrap_or(&fallback_mappings);
        let first_mapping_idx = mapping_symbols
            .binary_search_by_key(&start_addr, |x| x.address)
            .unwrap_or_else(|idx| idx.saturating_sub(1));
        let mut mode = mapping_symbols[first_mapping_idx].mapping;

        let mut mappings_iter = mapping_symbols
            .iter()
            .copied()
            .skip(first_mapping_idx + 1)
            .take_while(|x| x.address < end_addr);
        let mut next_mapping = mappings_iter.next();

        let min_ins_size = if mode == unarm::ParseMode::Thumb { 2 } else { 4 };
        let ins_count = code.len() / min_ins_size;
        let mut ops = Vec::<InstructionRef>::with_capacity(ins_count);

        let options = self.unarm_options(diff_config);

        let mut address = start_addr;
        while address < end_addr {
            while let Some(next) = next_mapping.filter(|x| address >= x.address) {
                // Change mapping
                mode = next.mapping;
                next_mapping = mappings_iter.next();
            }

            let data = &code[(address - start_addr) as usize..];
            if data.len() < min_ins_size {
                // Push the remainder as data
                ops.push(InstructionRef {
                    address: address as u64,
                    size: data.len() as u8,
                    opcode: OPCODE_DATA,
                    branch_dest: None,
                });
                break;
            }

            // Check how many bytes we can/should read
            let bytes_until_next_mapping =
                next_mapping.map(|m| (m.address - address) as usize).unwrap_or(usize::MAX);
            let num_code_bytes = if data.len() >= 4 && bytes_until_next_mapping >= 4 {
                if mode == unarm::ParseMode::Data && address & 3 != 0 {
                    // 32-bit .word value should be aligned on a 4-byte boundary, otherwise use .hword
                    2
                } else {
                    // Read 4 bytes even for Thumb, as the parser will determine if it's a 2 or 4 byte instruction
                    4
                }
            } else if mode != unarm::ParseMode::Arm {
                2
            } else {
                // Invalid instruction size
                ops.push(InstructionRef {
                    address: address as u64,
                    size: min_ins_size as u8,
                    opcode: OPCODE_INVALID,
                    branch_dest: None,
                });
                address += min_ins_size as u32;
                continue;
            };

            let code = match num_code_bytes {
                4 => match self.endianness {
                    object::Endianness::Little => {
                        u32::from_le_bytes([data[0], data[1], data[2], data[3]])
                    }
                    object::Endianness::Big => {
                        if mode != unarm::ParseMode::Thumb {
                            u32::from_be_bytes([data[0], data[1], data[2], data[3]])
                        } else {
                            // For 4-byte Thumb instructions, read two 16-bit halfwords in big endian
                            u32::from_be_bytes([data[2], data[3], data[0], data[1]])
                        }
                    }
                },
                2 => match self.endianness {
                    object::Endianness::Little => u16::from_le_bytes([data[0], data[1]]) as u32,
                    object::Endianness::Big => u16::from_be_bytes([data[0], data[1]]) as u32,
                },
                _ => unreachable!(),
            };

            let (opcode, ins, ins_size) = match mode {
                unarm::ParseMode::Arm => {
                    let ins = unarm::parse_arm(code, address, &options);
                    let opcode = ins.discriminant() | (1 << 15);
                    (opcode, ins, 4)
                }
                unarm::ParseMode::Thumb => {
                    let (ins, size) = unarm::parse_thumb(code, address, &options);
                    let opcode = ins.discriminant();
                    (opcode, ins, size)
                }
                unarm::ParseMode::Data => (
                    OPCODE_DATA,
                    if num_code_bytes == 4 {
                        unarm::Ins::Word(code)
                    } else {
                        unarm::Ins::HalfWord(code as u16)
                    },
                    num_code_bytes,
                ),
            };

            let branch_dest = match ins {
                unarm::Ins::B { target, .. }
                | unarm::Ins::Bl { target, .. }
                | unarm::Ins::Blx { target: unarm::BlxTarget::Direct(target), .. } => {
                    Some(target.addr)
                }
                _ => None,
            };

            ops.push(InstructionRef {
                address: address as u64,
                size: ins_size as u8,
                opcode,
                branch_dest: branch_dest.map(|x| x as u64),
            });
            address += ins_size;
        }

        Ok(ops)
    }

    fn display_instruction(
        &self,
        resolved: ResolvedInstructionRef,
        diff_config: &DiffObjConfig,
        cb: &mut dyn FnMut(InstructionPart) -> Result<()>,
    ) -> Result<()> {
        let ins = self.parse_ins_ref(resolved.ins_ref, resolved.code, diff_config)?;

        let options = self.unarm_options(diff_config);
        let mut string_fmt = unarm::StringFormatter::new(&options);
        ins.write_opcode(&mut string_fmt)?;
        let opcode = string_fmt.into_string();
        cb(InstructionPart::opcode(opcode, resolved.ins_ref.opcode))?;

        let mut args_formatter =
            ArgsFormatter { options: &options, cb, resolved: &resolved, skip_leading_space: true };
        ins.write_params(&mut args_formatter)?;
        Ok(())
    }

    fn relocation_override(
        &self,
        _file: &object::File<'_>,
        section: &object::Section,
        address: u64,
        relocation: &object::Relocation,
    ) -> Result<Option<RelocationOverride>> {
        match relocation.flags() {
            // Handle ELF implicit relocations
            object::RelocationFlags::Elf { r_type } => {
                if relocation.has_implicit_addend() {
                    // R_ARM_V4BX marks a `bx` instruction so the linker can optionally
                    // rewrite it for ARMv4 interworking. Drops the addend since it's meaningless.
                    if r_type == elf::R_ARM_V4BX {
                        return Ok(Some(RelocationOverride {
                            target: RelocationOverrideTarget::Skip,
                            addend: 0,
                        }));
                    }

                    let section_data = section.data()?;
                    let address = address as usize;
                    let addend = match r_type {
                        // ARM calls
                        elf::R_ARM_PC24
                        | elf::R_ARM_XPC25
                        | elf::R_ARM_CALL
                        | elf::R_ARM_JUMP24 => {
                            let data = section_data[address..address + 4].try_into()?;
                            let addend = self.endianness.read_i32(data);
                            let imm24 = addend & 0xffffff;
                            (imm24 << 2) << 8 >> 8
                        }

                        // Thumb calls
                        elf::R_ARM_THM_PC22 | elf::R_ARM_THM_XPC22 => {
                            let data = section_data[address..address + 2].try_into()?;
                            let high = self.endianness.read_i16(data) as i32;
                            let data = section_data[address + 2..address + 4].try_into()?;
                            let low = self.endianness.read_i16(data) as i32;

                            let imm22 = ((high & 0x7ff) << 11) | (low & 0x7ff);
                            (imm22 << 1) << 9 >> 9
                        }

                        // Thumb unconditional branch (B, 11-bit offset)
                        elf::R_ARM_THM_PC11 => {
                            let data = section_data[address..address + 2].try_into()?;
                            let insn = self.endianness.read_u16(data) as i32;
                            let imm11 = insn & 0x7ff;
                            (imm11 << 1) << 20 >> 20
                        }

                        // Thumb conditional branch (B<cond>, 8-bit offset)
                        elf::R_ARM_THM_PC9 => {
                            let data = section_data[address..address + 2].try_into()?;
                            let insn = self.endianness.read_u16(data) as i32;
                            let imm8 = insn & 0xff;
                            (imm8 << 1) << 23 >> 23
                        }

                        // Data
                        elf::R_ARM_ABS32 => {
                            let data = section_data[address..address + 4].try_into()?;
                            self.endianness.read_i32(data)
                        }

                        flags => bail!("Unsupported ARM implicit relocation {flags:?}"),
                    };
                    Ok(Some(RelocationOverride {
                        target: RelocationOverrideTarget::Keep,
                        addend: addend as i64,
                    }))
                } else {
                    Ok(None)
                }
            }
            _ => Ok(None),
        }
    }

    fn reloc_name(&self, flags: RelocationFlags) -> Option<&'static str> {
        match flags {
            RelocationFlags::Elf(r_type) => match r_type {
                elf::R_ARM_NONE => Some("R_ARM_NONE"),
                elf::R_ARM_ABS32 => Some("R_ARM_ABS32"),
                elf::R_ARM_REL32 => Some("R_ARM_REL32"),
                elf::R_ARM_ABS16 => Some("R_ARM_ABS16"),
                elf::R_ARM_ABS8 => Some("R_ARM_ABS8"),
                elf::R_ARM_THM_PC22 => Some("R_ARM_THM_PC22"),
                elf::R_ARM_THM_XPC22 => Some("R_ARM_THM_XPC22"),
                elf::R_ARM_PC24 => Some("R_ARM_PC24"),
                elf::R_ARM_XPC25 => Some("R_ARM_XPC25"),
                elf::R_ARM_CALL => Some("R_ARM_CALL"),
                elf::R_ARM_THM_PC11 => Some("R_ARM_THM_PC11"),
                elf::R_ARM_THM_PC9 => Some("R_ARM_THM_PC9"),
                elf::R_ARM_V4BX => Some("R_ARM_V4BX"),
                _ => None,
            },
            _ => None,
        }
    }

    fn data_reloc_size(&self, flags: RelocationFlags) -> usize {
        match flags {
            RelocationFlags::Elf(r_type) => match r_type {
                elf::R_ARM_NONE => 0,
                elf::R_ARM_ABS32 => 4,
                elf::R_ARM_REL32 => 4,
                elf::R_ARM_ABS16 => 2,
                elf::R_ARM_ABS8 => 1,
                elf::R_ARM_THM_PC22 => 4,
                elf::R_ARM_THM_XPC22 => 4,
                elf::R_ARM_PC24 => 4,
                elf::R_ARM_XPC25 => 4,
                elf::R_ARM_CALL => 4,
                elf::R_ARM_THM_PC11 => 2,
                elf::R_ARM_THM_PC9 => 2,
                _ => 1,
            },
            _ => 1,
        }
    }

    fn symbol_address(&self, address: u64, kind: SymbolKind) -> u64 {
        if kind == SymbolKind::Function { address & !1 } else { address }
    }

    fn extra_symbol_flags(&self, symbol: &object::Symbol) -> SymbolFlagSet {
        let mut flags = SymbolFlagSet::default();
        if DisasmMode::from_object_symbol(symbol).is_some() {
            flags |= SymbolFlag::Hidden;
        }
        flags
    }

    fn infer_function_size(
        &self,
        symbol: &Symbol,
        section: &Section,
        mut next_address: u64,
    ) -> Result<u64> {
        // TODO: This should probably check the disasm mode and trim accordingly,
        // but self.disasm_modes isn't populated until post_init, so it needs a refactor.

        // Trim any trailing 2-byte zeroes from the end (padding)
        while next_address >= symbol.address + 2
            && let Some(data) = section.data_range(next_address - 2, 2)
            && data == [0u8; 2]
        {
            next_address -= 2;
            if let Some(relocation) = section.relocation_at(next_address, 2) {
                // Avoid cutting trailing relocations in half.
                next_address += self.data_reloc_size(relocation.flags) as u64;
                break;
            }
        }
        Ok(next_address.saturating_sub(symbol.address))
    }
}

#[derive(Clone, Copy, Debug)]
struct DisasmMode {
    address: u32,
    mapping: unarm::ParseMode,
}

impl DisasmMode {
    fn from_object_symbol<'a>(sym: &object::Symbol<'a, '_, &'a [u8]>) -> Option<Self> {
        sym.name()
            .ok()
            .and_then(unarm::ParseMode::from_mapping_symbol)
            .map(|mapping| DisasmMode { address: sym.address() as u32, mapping })
    }

    fn from_symbol(sym: &Symbol) -> Option<Self> {
        unarm::ParseMode::from_mapping_symbol(&sym.name)
            .map(|mapping| DisasmMode { address: sym.address as u32, mapping })
    }
}

pub struct ArgsFormatter<'a> {
    options: &'a unarm::Options,
    cb: &'a mut dyn FnMut(InstructionPart) -> Result<()>,
    resolved: &'a ResolvedInstructionRef<'a>,
    skip_leading_space: bool,
}

impl ArgsFormatter<'_> {
    fn write(&mut self, part: InstructionPart) -> core::fmt::Result {
        (self.cb)(part).map_err(|_| core::fmt::Error)
    }

    fn write_opaque<F>(&mut self, value: F) -> core::fmt::Result
    where F: unarm::FormatValue {
        let mut string_fmt = unarm::StringFormatter::new(self.options);
        value.write(&mut string_fmt)?;
        self.write(InstructionPart::opaque(string_fmt.into_string()))?;
        Ok(())
    }
}

impl Write for ArgsFormatter<'_> {
    fn write_str(&mut self, s: &str) -> core::fmt::Result { self.write(InstructionPart::basic(s)) }
}

impl unarm::FormatIns for ArgsFormatter<'_> {
    fn options(&self) -> &unarm::Options { self.options }

    fn write_ins(&mut self, ins: &unarm::Ins) -> core::fmt::Result {
        let mut string_fmt = unarm::StringFormatter::new(self.options);
        ins.write_opcode(&mut string_fmt)?;
        let opcode = string_fmt.into_string();
        self.write(InstructionPart::Opcode(Cow::Owned(opcode), self.resolved.ins_ref.opcode))?;
        ins.write_params(self)
    }

    fn write_space(&mut self) -> core::fmt::Result {
        if self.skip_leading_space {
            self.skip_leading_space = false;
            Ok(())
        } else {
            self.write_str(" ")
        }
    }

    fn write_separator(&mut self) -> core::fmt::Result { self.write(InstructionPart::separator()) }

    fn write_uimm(&mut self, uimm: u32) -> core::fmt::Result {
        if let Some(resolved) = self.resolved.relocation
            && let RelocationFlags::Elf(elf::R_ARM_ABS32) = resolved.relocation.flags
        {
            return self.write(InstructionPart::reloc());
        }
        self.write(InstructionPart::unsigned(uimm))
    }

    fn write_simm(&mut self, simm: i32) -> core::fmt::Result {
        self.write(InstructionPart::signed(simm))
    }

    fn write_branch_target(&mut self, branch_target: unarm::BranchTarget) -> core::fmt::Result {
        if let Some(resolved) = self.resolved.relocation {
            match resolved.relocation.flags {
                RelocationFlags::Elf(elf::R_ARM_THM_XPC22)
                | RelocationFlags::Elf(elf::R_ARM_THM_PC22)
                | RelocationFlags::Elf(elf::R_ARM_PC24)
                | RelocationFlags::Elf(elf::R_ARM_XPC25)
                | RelocationFlags::Elf(elf::R_ARM_CALL)
                | RelocationFlags::Elf(elf::R_ARM_THM_PC11)
                | RelocationFlags::Elf(elf::R_ARM_THM_PC9) => {
                    return self.write(InstructionPart::reloc());
                }
                _ => {}
            }
        }
        self.write(InstructionPart::branch_dest(branch_target.addr))
    }

    fn write_reg(&mut self, reg: unarm::Reg) -> core::fmt::Result { self.write_opaque(reg) }

    fn write_status_reg(&mut self, status_reg: unarm::StatusReg) -> core::fmt::Result {
        self.write_opaque(status_reg)
    }

    fn write_status_fields(&mut self, status_fields: unarm::StatusFields) -> core::fmt::Result {
        self.write_opaque(status_fields)
    }

    fn write_shift_op(&mut self, shift_op: unarm::ShiftOp) -> core::fmt::Result {
        self.write_opaque(shift_op)
    }

    fn write_coproc(&mut self, coproc: unarm::Coproc) -> core::fmt::Result {
        self.write_opaque(coproc)
    }

    fn write_co_reg(&mut self, co_reg: unarm::CoReg) -> core::fmt::Result {
        self.write_opaque(co_reg)
    }

    fn write_aif_flags(&mut self, aif_flags: unarm::AifFlags) -> core::fmt::Result {
        self.write_opaque(aif_flags)
    }

    fn write_endianness(&mut self, endianness: unarm::Endianness) -> core::fmt::Result {
        self.write_opaque(endianness)
    }

    fn write_sreg(&mut self, sreg: unarm::Sreg) -> core::fmt::Result { self.write_opaque(sreg) }

    fn write_dreg(&mut self, dreg: unarm::Dreg) -> core::fmt::Result { self.write_opaque(dreg) }

    fn write_fpscr(&mut self, fpscr: unarm::Fpscr) -> core::fmt::Result { self.write_opaque(fpscr) }

    fn write_addr_ldr_str(&mut self, addr_ldr_str: unarm::AddrLdrStr) -> core::fmt::Result {
        addr_ldr_str.write(self)?;
        if let unarm::AddrLdrStr::Pre {
            rn: unarm::Reg::Pc,
            offset: unarm::LdrStrOffset::Imm(offset),
            ..
        } = addr_ldr_str
        {
            let thumb = self.resolved.ins_ref.opcode & (1 << 15) == 0;
            let pc_offset = if thumb { 4 } else { 8 };
            let pc = (self.resolved.ins_ref.address as u32 & !3) + pc_offset;
            self.write(InstructionPart::basic(" (->"))?;
            self.write(InstructionPart::branch_dest(pc.wrapping_add(offset as u32)))?;
            self.write(InstructionPart::basic(")"))?;
        }
        Ok(())
    }
}
