use elf::{
    abi::{
        R_X86_64_64, R_X86_64_DTPMOD64, R_X86_64_DTPOFF64, R_X86_64_GLOB_DAT, R_X86_64_JUMP_SLOT,
        R_X86_64_RELATIVE, R_X86_64_TPOFF64,
    },
    relocation::{Rel, Rela},
};

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

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

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 x86_64")
    }
}

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 {
                // Object ID (index into DTV) of the object containing this symbol
                R_X86_64_DTPMOD64 => Ok(Some(RelaValue::QWord(tls.module_id as _))),
                R_X86_64_DTPOFF64 => Ok(Some(RelaValue::QWord(tls.offset as _))),
                R_X86_64_TPOFF64 => {
                    // Need to extract fixed global offset
                    let tls_layout = state.tls_layout.as_ref().unwrap();
                    log::info!("TPOFF64: {name} -> module_id={}", tls.module_id);
                    // Offset from TLS start
                    let offset = tls_layout.offset(tls.module_id, tls.offset).unwrap();
                    let offset_from_tp = -((tls_layout.tp_offset - offset) as i64);
                    log::debug!("{}@tpoff -> {}", name, offset_from_tp);

                    Ok(Some(RelaValue::QWord(offset_from_tp)))
                }

                _ => todo!("Unsupported relocation against TLS symbol: {}", self.r_type),
            },
            &ResolvedSymbol::Null(object_id) => match self.r_type {
                // TLS module ID
                R_X86_64_DTPMOD64 => Ok(Some(RelaValue::QWord((object_id + 1) as _))),
                // B + A
                R_X86_64_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 {
                    // S
                    R_X86_64_JUMP_SLOT | R_X86_64_GLOB_DAT => Ok(Some(RelaValue::QWord(s))),
                    // S + A
                    R_X86_64_64 => Ok(Some(RelaValue::QWord(s + self.r_addend))),
                    _ => todo!("Unsupported relocation type: {}", self.r_type),
                }
            }
        }
    }
}
