use elf::{
    abi::{
        R_AARCH64_ABS64, R_AARCH64_GLOB_DAT, R_AARCH64_JUMP_SLOT, R_AARCH64_RELATIVE,
        R_AARCH64_TLSDESC, R_AARCH64_TLS_TPREL,
    },
    relocation::{Rel, Rela},
};

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

use super::{RelValue, RelaValue, Relocation};

// Descriptor method of TLS access:
// * For static access:
//      [0]: resolver function pointer
//      [1]: fixed offset of the variable with the TLS area
// * For dynamic, TODO
fn make_tlsdesc_relocation(
    dynamic: bool,
    state: &State,
    module_id: u32,
    symbol_offset: usize,
) -> RelaValue {
    if dynamic {
        todo!("Dynamic TLSDESC relocations are not yet supported")
    }
    let layout = state
        .tls_layout
        .as_ref()
        .expect("TLSDESC relocation, but no TLS segments in linked objects");
    let Some(offset) = layout.offset(module_id, symbol_offset) else {
        panic!("TLSDESC relocation against unknown module_id={module_id}, offset={symbol_offset}");
    };

    let resolver = builtins::tlsdesc_resolve_static as usize;
    RelaValue::DQWord(resolver as i64, offset as i64)
}

impl Relocation for Rel {
    type Value = RelValue;

    fn resolve(
        &self,
        _state: &State,
        _name: &str,
        _symbol: &ResolvedSymbol,
        _load_base: usize,
    ) -> Result<Option<Self::Value>, Error> {
        unimplemented!("rel-type relocations are not implemented for aarch64")
    }
}

impl Relocation for Rela {
    type Value = RelaValue;

    fn resolve(
        &self,
        state: &State,
        name: &str,
        symbol: &ResolvedSymbol,
        load_base: usize,
    ) -> Result<Option<Self::Value>, Error> {
        match *symbol {
            ResolvedSymbol::Tls(tls) => match self.r_type {
                // See make_tlsdesc_relocation()
                R_AARCH64_TLSDESC => Ok(Some(make_tlsdesc_relocation(
                    false,
                    state,
                    tls.module_id,
                    tls.offset,
                ))),
                R_AARCH64_TLS_TPREL => {
                    let layout = state.tls_layout.as_ref().unwrap();
                    let offset = layout.offset(tls.module_id, tls.offset).unwrap();
                    log::debug!("{}@tprel -> {}", name, offset);
                    Ok(Some(RelaValue::QWord(offset as _)))
                }
                _ => todo!("Unsupported relocation against TLS symbol: {}", self.r_type),
            },
            ResolvedSymbol::Null(object_id) => match self.r_type {
                // See make_tlsdesc_relocation()
                R_AARCH64_TLSDESC => Ok(Some(make_tlsdesc_relocation(
                    false,
                    state,
                    object_id + 1,
                    0,
                ))),
                // B + A
                R_AARCH64_RELATIVE => Ok(Some(RelaValue::QWord(load_base as i64 + self.r_addend))),
                _ => todo!(
                    "Unsupported relocation against NULL symbol: {}",
                    self.r_type
                ),
            },
            _ => {
                let s = symbol.value();
                if s == 0 {
                    todo!()
                }
                match self.r_type {
                    // Direct 64 bit: S + A
                    R_AARCH64_ABS64 => Ok(Some(RelaValue::QWord(s + self.r_addend))),
                    // Direct 64 bit: S
                    R_AARCH64_JUMP_SLOT | R_AARCH64_GLOB_DAT => Ok(Some(RelaValue::QWord(s))),
                    _ => todo!("Unsupported relocation: {}", self.r_type),
                }
            }
        }
    }
}
