use std::cell::RefCell;
use std::ffi::c_void;
use std::ffi::CStr;
use std::ffi::CString;

struct Person {
    name: String,
    lucky_number: i32,
}

impl Person {
    fn new(name: &str, lucky_number: i32) -> Person {
        Person {
            name: String::from(name),
            lucky_number: lucky_number,
        }
    }

    fn get_name(&self) -> String {
        String::from(&self.name)
    }

    fn set_name(&mut self, new: &str) {
        self.name = new.to_string();
    }

    fn get_lucky_number(&self) -> i32 {
        self.lucky_number
    }
}

type CPerson = c_void;

#[no_mangle]
pub extern "C" fn person_new(
    _class: *const i8,
    name: *const i8,
    lucky_number: i32,
) -> *mut CPerson {
    let name = unsafe { CStr::from_ptr(name) };
    let name = name.to_string_lossy().into_owned();
    Box::into_raw(Box::new(Person::new(&name, lucky_number))) as *mut CPerson
}

#[no_mangle]
pub extern "C" fn person_name(p: *mut CPerson) -> *const i8 {
    thread_local!(
        static KEEP: RefCell<Option<CString>> = RefCell::new(None);
    );

    let p = unsafe { &*(p as *mut Person) };
    let name = CString::new(p.get_name()).unwrap();
    let ptr = name.as_ptr();
    KEEP.with(|k| {
        *k.borrow_mut() = Some(name);
    });
    ptr
}

#[no_mangle]
pub extern "C" fn person_rename(p: *mut CPerson, new: *const i8) {
    let new = unsafe { CStr::from_ptr(new) };
    let p = unsafe { &mut *(p as *mut Person) };
    if let Ok(new) = new.to_str() {
        p.set_name(new);
    }
}

#[no_mangle]
pub extern "C" fn person_lucky_number(p: *mut CPerson) -> i32 {
    let p = unsafe { &*(p as *mut Person) };
    p.get_lucky_number()
}

#[allow(non_snake_case)]
#[no_mangle]
pub extern "C" fn person_DESTROY(p: *mut CPerson) {
    unsafe { Box::from_raw(p as *mut Person) };
}

#[cfg(test)]
mod test;