/*!
# Foreign Function Interface

This module contains all functions that interact with Ocaml via the foreign function interface.
*/

use std::rc::Rc;

pub mod analysis;
pub mod serde;

/// Helper function for catching panics at the ffi-border.
/// If a panic occurs while executing F and that panic unwinds the stack,
/// the panic is caught and an Ocaml failwith exception is thrown instead.
///
/// Stack unwinding through a panic across a ffi-boundary is undefined behaviour.
/// As of Rust 1.41 catching panics at ffi-borders is still not the default behaviour,
/// since it would break backwards compatibility with some crates depending on this undefined behaviour.
/// Throwing an Ocaml failwith exception instead allows stack unwinding and better error messages.
/// Note that the Ocaml exception should *not* be caught,
/// since recovering from it may lead to undefined behavior on the Rust side.
fn failwith_on_panic<F, T>(closure: F) -> T
where
    F: FnOnce() -> T,
{
    match std::panic::catch_unwind(std::panic::AssertUnwindSafe(closure)) {
        Ok(value) => value,
        Err(_) => {
            // Throw an Ocaml failwith-exception.
            // This may not be safe if the exception is caught and recovered from on the Ocaml side!
            // We assume that these errors are only caught for error printing but not for recovering from it.
            ocaml::runtime::failwith("Rust-Panic catched at FFI-boundary");
            std::process::abort();
        }
    }
}

/// This is a convenience trait for objects that may be sent as opaque objects across the ffi-boundary to Ocaml.
/// For that they are wrapped as Rc<T>.
/// Note that this trait does not prevent memory leaks in itself!
/// Whenever such an object is created and sent across the ffi-boundary,
/// the finalizer must be attached to it on the Ocaml side!
trait OcamlSendable: std::marker::Sized {
    /// Pack the object into an Ocaml value
    fn to_ocaml(self) -> ocaml::Value {
        let boxed_val = Rc::new(self);
        ocaml::Value::nativeint(Rc::into_raw(boxed_val) as isize)
    }

    /// Unpack an object that is stored as a `Rc<T>` wrapped in an Ocaml value.
    ///
    /// Note that the caller has to ensure that the wrapped object has the correct type.
    unsafe fn from_ocaml(ocaml_val: &ocaml::Value) -> &Self {
        let ptr: *const Self = ocaml_val.nativeint_val() as *const Self;
        ptr.as_ref().unwrap()
    }

    /// Unpack a `Rc<T>` object wrapped in an Ocaml value and return a clone of it.
    ///
    /// Note that the caller has to ensure that the wrapped object has the correct type.
    unsafe fn from_ocaml_rc(ocaml_val: &ocaml::Value) -> Rc<Self> {
        let ptr: *const Self = ocaml_val.nativeint_val() as *const Self;
        let rc_box = Rc::from_raw(ptr);
        let rc_clone = rc_box.clone(); // Increasing the reference count by 1
        let _ = Rc::into_raw(rc_box); // Do not decrease the reference count when rc_box goes out of scope!
        rc_clone
    }

    fn ocaml_finalize(ocaml_val: ocaml::Value) {
        let ptr: *const Self = ocaml_val.nativeint_val() as *const Self;
        let _ = unsafe { Rc::from_raw(ptr) };
    }
}
