use crate::exception::Exception;
use crate::rust_str::RustStr;
use alloc::boxed::Box;
use alloc::string::{String, ToString};
use core::fmt::Display;
use core::ptr;
use core::result::Result as StdResult;
use core::slice;
use core::str;

#[repr(C)]
pub union Result {
    err: RustStr,
    ok: *const u8, // null
}

pub unsafe fn r#try<T, E>(ret: *mut T, result: StdResult<T, E>) -> Result
where
    E: Display,
{
    match result {
        Ok(ok) => {
            ptr::write(ret, ok);
            Result { ok: ptr::null() }
        }
        Err(err) => to_c_error(err.to_string()),
    }
}

unsafe fn to_c_error(msg: String) -> Result {
    let mut msg = msg;
    msg.as_mut_vec().push(b'\0');
    let ptr = msg.as_ptr();
    let len = msg.len();

    extern "C" {
        #[link_name = "cxxbridge1$error"]
        fn error(ptr: *const u8, len: usize) -> *const u8;
    }

    let copy = error(ptr, len);
    let slice = slice::from_raw_parts(copy, len);
    let string = str::from_utf8_unchecked(slice);
    let err = RustStr::from(string);
    Result { err }
}

impl Result {
    pub unsafe fn exception(self) -> StdResult<(), Exception> {
        if self.ok.is_null() {
            Ok(())
        } else {
            let err = self.err;
            let slice = slice::from_raw_parts_mut(err.ptr.as_ptr(), err.len);
            let s = str::from_utf8_unchecked_mut(slice);
            Err(Exception {
                what: Box::from_raw(s),
            })
        }
    }
}
