use elf::{
    abi::{
        R_RISCV_64, R_RISCV_JUMP_SLOT, R_RISCV_RELATIVE, R_RISCV_TLS_DTPMOD64, R_RISCV_TLS_DTPREL64,
    },
    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> {
        Err(Error::UnhandledObject)
    }
}

impl Relocation for Rela {
    type Value = RelaValue;

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

        match *symbol {
            ResolvedSymbol::Tls(tls) => match self.r_type {
                R_RISCV_TLS_DTPMOD64 => Ok(Some(RelaValue::QWord(tls.module_id as _))),
                R_RISCV_TLS_DTPREL64 => Ok(Some(RelaValue::QWord(tls.offset as _))),
                _ => {
                    log::error!(
                        "Unhandled riscv64 relocation against TLS symbol: {:#x}",
                        self.r_type
                    );
                    Err(Error::UnhandledObject)
                }
            },
            ResolvedSymbol::Null(object_id) => match self.r_type {
                R_RISCV_TLS_DTPMOD64 => Ok(Some(RelaValue::QWord((object_id + 1) as _))),
                R_RISCV_RELATIVE => Ok(Some(RelaValue::QWord(load_base as i64 + self.r_addend))),
                _ => {
                    log::error!(
                        "Unhandled riscv64 relocation against NULL: {:#x}",
                        self.r_type
                    );
                    Err(Error::UnhandledObject)
                }
            },
            _ => {
                let s = symbol.value();
                if s == 0 {
                    todo!()
                }
                match self.r_type {
                    R_RISCV_64 => Ok(Some(RelaValue::QWord(s + self.r_addend))),
                    R_RISCV_JUMP_SLOT => Ok(Some(RelaValue::QWord(s))),
                    _ => {
                        log::error!("Unhandled riscv64 relocation: {:#x}", self.r_type);
                        Err(Error::UnhandledObject)
                    }
                }
            }
        }
    }
}
