use alloc::borrow::Cow;
use alloc::string::String;
use alloc::vec::Vec;
use core::{fmt, result, str};
#[cfg(not(feature = "std"))]
use hashbrown::HashMap;
#[cfg(feature = "std")]
use std::{boxed::Box, collections::HashMap, error, io};
use crate::endian::{Endianness, U32, U64};
use crate::{
    Architecture, BinaryFormat, ComdatKind, FileFlags, RelocationEncoding, RelocationKind,
    SectionFlags, SectionKind, SymbolFlags, SymbolKind, SymbolScope,
};
#[cfg(feature = "coff")]
mod coff;
#[cfg(feature = "coff")]
pub use coff::CoffExportStyle;
#[cfg(feature = "elf")]
pub mod elf;
#[cfg(feature = "macho")]
mod macho;
#[cfg(feature = "macho")]
pub use macho::MachOBuildVersion;
#[cfg(feature = "pe")]
pub mod pe;
#[cfg(feature = "xcoff")]
mod xcoff;
mod string;
pub use string::StringId;
mod util;
pub use util::*;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Error(String);
impl fmt::Display for Error {
    #[inline]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.write_str(&self.0)
    }
}
#[cfg(feature = "std")]
impl error::Error for Error {}
pub type Result<T> = result::Result<T, Error>;
#[derive(Debug)]
pub struct Object<'a> {
    format: BinaryFormat,
    architecture: Architecture,
    endian: Endianness,
    sections: Vec<Section<'a>>,
    standard_sections: HashMap<StandardSection, SectionId>,
    symbols: Vec<Symbol>,
    symbol_map: HashMap<Vec<u8>, SymbolId>,
    stub_symbols: HashMap<SymbolId, SymbolId>,
    comdats: Vec<Comdat>,
    pub flags: FileFlags,
    pub mangling: Mangling,
    tlv_bootstrap: Option<SymbolId>,
    #[cfg(feature = "macho")]
    macho_build_version: Option<MachOBuildVersion>,
}
impl<'a> Object<'a> {
    pub fn new(format: BinaryFormat, architecture: Architecture, endian: Endianness) -> Object<'a> {
        Object {
            format,
            architecture,
            endian,
            sections: Vec::new(),
            standard_sections: HashMap::new(),
            symbols: Vec::new(),
            symbol_map: HashMap::new(),
            stub_symbols: HashMap::new(),
            comdats: Vec::new(),
            flags: FileFlags::None,
            mangling: Mangling::default(format, architecture),
            tlv_bootstrap: None,
            #[cfg(feature = "macho")]
            macho_build_version: None,
        }
    }
    #[inline]
    pub fn format(&self) -> BinaryFormat {
        self.format
    }
    #[inline]
    pub fn architecture(&self) -> Architecture {
        self.architecture
    }
    #[inline]
    pub fn mangling(&self) -> Mangling {
        self.mangling
    }
    #[inline]
    pub fn set_mangling(&mut self, mangling: Mangling) {
        self.mangling = mangling;
    }
    #[allow(unused_variables)]
    pub fn segment_name(&self, segment: StandardSegment) -> &'static [u8] {
        match self.format {
            #[cfg(feature = "coff")]
            BinaryFormat::Coff => &[],
            #[cfg(feature = "elf")]
            BinaryFormat::Elf => &[],
            #[cfg(feature = "macho")]
            BinaryFormat::MachO => self.macho_segment_name(segment),
            _ => unimplemented!(),
        }
    }
    #[inline]
    pub fn section(&self, section: SectionId) -> &Section<'a> {
        &self.sections[section.0]
    }
    #[inline]
    pub fn section_mut(&mut self, section: SectionId) -> &mut Section<'a> {
        &mut self.sections[section.0]
    }
    pub fn set_section_data<T>(&mut self, section: SectionId, data: T, align: u64)
    where
        T: Into<Cow<'a, [u8]>>,
    {
        self.sections[section.0].set_data(data, align)
    }
    pub fn append_section_data(&mut self, section: SectionId, data: &[u8], align: u64) -> u64 {
        self.sections[section.0].append_data(data, align)
    }
    pub fn append_section_bss(&mut self, section: SectionId, size: u64, align: u64) -> u64 {
        self.sections[section.0].append_bss(size, align)
    }
    pub fn section_id(&mut self, section: StandardSection) -> SectionId {
        self.standard_sections
            .get(§ion)
            .cloned()
            .unwrap_or_else(|| {
                let (segment, name, kind, flags) = self.section_info(section);
                let id = self.add_section(segment.to_vec(), name.to_vec(), kind);
                self.section_mut(id).flags = flags;
                id
            })
    }
    pub fn add_section(&mut self, segment: Vec<u8>, name: Vec<u8>, kind: SectionKind) -> SectionId {
        let id = SectionId(self.sections.len());
        self.sections.push(Section {
            segment,
            name,
            kind,
            size: 0,
            align: 1,
            data: Cow::Borrowed(&[]),
            relocations: Vec::new(),
            symbol: None,
            flags: SectionFlags::None,
        });
        let section = &self.sections[id.0];
        for standard_section in StandardSection::all() {
            if !self.standard_sections.contains_key(standard_section) {
                let (segment, name, kind, _flags) = self.section_info(*standard_section);
                if segment == &*section.segment && name == &*section.name && kind == section.kind {
                    self.standard_sections.insert(*standard_section, id);
                }
            }
        }
        id
    }
    fn section_info(
        &self,
        section: StandardSection,
    ) -> (&'static [u8], &'static [u8], SectionKind, SectionFlags) {
        match self.format {
            #[cfg(feature = "coff")]
            BinaryFormat::Coff => self.coff_section_info(section),
            #[cfg(feature = "elf")]
            BinaryFormat::Elf => self.elf_section_info(section),
            #[cfg(feature = "macho")]
            BinaryFormat::MachO => self.macho_section_info(section),
            #[cfg(feature = "xcoff")]
            BinaryFormat::Xcoff => self.xcoff_section_info(section),
            _ => unimplemented!(),
        }
    }
    pub fn add_subsection(
        &mut self,
        section: StandardSection,
        name: &[u8],
        data: &[u8],
        align: u64,
    ) -> (SectionId, u64) {
        let section_id = if self.has_subsections_via_symbols() {
            self.set_subsections_via_symbols();
            self.section_id(section)
        } else {
            let (segment, name, kind, flags) = self.subsection_info(section, name);
            let id = self.add_section(segment.to_vec(), name, kind);
            self.section_mut(id).flags = flags;
            id
        };
        let offset = self.append_section_data(section_id, data, align);
        (section_id, offset)
    }
    fn has_subsections_via_symbols(&self) -> bool {
        match self.format {
            BinaryFormat::Coff | BinaryFormat::Elf | BinaryFormat::Xcoff => false,
            BinaryFormat::MachO => true,
            _ => unimplemented!(),
        }
    }
    fn set_subsections_via_symbols(&mut self) {
        match self.format {
            #[cfg(feature = "macho")]
            BinaryFormat::MachO => self.macho_set_subsections_via_symbols(),
            _ => unimplemented!(),
        }
    }
    fn subsection_info(
        &self,
        section: StandardSection,
        value: &[u8],
    ) -> (&'static [u8], Vec<u8>, SectionKind, SectionFlags) {
        let (segment, section, kind, flags) = self.section_info(section);
        let name = self.subsection_name(section, value);
        (segment, name, kind, flags)
    }
    #[allow(unused_variables)]
    fn subsection_name(&self, section: &[u8], value: &[u8]) -> Vec<u8> {
        debug_assert!(!self.has_subsections_via_symbols());
        match self.format {
            #[cfg(feature = "coff")]
            BinaryFormat::Coff => self.coff_subsection_name(section, value),
            #[cfg(feature = "elf")]
            BinaryFormat::Elf => self.elf_subsection_name(section, value),
            _ => unimplemented!(),
        }
    }
    #[inline]
    pub fn comdat(&self, comdat: ComdatId) -> &Comdat {
        &self.comdats[comdat.0]
    }
    #[inline]
    pub fn comdat_mut(&mut self, comdat: ComdatId) -> &mut Comdat {
        &mut self.comdats[comdat.0]
    }
    pub fn add_comdat(&mut self, comdat: Comdat) -> ComdatId {
        let comdat_id = ComdatId(self.comdats.len());
        self.comdats.push(comdat);
        comdat_id
    }
    pub fn symbol_id(&self, name: &[u8]) -> Option<SymbolId> {
        self.symbol_map.get(name).cloned()
    }
    #[inline]
    pub fn symbol(&self, symbol: SymbolId) -> &Symbol {
        &self.symbols[symbol.0]
    }
    #[inline]
    pub fn symbol_mut(&mut self, symbol: SymbolId) -> &mut Symbol {
        &mut self.symbols[symbol.0]
    }
    pub fn add_symbol(&mut self, mut symbol: Symbol) -> SymbolId {
        debug_assert!(symbol.is_undefined() || symbol.scope != SymbolScope::Unknown);
        if symbol.kind == SymbolKind::Section {
            let symbol_id = self.section_symbol(symbol.section.id().unwrap());
            if symbol.flags != SymbolFlags::None {
                self.symbol_mut(symbol_id).flags = symbol.flags;
            }
            return symbol_id;
        }
        if !symbol.name.is_empty()
            && (symbol.kind == SymbolKind::Text
                || symbol.kind == SymbolKind::Data
                || symbol.kind == SymbolKind::Tls)
        {
            let unmangled_name = symbol.name.clone();
            if let Some(prefix) = self.mangling.global_prefix() {
                symbol.name.insert(0, prefix);
            }
            let symbol_id = self.add_raw_symbol(symbol);
            self.symbol_map.insert(unmangled_name, symbol_id);
            symbol_id
        } else {
            self.add_raw_symbol(symbol)
        }
    }
    fn add_raw_symbol(&mut self, symbol: Symbol) -> SymbolId {
        let symbol_id = SymbolId(self.symbols.len());
        self.symbols.push(symbol);
        symbol_id
    }
    #[inline]
    pub fn has_uninitialized_tls(&self) -> bool {
        self.format != BinaryFormat::Coff
    }
    #[inline]
    pub fn has_common(&self) -> bool {
        self.format == BinaryFormat::MachO
    }
    pub fn add_common_symbol(&mut self, mut symbol: Symbol, size: u64, align: u64) -> SymbolId {
        if self.has_common() {
            let symbol_id = self.add_symbol(symbol);
            let section = self.section_id(StandardSection::Common);
            self.add_symbol_bss(symbol_id, section, size, align);
            symbol_id
        } else {
            symbol.section = SymbolSection::Common;
            symbol.size = size;
            self.add_symbol(symbol)
        }
    }
    pub fn add_file_symbol(&mut self, name: Vec<u8>) -> SymbolId {
        self.add_raw_symbol(Symbol {
            name,
            value: 0,
            size: 0,
            kind: SymbolKind::File,
            scope: SymbolScope::Compilation,
            weak: false,
            section: SymbolSection::None,
            flags: SymbolFlags::None,
        })
    }
    pub fn section_symbol(&mut self, section_id: SectionId) -> SymbolId {
        let section = &mut self.sections[section_id.0];
        if let Some(symbol) = section.symbol {
            return symbol;
        }
        let name = if self.format == BinaryFormat::Coff {
            section.name.clone()
        } else {
            Vec::new()
        };
        let symbol_id = SymbolId(self.symbols.len());
        self.symbols.push(Symbol {
            name,
            value: 0,
            size: 0,
            kind: SymbolKind::Section,
            scope: SymbolScope::Compilation,
            weak: false,
            section: SymbolSection::Section(section_id),
            flags: SymbolFlags::None,
        });
        section.symbol = Some(symbol_id);
        symbol_id
    }
    pub fn add_symbol_data(
        &mut self,
        symbol_id: SymbolId,
        section: SectionId,
        data: &[u8],
        align: u64,
    ) -> u64 {
        let offset = self.append_section_data(section, data, align);
        self.set_symbol_data(symbol_id, section, offset, data.len() as u64);
        offset
    }
    pub fn add_symbol_bss(
        &mut self,
        symbol_id: SymbolId,
        section: SectionId,
        size: u64,
        align: u64,
    ) -> u64 {
        let offset = self.append_section_bss(section, size, align);
        self.set_symbol_data(symbol_id, section, offset, size);
        offset
    }
    #[allow(unused_mut)]
    pub fn set_symbol_data(
        &mut self,
        mut symbol_id: SymbolId,
        section: SectionId,
        offset: u64,
        size: u64,
    ) {
        debug_assert!(self.symbol(symbol_id).scope != SymbolScope::Unknown);
        match self.format {
            #[cfg(feature = "macho")]
            BinaryFormat::MachO => symbol_id = self.macho_add_thread_var(symbol_id),
            _ => {}
        }
        let symbol = self.symbol_mut(symbol_id);
        symbol.value = offset;
        symbol.size = size;
        symbol.section = SymbolSection::Section(section);
    }
    pub fn symbol_section_and_offset(&mut self, symbol_id: SymbolId) -> Option<(SymbolId, u64)> {
        let symbol = self.symbol(symbol_id);
        if symbol.kind == SymbolKind::Section {
            return Some((symbol_id, 0));
        }
        let symbol_offset = symbol.value;
        let section = symbol.section.id()?;
        let section_symbol = self.section_symbol(section);
        Some((section_symbol, symbol_offset))
    }
    pub fn add_relocation(&mut self, section: SectionId, mut relocation: Relocation) -> Result<()> {
        let addend = match self.format {
            #[cfg(feature = "coff")]
            BinaryFormat::Coff => self.coff_fixup_relocation(&mut relocation),
            #[cfg(feature = "elf")]
            BinaryFormat::Elf => self.elf_fixup_relocation(&mut relocation)?,
            #[cfg(feature = "macho")]
            BinaryFormat::MachO => self.macho_fixup_relocation(&mut relocation),
            #[cfg(feature = "xcoff")]
            BinaryFormat::Xcoff => self.xcoff_fixup_relocation(&mut relocation),
            _ => unimplemented!(),
        };
        if addend != 0 {
            self.write_relocation_addend(section, &relocation, addend)?;
        }
        self.sections[section.0].relocations.push(relocation);
        Ok(())
    }
    fn write_relocation_addend(
        &mut self,
        section: SectionId,
        relocation: &Relocation,
        addend: i64,
    ) -> Result<()> {
        let data = self.sections[section.0].data_mut();
        let offset = relocation.offset as usize;
        match relocation.size {
            32 => data.write_at(offset, &U32::new(self.endian, addend as u32)),
            64 => data.write_at(offset, &U64::new(self.endian, addend as u64)),
            _ => {
                return Err(Error(format!(
                    "unimplemented relocation addend {:?}",
                    relocation
                )));
            }
        }
        .map_err(|_| {
            Error(format!(
                "invalid relocation offset {}+{} (max {})",
                relocation.offset,
                relocation.size,
                data.len()
            ))
        })
    }
    pub fn write(&self) -> Result<Vec<u8>> {
        let mut buffer = Vec::new();
        self.emit(&mut buffer)?;
        Ok(buffer)
    }
    #[cfg(feature = "std")]
    pub fn write_stream<W: io::Write>(&self, w: W) -> result::Result<(), Box<dyn error::Error>> {
        let mut stream = StreamingBuffer::new(w);
        self.emit(&mut stream)?;
        stream.result()?;
        stream.into_inner().flush()?;
        Ok(())
    }
    pub fn emit(&self, buffer: &mut dyn WritableBuffer) -> Result<()> {
        match self.format {
            #[cfg(feature = "coff")]
            BinaryFormat::Coff => self.coff_write(buffer),
            #[cfg(feature = "elf")]
            BinaryFormat::Elf => self.elf_write(buffer),
            #[cfg(feature = "macho")]
            BinaryFormat::MachO => self.macho_write(buffer),
            #[cfg(feature = "xcoff")]
            BinaryFormat::Xcoff => self.xcoff_write(buffer),
            _ => unimplemented!(),
        }
    }
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum StandardSegment {
    Text,
    Data,
    Debug,
}
#[allow(missing_docs)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum StandardSection {
    Text,
    Data,
    ReadOnlyData,
    ReadOnlyDataWithRel,
    ReadOnlyString,
    UninitializedData,
    Tls,
    UninitializedTls,
    TlsVariables,
    Common,
    GnuProperty,
}
impl StandardSection {
    pub fn kind(self) -> SectionKind {
        match self {
            StandardSection::Text => SectionKind::Text,
            StandardSection::Data => SectionKind::Data,
            StandardSection::ReadOnlyData => SectionKind::ReadOnlyData,
            StandardSection::ReadOnlyDataWithRel => SectionKind::ReadOnlyDataWithRel,
            StandardSection::ReadOnlyString => SectionKind::ReadOnlyString,
            StandardSection::UninitializedData => SectionKind::UninitializedData,
            StandardSection::Tls => SectionKind::Tls,
            StandardSection::UninitializedTls => SectionKind::UninitializedTls,
            StandardSection::TlsVariables => SectionKind::TlsVariables,
            StandardSection::Common => SectionKind::Common,
            StandardSection::GnuProperty => SectionKind::Note,
        }
    }
    fn all() -> &'static [StandardSection] {
        &[
            StandardSection::Text,
            StandardSection::Data,
            StandardSection::ReadOnlyData,
            StandardSection::ReadOnlyDataWithRel,
            StandardSection::ReadOnlyString,
            StandardSection::UninitializedData,
            StandardSection::Tls,
            StandardSection::UninitializedTls,
            StandardSection::TlsVariables,
            StandardSection::Common,
            StandardSection::GnuProperty,
        ]
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SectionId(usize);
#[derive(Debug)]
pub struct Section<'a> {
    segment: Vec<u8>,
    name: Vec<u8>,
    kind: SectionKind,
    size: u64,
    align: u64,
    data: Cow<'a, [u8]>,
    relocations: Vec<Relocation>,
    symbol: Option<SymbolId>,
    pub flags: SectionFlags,
}
impl<'a> Section<'a> {
    #[inline]
    pub fn name(&self) -> Option<&str> {
        str::from_utf8(&self.name).ok()
    }
    #[inline]
    pub fn segment(&self) -> Option<&str> {
        str::from_utf8(&self.segment).ok()
    }
    #[inline]
    pub fn is_bss(&self) -> bool {
        self.kind.is_bss()
    }
    pub fn set_data<T>(&mut self, data: T, align: u64)
    where
        T: Into<Cow<'a, [u8]>>,
    {
        debug_assert!(!self.is_bss());
        debug_assert_eq!(align & (align - 1), 0);
        debug_assert!(self.data.is_empty());
        self.data = data.into();
        self.size = self.data.len() as u64;
        self.align = align;
    }
    pub fn append_data(&mut self, append_data: &[u8], align: u64) -> u64 {
        debug_assert!(!self.is_bss());
        debug_assert_eq!(align & (align - 1), 0);
        if self.align < align {
            self.align = align;
        }
        let align = align as usize;
        let data = self.data.to_mut();
        let mut offset = data.len();
        if offset & (align - 1) != 0 {
            offset += align - (offset & (align - 1));
            data.resize(offset, 0);
        }
        data.extend_from_slice(append_data);
        self.size = data.len() as u64;
        offset as u64
    }
    pub fn append_bss(&mut self, size: u64, align: u64) -> u64 {
        debug_assert!(self.is_bss());
        debug_assert_eq!(align & (align - 1), 0);
        if self.align < align {
            self.align = align;
        }
        let mut offset = self.size;
        if offset & (align - 1) != 0 {
            offset += align - (offset & (align - 1));
            self.size = offset;
        }
        self.size += size;
        offset
    }
    pub fn data(&self) -> &[u8] {
        debug_assert!(!self.is_bss());
        &self.data
    }
    pub fn data_mut(&mut self) -> &mut [u8] {
        debug_assert!(!self.is_bss());
        self.data.to_mut()
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum SymbolSection {
    None,
    Undefined,
    Absolute,
    Common,
    Section(SectionId),
}
impl SymbolSection {
    #[inline]
    pub fn id(self) -> Option<SectionId> {
        if let SymbolSection::Section(id) = self {
            Some(id)
        } else {
            None
        }
    }
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct SymbolId(usize);
#[derive(Debug)]
pub struct Symbol {
    pub name: Vec<u8>,
    pub value: u64,
    pub size: u64,
    pub kind: SymbolKind,
    pub scope: SymbolScope,
    pub weak: bool,
    pub section: SymbolSection,
    pub flags: SymbolFlags<SectionId, SymbolId>,
}
impl Symbol {
    #[inline]
    pub fn name(&self) -> Option<&str> {
        str::from_utf8(&self.name).ok()
    }
    #[inline]
    pub fn is_undefined(&self) -> bool {
        self.section == SymbolSection::Undefined
    }
    #[inline]
    pub fn is_common(&self) -> bool {
        self.section == SymbolSection::Common
    }
    #[inline]
    pub fn is_local(&self) -> bool {
        self.scope == SymbolScope::Compilation
    }
}
#[derive(Debug)]
pub struct Relocation {
    pub offset: u64,
    pub size: u8,
    pub kind: RelocationKind,
    pub encoding: RelocationEncoding,
    pub symbol: SymbolId,
    pub addend: i64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ComdatId(usize);
#[derive(Debug)]
pub struct Comdat {
    pub kind: ComdatKind,
    pub symbol: SymbolId,
    pub sections: Vec<SectionId>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Mangling {
    None,
    Coff,
    CoffI386,
    Elf,
    MachO,
    Xcoff,
}
impl Mangling {
    pub fn default(format: BinaryFormat, architecture: Architecture) -> Self {
        match (format, architecture) {
            (BinaryFormat::Coff, Architecture::I386) => Mangling::CoffI386,
            (BinaryFormat::Coff, _) => Mangling::Coff,
            (BinaryFormat::Elf, _) => Mangling::Elf,
            (BinaryFormat::MachO, _) => Mangling::MachO,
            (BinaryFormat::Xcoff, _) => Mangling::Xcoff,
            _ => Mangling::None,
        }
    }
    pub fn global_prefix(self) -> Option<u8> {
        match self {
            Mangling::None | Mangling::Elf | Mangling::Coff | Mangling::Xcoff => None,
            Mangling::CoffI386 | Mangling::MachO => Some(b'_'),
        }
    }
}