use proc_macro::TokenStream;
// use proc_macro2::TokenStream as TokenStream2;

use syn::*;
use quote::quote;

#[proc_macro_attribute]
/// Creates `DllMain` under the hood, then spawns the thread with annotated function.
/// ```
/// use core::ffi::c_void;
///
/// #[dll_entry_point]
/// fn main_thread(lp_thread_parameter: *mut c_void) -> u32 {
///     println!("Hello from #RustLang!");
///     return 0;
/// }
/// ```
pub fn dll_entry_point(_: TokenStream, item: TokenStream) -> TokenStream {
    let target = parse_macro_input!(item as ItemFn);
    assert!(target.sig.asyncness.is_none());

    if let ReturnType::Type(_, t) = &target.sig.output {
        if let Type::Path(tp) = t.as_ref() {
            assert_eq!(
                tp.path.segments.first().unwrap().ident.to_string().as_str(),
                "u32",
                "Invalid return type. Expected `u32`."
            );
        } else {
            panic!("Invalid return type. Expected: `u32`.");
        }
    } else {
        panic!("Invalid signature. Expected return type: `u32`.");
    }

    assert_eq!(
        target
            .sig
            .abi
            .clone()
            .expect("Expected a `system` ABI")
            .name
            .expect("Expected a `system` ABI")
            .value()
            .as_str(),
        "system",
        "Function must be of ABI: `system`"
    );

    let fn_ident = &target.sig.ident;

    let out = quote! {
        #target

        #[no_mangle]
        unsafe extern "system" fn DllMain(
            hinst_dll: *mut core::ffi::c_void,
            fdw_reason: u32,
            _: *mut core::ffi::c_void) -> i32
        {
            if fdw_reason == 1 {
                enlit::winapi::um::processthreadsapi::CreateThread(
                    0 as *mut _,
                    0,
                    std::mem::transmute::<_, unsafe extern "system" fn(_) -> u32>(#fn_ident as usize).into(),
                    hinst_dll as _,
                    0,
                    0 as *mut _
                );
            }
            1
        }
    };
    out.into()
}