use crate::unix::c_str::CString;
use alloc::boxed::Box;

#[repr(C)]
pub enum RTLDState {
    /// Mapping change is complete.
    RT_CONSISTENT,
    /// Beginning to add a new object.
    RT_ADD,
    /// Beginning to remove an object mapping.
    RT_DELETE,
}

/// Data structure for sharing debugging information from the
/// run-time dynamic linker for loaded ELF shared objects.
#[repr(C)]
pub struct RTLDDebug {
    /// Version number for this protocol.
    r_version: i32,
    /// Head of the chain of loaded objects.
    r_map: *mut LinkMap,
    //struct link_map *r_map;
    /// This is the address of a function internal to the run-time linker,
    /// that will always be called when the linker begins to map in a
    /// library or unmap it, and again when the mapping change is complete.
    /// The debugger can set a breakpoint at this address if it wants to
    /// notice shared object mapping changes.
    pub r_brk: extern "C" fn(),

    /// This state value describes the mapping change taking place when
    /// the `r_brk' address is called.
    pub state: RTLDState,

    ///  Base address the linker is loaded at.
    pub r_ldbase: usize,
}

impl RTLDDebug {
    const NEW: Self = RTLDDebug {
        r_version: 1,
        r_map: 0 as *mut LinkMap,
        r_brk: _dl_debug_state,
        state: RTLDState::RT_CONSISTENT,
        r_ldbase: 0,
    };

    pub fn insert(&mut self, l_addr: usize, name: &str, l_ld: usize) {
        if self.r_map.is_null() {
            self.r_map = LinkMap::new_with_args(l_addr, name, l_ld);
        } else {
            unsafe { (*self.r_map).add_object(l_addr, name, l_ld) };
        }
        return;
    }
    pub fn insert_first(&mut self, l_addr: usize, name: &str, l_ld: usize) {
        if self.r_map.is_null() {
            self.r_map = LinkMap::new_with_args(l_addr, name, l_ld);
        } else {
            let tmp = self.r_map;
            self.r_map = LinkMap::new_with_args(l_addr, name, l_ld);
            unsafe { (*self.r_map).link(&mut *tmp) };
        }
        return;
    }
}

#[repr(C)]
struct LinkMap {
    /* These members are part of the protocol with the debugger.
    This is the same format used in SVR4.  */
    /// Difference between the address in the ELF
    /// file and the addresses in memory.
    l_addr: usize,
    /// Absolute file name object was found in.
    l_name: *const ::c_char,
    /// Dynamic section of the shared object.
    l_ld: usize,
    l_next: *mut LinkMap,
    l_prev: *mut LinkMap,
}

impl LinkMap {
    fn new() -> *mut Self {
        let map = Box::new(LinkMap {
            l_addr: 0,
            l_name: 0 as *const ::c_char,
            l_ld: 0,
            l_next: 0 as *mut LinkMap,
            l_prev: 0 as *mut LinkMap,
        });
        Box::into_raw(map)
    }
    fn link(&mut self, map: &mut LinkMap) {
        map.l_prev = self as *mut LinkMap;
        self.l_next = map as *mut LinkMap;
    }
    fn new_with_args(l_addr: usize, name: &str, l_ld: usize) -> *mut Self {
        let map = LinkMap::new();
        unsafe {
            (*map).l_addr = l_addr;
            (*map).l_ld = l_ld;
            let c_name = CString::new(name).unwrap();
            (*map).l_name = c_name.into_raw() as *const ::c_char;
        }
        map
    }

    fn add_object(&mut self, l_addr: usize, name: &str, l_ld: usize) {
        let node = LinkMap::new_with_args(l_addr, name, l_ld);
        let mut last = self;
        while !last.l_next.is_null() {
            last = unsafe { last.l_next.as_mut() }.unwrap();
        }
        unsafe {
            (*node).l_prev = last;
            (*last).l_next = node;
        }
    }
}

/*
 * Gdb may be looking for this fuction with that exact name and set
 * break point there
 */
#[linkage = "weak"]
#[no_mangle]
pub extern "C" fn _dl_debug_state() {}

#[no_mangle]
pub static mut _r_debug: RTLDDebug = RTLDDebug::NEW;
