//! Internal implementation details for the host-guest interface.
//!
//! Note that the vast majority of this module is just providing FFI-safe
//! versions of common `std` types (e.g. `Vec`, `String`, and `Box<dyn Error>`),
//! or FFI-safe trait objects.
//!
/// If the macro generated the wrong code, this doctest would fail.
///
/// ```rust
/// use fj::{abi::INIT_FUNCTION_NAME, models::Metadata};
///
/// fj::register_model!(|_| {
///     Ok(Metadata::new("My Model", "1.2.3"))
/// });
///
/// mod x {
///     extern "C" {
///         pub fn fj_model_init(_: *mut fj::abi::Host<'_>) -> fj::abi::InitResult;
///     }
/// }
///
/// // make sure our function has the right signature
/// let func: fj::abi::InitFunction = fj_model_init;
///
/// // We can also make sure the unmangled name is correct by calling it.
///
/// let metadata: fj::models::Metadata = unsafe {
///     let mut host = Host;
///     let mut host = fj::abi::Host::from(&mut host);
///     x::fj_model_init(&mut host).unwrap().into()
/// };
///
/// assert_eq!(metadata.name, "My Model");
///
/// struct Host;
/// impl fj::models::Host for Host {
///     fn register_boxed_model(&mut self, model: Box<dyn fj::models::Model>) { todo!() }
/// }
/// ```
mod context;
pub mod ffi_safe;
mod host;
mod metadata;
mod model;

use std::any::Any;

pub use self::{
    context::Context,
    host::Host,
    metadata::{Metadata, ModelMetadata},
    model::Model,
};

/// Define the initialization routine used when registering models.
///
/// See the [`crate::model`] macro if your model can be implemented as a pure
/// function.
///
/// # Examples
///
/// ```rust
/// use fj::models::*;
///
/// fj::register_model!(|host: &mut dyn Host| {
///     host.register_model(MyModel::default());
///
///     Ok(Metadata::new(env!("CARGO_PKG_NAME"), env!("CARGO_PKG_VERSION")))
/// });
///
/// #[derive(Default)]
/// struct MyModel { }
///
/// impl Model for MyModel {
///     fn metadata(&self) -> std::result::Result<fj::models::ModelMetadata, Box<(dyn std::error::Error + Send + Sync + 'static)>> { todo!() }
///
///     fn shape(&self, ctx: &dyn Context) -> Result<fj::Shape, Error> {
///         todo!()
///     }
/// }
/// ```
#[macro_export]
macro_rules! register_model {
    ($init:expr) => {
        #[no_mangle]
        unsafe extern "C" fn fj_model_init(
            mut host: *mut $crate::abi::Host<'_>,
        ) -> $crate::abi::InitResult {
            let init: fn(
                &mut dyn $crate::models::Host,
            ) -> Result<
                $crate::models::Metadata,
                $crate::models::Error,
            > = $init;

            match init(&mut *host) {
                Ok(meta) => $crate::abi::InitResult::Ok(meta.into()),
                Err(e) => $crate::abi::InitResult::Err(e.into()),
            }
        }
    };
}

/// The signature of the function generated by [`register_model`].
///
/// ```rust
/// fj::register_model!(|_| { todo!() });
///
/// const _: fj::abi::InitFunction = fj_model_init;
/// ```
pub type InitFunction = unsafe extern "C" fn(*mut Host<'_>) -> InitResult;
pub type InitResult = ffi_safe::Result<Metadata, ffi_safe::BoxedError>;
pub type ShapeResult = ffi_safe::Result<crate::Shape, ffi_safe::BoxedError>;
pub type ModelMetadataResult =
    ffi_safe::Result<ModelMetadata, ffi_safe::BoxedError>;

/// The name of the function generated by [`register_model`].
///
pub const INIT_FUNCTION_NAME: &str = "fj_model_init";

fn on_panic(payload: Box<dyn Any + Send>) -> crate::abi::ffi_safe::String {
    let msg: &str =
        if let Some(s) = payload.downcast_ref::<std::string::String>() {
            s.as_str()
        } else if let Some(s) = payload.downcast_ref::<&str>() {
            s
        } else {
            "A panic occurred"
        };

    crate::abi::ffi_safe::String::from(msg.to_string())
}
