use elf::{
    parse::ParseAt,
    relocation::{Rel, Rela},
};

use crate::{error::Error, mapping::Mapping, object::ResolvedSymbol, state::State};

#[cfg(any(target_arch = "aarch64", rust_analyzer))]
mod aarch64;
#[cfg(any(target_arch = "riscv64", rust_analyzer))]
mod riscv64;
#[cfg(any(target_arch = "x86_64", rust_analyzer))]
mod x86_64;

pub enum RelaValue {
    DQWord(i64, i64),
    QWord(i64),
}

pub enum RelValue {
    DWord(i32),
    DWordNoAddend(i32),
}

pub trait RelocationValue {
    fn write(&self, mapping: &mut Mapping, offset: u64);
}

pub trait SymbolRelocation: Relocation + ParseAt {
    fn symbol(&self) -> usize;
    fn offset(&self) -> u64;
}

pub trait Relocation {
    type Value: RelocationValue;

    fn resolve(
        &self,
        state: &State,
        name: &str,
        symbol: &ResolvedSymbol,
        load_base: usize,
    ) -> Result<Option<Self::Value>, Error>;
}

impl RelocationValue for RelaValue {
    fn write(&self, mapping: &mut Mapping, offset: u64) {
        match *self {
            Self::DQWord(v0, v1) => unsafe {
                mapping.qword(offset).write_unaligned(v0);
                mapping.qword(offset + 8).write_unaligned(v1);
            },
            Self::QWord(value) => unsafe {
                let ptr = mapping.qword(offset);
                // yggdrasil_rt::debug_trace!("write({:p}, {:#x})", ptr, value);
                ptr.write_unaligned(value)
            },
        }
    }
}

impl RelocationValue for RelValue {
    fn write(&self, mapping: &mut Mapping, offset: u64) {
        match *self {
            Self::DWord(value) => {
                let a = unsafe { mapping.dword(offset).read() };
                unsafe { mapping.dword(offset).write(value + a) };
            }
            Self::DWordNoAddend(value) => {
                unsafe { mapping.dword(offset).write(value) };
            }
        }
    }
}

impl SymbolRelocation for Rel {
    fn symbol(&self) -> usize {
        self.r_sym as _
    }

    fn offset(&self) -> u64 {
        self.r_offset
    }
}

impl SymbolRelocation for Rela {
    fn symbol(&self) -> usize {
        self.r_sym as _
    }

    fn offset(&self) -> u64 {
        self.r_offset
    }
}
