//! Native Functions.
//!
//! This module creates the helper `NativeFunc` that let us call WebAssembly
//! functions with the native ABI, that is:
//!
//! ```ignore
//! let add_one = instance.exports.get_function("function_name")?;
//! let add_one_native: NativeFunc<i32, i32> = add_one.native().unwrap();
//! ```
use std::marker::PhantomData;

use crate::externals::function::{
    FunctionDefinition, HostFunctionDefinition, VMDynamicFunction, VMDynamicFunctionWithEnv,
    VMDynamicFunctionWithoutEnv, WasmFunctionDefinition,
};
use crate::{FromToNativeWasmType, Function, RuntimeError, Store, WasmTypeList};
use std::panic::{catch_unwind, AssertUnwindSafe};
use wasmer_engine::ExportFunction;
use wasmer_types::NativeWasmType;
use wasmer_vm::{VMDynamicFunctionContext, VMFunctionBody, VMFunctionEnvironment, VMFunctionKind};

/// A WebAssembly function that can be called natively
/// (using the Native ABI).
pub struct NativeFunc<Args = (), Rets = ()> {
    definition: FunctionDefinition,
    store: Store,
    exported: ExportFunction,
    _phantom: PhantomData<(Args, Rets)>,
}

unsafe impl<Args, Rets> Send for NativeFunc<Args, Rets> {}

impl<Args, Rets> NativeFunc<Args, Rets>
where
    Args: WasmTypeList,
    Rets: WasmTypeList,
{
    pub(crate) fn new(
        store: Store,
        exported: ExportFunction,
        definition: FunctionDefinition,
    ) -> Self {
        Self {
            definition,
            store,
            exported,
            _phantom: PhantomData,
        }
    }

    pub(crate) fn vmctx(&self) -> VMFunctionEnvironment {
        self.exported.vm_function.vmctx
    }

    pub(crate) fn address(&self) -> *const VMFunctionBody {
        self.exported.vm_function.address
    }

    pub(crate) fn arg_kind(&self) -> VMFunctionKind {
        self.exported.vm_function.kind
    }
}

/*
impl<Args, Rets> From<&NativeFunc<Args, Rets>> for VMExportFunction
where
    Args: WasmTypeList,
    Rets: WasmTypeList,
{
    fn from(other: &NativeFunc<Args, Rets>) -> Self {
        let signature = FunctionType::new(Args::wasm_types(), Rets::wasm_types());
        Self {
            address: other.address,
            vmctx: other.vmctx,
            signature,
            kind: other.arg_kind,
            call_trampoline: None,
            instance_allocator: None,
        }
    }
}*/

impl<Args, Rets> From<&NativeFunc<Args, Rets>> for ExportFunction
where
    Args: WasmTypeList,
    Rets: WasmTypeList,
{
    fn from(other: &NativeFunc<Args, Rets>) -> Self {
        other.exported.clone()
    }
}

impl<Args, Rets> From<NativeFunc<Args, Rets>> for Function
where
    Args: WasmTypeList,
    Rets: WasmTypeList,
{
    fn from(other: NativeFunc<Args, Rets>) -> Self {
        Self {
            store: other.store,
            definition: other.definition,
            exported: other.exported.clone(),
        }
    }
}

