//! dlfcn implementation for Redox, following http://pubs.opengroup.org/onlinepubs/7908799/xsh/dlfcn.h.html

use core::{
    ptr, str,
    sync::atomic::{AtomicUsize, Ordering},
};
use crate::unix::c_str::*;
use crate::ld_so::tcb::Tcb;

static ERROR_NOT_SUPPORTED: &'static CStr = c_str!("dlfcn not supported");

#[thread_local]
static ERROR: AtomicUsize = AtomicUsize::new(0);

#[no_mangle]
pub unsafe extern "C" fn dladdr(_addr: *mut ::c_void, info: *mut ::Dl_info) -> ::c_int {
    //TODO
    (*info).dli_fname = ptr::null();
    (*info).dli_fbase = ptr::null_mut();
    (*info).dli_sname = ptr::null();
    (*info).dli_saddr = ptr::null_mut();
    0
}

#[no_mangle]
pub unsafe extern "C" fn dlopen(cfilename: *const ::c_char, _flags: ::c_int) -> *mut ::c_void {
    //TODO support all sort of flags

    let filename = if cfilename.is_null() {
        None
    } else {
        Some(str::from_utf8_unchecked(
            CStr::from_ptr(cfilename).to_bytes(),
        ))
    };

    let tcb = match Tcb::current() {
        Some(tcb) => tcb,
        None => {
            ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
            return ptr::null_mut();
        }
    };
    if tcb.linker_ptr.is_null() {
        ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
        return ptr::null_mut();
    }
    let mut linker = (&*tcb.linker_ptr).lock();

    let cbs_c = linker.cbs.clone();
    let cbs = cbs_c.borrow();

    let id = match (cbs.load_library)(&mut linker, filename) {
        Err(_err) => {
            ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
            return ptr::null_mut();
        }
        Ok(id) => id,
    };

    id as *mut ::c_void
}

#[no_mangle]
pub unsafe extern "C" fn dlsym(handle: *mut ::c_void, symbol: *const ::c_char) -> *mut ::c_void {
    if symbol.is_null() {
        ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
        return ptr::null_mut();
    }

    let symbol_str = str::from_utf8_unchecked(CStr::from_ptr(symbol).to_bytes());

    let tcb = match Tcb::current() {
        Some(tcb) => tcb,
        None => {
            ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
            return ptr::null_mut();
        }
    };

    if tcb.linker_ptr.is_null() {
        ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
        return ptr::null_mut();
    }

    let linker = (&*tcb.linker_ptr).lock();
    let cbs_c = linker.cbs.clone();
    let cbs = cbs_c.borrow();
    match (cbs.get_sym)(&linker, handle as usize, symbol_str) {
        Some(sym) => sym,
        _ => {
            ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
            ptr::null_mut()
        }
    }
}

#[no_mangle]
pub unsafe extern "C" fn dlclose(handle: *mut ::c_void) -> ::c_int {
    let tcb = match Tcb::current() {
        Some(tcb) => tcb,
        None => {
            ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
            return -1;
        }
    };

    if tcb.linker_ptr.is_null() {
        ERROR.store(ERROR_NOT_SUPPORTED.as_ptr() as usize, Ordering::SeqCst);
        return -1;
    };
    let mut linker = (&*tcb.linker_ptr).lock();
    let cbs_c = linker.cbs.clone();
    let cbs = cbs_c.borrow();
    (cbs.unload)(&mut linker, handle as usize);
    0
}

#[no_mangle]
pub extern "C" fn dlerror() -> *mut ::c_char {
    ERROR.swap(0, Ordering::SeqCst) as *mut ::c_char
}
