//! Implementation crate for `multiversion`.
extern crate proc_macro;

mod cfg;
mod dispatcher;
mod match_target;
mod multiversion;
mod target;
mod util;

use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse::Nothing, parse_macro_input, punctuated::Punctuated, ItemFn};

#[proc_macro_attribute]
pub fn multiversion(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let func = parse_macro_input!(input as ItemFn);
    match multiversion::make_multiversioned_fn(attr.into(), func) {
        Ok(tokens) => tokens.into_token_stream(),
        Err(err) => err.to_compile_error(),
    }
    .into()
}

#[proc_macro_attribute]
pub fn target(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let target = parse_macro_input!(attr as syn::LitStr);
    let func = parse_macro_input!(input as ItemFn);
    match target::make_target_fn(target, func) {
        Ok(tokens) => tokens.into_token_stream(),
        Err(err) => err.to_compile_error(),
    }
    .into()
}

#[proc_macro_attribute]
pub fn inherit_target(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    parse_macro_input!(attr as Nothing);
    let func = parse_macro_input!(input as ItemFn);
    quote! {
        __multiversion::inherit_target! { #func }
    }
    .into()
}

#[proc_macro]
pub fn selected_target(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    parse_macro_input!(input as Nothing);
    quote! {
        __multiversion::FEATURES
    }
    .into()
}

#[proc_macro_attribute]
pub fn target_cfg(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let attr = TokenStream::from(attr);
    let input = TokenStream::from(input);
    quote! {
        __multiversion::target_cfg!{ [#attr] #input }
    }
    .into()
}

#[proc_macro_attribute]
pub fn target_cfg_attr(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let attr = TokenStream::from(attr);
    let input = TokenStream::from(input);
    quote! {
        __multiversion::target_cfg_attr!{ [#attr] #input }
    }
    .into()
}

#[proc_macro]
pub fn target_cfg_f(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = TokenStream::from(input);
    quote! {
        __multiversion::target_cfg_f!{ #input }
    }
    .into()
}

#[proc_macro_attribute]
pub fn target_cfg_impl(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let meta = parse_macro_input!(attr with Punctuated::parse_terminated);
    let input = TokenStream::from(input);

    match cfg::transform(meta) {
        Ok(meta) => {
            quote! {
                #[cfg(#meta)]
                #input
            }
        }
        Err(err) => err.to_compile_error(),
    }
    .into()
}

#[proc_macro_attribute]
pub fn target_cfg_attr_impl(
    attr: proc_macro::TokenStream,
    input: proc_macro::TokenStream,
) -> proc_macro::TokenStream {
    let mut meta = parse_macro_input!(attr with Punctuated::parse_terminated);
    let input = TokenStream::from(input);

    let attr = meta.pop().unwrap();
    match cfg::transform(meta) {
        Ok(meta) => {
            quote! {
                #[cfg_attr(#meta, #attr)]
                #input
            }
        }
        Err(err) => err.to_compile_error(),
    }
    .into()
}

#[proc_macro]
pub fn target_cfg_f_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let meta = parse_macro_input!(input with Punctuated::parse_terminated);

    match cfg::transform(meta) {
        Ok(meta) => {
            quote! {
                cfg!(#meta)
            }
        }
        Err(err) => err.to_compile_error(),
    }
    .into()
}

#[proc_macro]
pub fn match_target(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let input = TokenStream::from(input);
    quote! {
        __multiversion::match_target!{ #input }
    }
    .into()
}

#[proc_macro]
pub fn match_target_impl(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let match_target = parse_macro_input!(input as match_target::MatchTarget);
    match_target.into_token_stream().into()
}
