//! A set of convenience macros for our wasmtime-c-api crate.
//!
//! These are intended to mirror the macros in the `wasm.h` header file and
//! largely facilitate the `declare_ref` macro.

extern crate proc_macro;

use proc_macro2::{Ident, TokenStream, TokenTree};
use quote::quote;

fn extract_ident(input: proc_macro::TokenStream) -> Ident {
    let input = TokenStream::from(input);
    let i = match input.into_iter().next().unwrap() {
        TokenTree::Ident(i) => i,
        _ => panic!("expected an ident"),
    };
    let name = i.to_string();
    assert!(name.ends_with("_t"));
    return i;
}

#[proc_macro]
pub fn declare_own(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let ty = extract_ident(input);
    let name = ty.to_string();
    let delete = quote::format_ident!("{}_delete", &name[..name.len() - 2]);

    (quote! {
        #[no_mangle]
        pub extern fn #delete(_: Box<#ty>) {}
    })
    .into()
}

#[proc_macro]
pub fn declare_ty(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let ty = extract_ident(input);
    let name = ty.to_string();
    let copy = quote::format_ident!("{}_copy", &name[..name.len() - 2]);

    (quote! {
        wasmtime_c_api_macros::declare_own!(#ty);

        #[no_mangle]
        pub extern fn #copy(src: &#ty) -> Box<#ty> {
            Box::new(src.clone())
        }
    })
    .into()
}

#[proc_macro]
pub fn declare_ref(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let ty = extract_ident(input);
    let name = ty.to_string();
    let prefix = &name[..name.len() - 2];
    let copy = quote::format_ident!("{}_copy", prefix);
    let same = quote::format_ident!("{}_same", prefix);
    let get_host_info = quote::format_ident!("{}_get_host_info", prefix);
    let set_host_info = quote::format_ident!("{}_set_host_info", prefix);
    let set_host_info_final = quote::format_ident!("{}_set_host_info_with_finalizer", prefix);
    let as_ref = quote::format_ident!("{}_as_ref", prefix);
    let as_ref_const = quote::format_ident!("{}_as_ref_const", prefix);

    (quote! {
        wasmtime_c_api_macros::declare_own!(#ty);

        #[no_mangle]
        pub extern fn #copy(src: &#ty) -> Box<#ty> {
            Box::new(src.clone())
        }

        #[no_mangle]
        pub extern fn #same(_a: &#ty, _b: &#ty) -> bool {
            eprintln!("`{}` is not implemented", stringify!(#same));
            std::process::abort();
        }

        #[no_mangle]
        pub extern fn #get_host_info(a: &#ty) -> *mut std::os::raw::c_void {
            std::ptr::null_mut()
        }

        #[no_mangle]
        pub extern fn #set_host_info(a: &#ty, info: *mut std::os::raw::c_void) {
            eprintln!("`{}` is not implemented", stringify!(#set_host_info));
            std::process::abort();
        }

        #[no_mangle]
        pub extern fn #set_host_info_final(
            a: &#ty,
            info: *mut std::os::raw::c_void,
            finalizer: Option<extern "C" fn(*mut std::os::raw::c_void)>,
        ) {
            eprintln!("`{}` is not implemented", stringify!(#set_host_info_final));
            std::process::abort();
        }

        #[no_mangle]
        pub extern fn #as_ref(a: &#ty) -> Box<crate::wasm_ref_t> {
            eprintln!("`{}` is not implemented", stringify!(#as_ref));
            std::process::abort();
        }

        #[no_mangle]
        pub extern fn #as_ref_const(a: &#ty) -> Box<crate::wasm_ref_t> {
            eprintln!("`{}` is not implemented", stringify!(#as_ref_const));
            std::process::abort();
        }

        // TODO: implement `wasm_ref_as_#name#`
        // TODO: implement `wasm_ref_as_#name#_const`
    })
    .into()
}
