#![allow(unused_features)]
#![feature(f16)]
#![feature(f128)]
// `STATUS_DLL_NOT_FOUND` on i686 MinGW, not worth looking into.
#![cfg(not(all(target_arch = "x86", target_os = "windows", target_env = "gnu")))]

macro_rules! basic {
    (
        fn_name: $fn_name:ident,
        CFn: $CFn:ty,
        CArgs: $CArgs:ty,
        CRet: $CRet:ty,
        RustFn: $RustFn:ty,
        RustArgs: $RustArgs:ty,
        RustRet: $RustRet:ty,
        path: $path:path,
        attrs: [$($attr:meta),*],
        extra: [$($extra_tt:tt)*],
        fn_extra: $fn_extra:expr,
    ) => {
        $(#[$attr])*
        #[allow(dead_code)]
        pub mod $fn_name {
            type CFnTy<'a> = $CFn;
            type RustFnTy = $RustFn;
            type RustArgsTy = $RustArgs;
            type RustRetTy = $RustRet;
            const PATH: &str = stringify!($path);
            const A: &[&str] = &[$($extra_tt)*];
            fn foo(a: f32) -> f32 {
                $fn_extra(a)
            }
        }
    };
}

mod test_basic {
    libm_macros::for_each_function! {
        callback: basic,
        emit_types: all,
        skip: [sin, cos],
        attributes: [
            // just some random attributes
            #[allow(clippy::pedantic)]
            #[allow(dead_code)]
            [sinf, cosf]
        ],
        extra: ["foo", "bar"],
        fn_extra: match MACRO_FN_NAME {
            sin => |x| x + 2.0,
            cos | cosf => |x: f32| x.MACRO_FN_NAME_NORMALIZED(),
            _ => |_x| 100.0
        }
    }
}

macro_rules! basic_no_extra {
    (
        fn_name: $fn_name:ident,
        attrs: [$($attr:meta),*],
    ) => {
        $(#[$attr])*
        mod $fn_name {}
    };
}

mod test_basic_no_extra {
    // Test with no extra, no skip, and no attributes
    libm_macros::for_each_function! {
        callback: basic_no_extra,
    }
}

mod test_only {
    // Test that only works
    libm_macros::for_each_function! {
        callback: basic_no_extra,
        only: [sin, sinf],
    }
}

macro_rules! specified_types {
    (
        fn_name: $fn_name:ident,
        RustFn: $RustFn:ty,
        RustArgs: $RustArgs:ty,
        attrs: [$($attr:meta),*],
    ) => {
        $(#[$attr])*
        #[allow(dead_code)]
        mod $fn_name {
            type RustFnTy = $RustFn;
            type RustArgsTy = $RustArgs;
        }
    };
}

mod test_emit_types {
    // Test that we can specify a couple types to emit
    libm_macros::for_each_function! {
        callback: specified_types,
        emit_types: [RustFn, RustArgs],
    }
}

#[test]
fn test_skip_f16_f128() {
    macro_rules! skip_f16_f128 {
        (
        fn_name: $fn_name:ident,
        attrs: [$($attr:meta),*],
        extra: $vec:ident,
    ) => {
            $vec.push(stringify!($fn_name));
        };
    }

    let mut v = Vec::new();
    // Test with no extra, no skip, and no attributes
    libm_macros::for_each_function! {
        callback: skip_f16_f128,
        skip_f16_f128: true,
        extra: v,
    }

    for name in v {
        assert!(!name.contains("f16"), "{name}");
        assert!(!name.contains("f128"), "{name}");
    }
}

#[test]
fn test_fn_extra_expansion() {
    macro_rules! fn_extra_expansion {
        (
            fn_name: $fn_name:ident,
            attrs: [$($attr:meta),*],
            fn_extra: $vec:expr,
        ) => {
            $vec.push(stringify!($fn_name));
        };
    }

    let mut vf16 = Vec::new();
    let mut vf32 = Vec::new();
    let mut vf64 = Vec::new();
    let mut vf128 = Vec::new();
    let mut vbuiltins = Vec::new();

    // Test with no extra, no skip, and no attributes
    libm_macros::for_each_function! {
        callback: fn_extra_expansion,
        fn_extra: match MACRO_FN_NAME {
            ALL_F16 => vf16,
            ALL_F32 => vf32,
            ALL_F64 => vf64,
            ALL_F128 => vf128,
            ALL_BUILTINS => vbuiltins,
        }
    }

    // Skip functions with a suffix after the type spec
    vf16.retain(|name| !name.ends_with("_r"));
    vf32.retain(|name| !name.ends_with("_r"));
    vf64.retain(|name| !name.ends_with("_r"));
    vf128.retain(|name| !name.ends_with("_r"));

    for name in vf16 {
        assert!(name.ends_with("f16"), "{name}");
    }
    for name in vf32 {
        assert!(name.ends_with("f"), "{name}");
    }
    for name in vf128 {
        assert!(name.ends_with("f128"), "{name}");
    }

    // Nothing to assert here
    let _ = vf64;
    let _ = vbuiltins;
}