macro_rules! impl_native_traits {
    (  $( $x:ident ),* ) => {
        #[allow(unused_parens, non_snake_case)]
        impl<$( $x , )* Rets> NativeFunc<( $( $x ),* ), Rets>
        where
            $( $x: FromToNativeWasmType, )*
            Rets: WasmTypeList,
        {
            /// Call the typed func and return results.
            pub fn call(&self, $( $x: $x, )* ) -> Result<Rets, RuntimeError> {
                match self.definition {
                    FunctionDefinition::Wasm(WasmFunctionDefinition {
                        trampoline
                    }) => {
                        // TODO: when `const fn` related features mature more, we can declare a single array
                        // of the correct size here.
                        let mut params_list = [ $( $x.to_native().to_binary() ),* ];
                        let mut rets_list_array = Rets::empty_array();
                        let rets_list = rets_list_array.as_mut();
                        let using_rets_array;
                        let args_rets: &mut [i128] = if params_list.len() > rets_list.len() {
                            using_rets_array = false;
                            params_list.as_mut()
                        } else {
                            using_rets_array = true;
                            for (i, &arg) in params_list.iter().enumerate() {
                                rets_list[i] = arg;
                            }
                            rets_list.as_mut()
                        };
                        unsafe {
                            wasmer_vm::wasmer_call_trampoline(
                                self.vmctx(),
                                trampoline,
                                self.address(),
                                args_rets.as_mut_ptr() as *mut u8,
                            )
                        }?;
                        let num_rets = rets_list.len();
                        if !using_rets_array && num_rets > 0 {
                            let src_pointer = params_list.as_ptr();
                            let rets_list = &mut rets_list_array.as_mut()[0] as *mut i128;
                            unsafe {
                                // TODO: we can probably remove this copy by doing some clever `transmute`s.
                                // we know it's not overlapping because `using_rets_array` is false
                                std::ptr::copy_nonoverlapping(src_pointer,
                                                              rets_list,
                                                              num_rets);
                            }
                        }
                        Ok(Rets::from_array(rets_list_array))
                        // TODO: When the Host ABI and Wasm ABI are the same, we could do this instead:
                        // but we can't currently detect whether that's safe.
                        //
                        // let results = unsafe {
                        //     wasmer_vm::catch_traps_with_result(self.vmctx, || {
                        //         let f = std::mem::transmute::<_, unsafe extern "C" fn( *mut VMContext, $( $x, )*) -> Rets::CStruct>(self.address());
                        //         // We always pass the vmctx
                        //         f( self.vmctx, $( $x, )* )
                        //     }).map_err(RuntimeError::from_trap)?
                        // };
                        // Ok(Rets::from_c_struct(results))

                    }
                    FunctionDefinition::Host(HostFunctionDefinition {
                        has_env
                    }) => {
                        match self.arg_kind() {
                            VMFunctionKind::Static => {
                                let results = catch_unwind(AssertUnwindSafe(|| unsafe {
                                    let f = std::mem::transmute::<_, unsafe extern "C" fn( VMFunctionEnvironment, $( $x, )*) -> Rets::CStruct>(self.address());
                                    // We always pass the vmctx
                                    f( self.vmctx(), $( $x, )* )
                                })).map_err(|e| RuntimeError::new(format!("{:?}", e)))?;
                                Ok(Rets::from_c_struct(results))
                            },
                            VMFunctionKind::Dynamic => {
                                let params_list = [ $( $x.to_native().to_value() ),* ];
                                let results = if !has_env {
                                    type VMContextWithoutEnv = VMDynamicFunctionContext<VMDynamicFunctionWithoutEnv>;
                                    unsafe {
                                        let ctx = self.vmctx().host_env as *mut VMContextWithoutEnv;
                                        (*ctx).ctx.call(&params_list)?
                                    }
                                } else {
                                    type VMContextWithEnv = VMDynamicFunctionContext<VMDynamicFunctionWithEnv<std::ffi::c_void>>;
                                    unsafe {
                                        let ctx = self.vmctx().host_env as *mut VMContextWithEnv;
                                        (*ctx).ctx.call(&params_list)?
                                    }
                                };
                                let mut rets_list_array = Rets::empty_array();
                                let mut_rets = rets_list_array.as_mut() as *mut [i128] as *mut i128;
                                for (i, ret) in results.iter().enumerate() {
                                    unsafe {
                                        ret.write_value_to(mut_rets.add(i));
                                    }
                                }
                                Ok(Rets::from_array(rets_list_array))
                            }
                        }
                    },
                }

            }
        }

        #[allow(unused_parens)]
        impl<'a, $( $x, )* Rets> crate::exports::ExportableWithGenerics<'a, ($( $x ),*), Rets> for NativeFunc<( $( $x ),* ), Rets>
        where
            $( $x: FromToNativeWasmType, )*
            Rets: WasmTypeList,
        {
            fn get_self_from_extern_with_generics(_extern: &crate::externals::Extern) -> Result<Self, crate::exports::ExportError> {
                use crate::exports::Exportable;
                crate::Function::get_self_from_extern(_extern)?.native().map_err(|_| crate::exports::ExportError::IncompatibleType)
            }
        }
    };
}

impl_native_traits!();
impl_native_traits!(A1);
impl_native_traits!(A1, A2);
impl_native_traits!(A1, A2, A3);
impl_native_traits!(A1, A2, A3, A4);
impl_native_traits!(A1, A2, A3, A4, A5);
impl_native_traits!(A1, A2, A3, A4, A5, A6);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16);
impl_native_traits!(A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17);
impl_native_traits!(
    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18
);
impl_native_traits!(
    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19
);
impl_native_traits!(
    A1, A2, A3, A4, A5, A6, A7, A8, A9, A10, A11, A12, A13, A14, A15, A16, A17, A18, A19, A20
);
