//! # Quasiquoter
//! This file contains the implementation internals of the quasiquoter provided by `quote!`.

//! This quasiquoter uses macros 2.0 hygiene to reliably access
//! items from `proc_macro`, to build a `proc_macro::TokenStream`.

use super::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};

macro_rules! quote_tt {
    (($($t:tt)*)) => { Group::new(Delimiter::Parenthesis, quote!($($t)*)) };
    ([$($t:tt)*]) => { Group::new(Delimiter::Bracket, quote!($($t)*)) };
    ({$($t:tt)*}) => { Group::new(Delimiter::Brace, quote!($($t)*)) };
    (,) => { Punct::new(',', Spacing::Alone) };
    (.) => { Punct::new('.', Spacing::Alone) };
    (:) => { Punct::new(':', Spacing::Alone) };
    (;) => { Punct::new(';', Spacing::Alone) };
    (!) => { Punct::new('!', Spacing::Alone) };
    (<) => { Punct::new('<', Spacing::Alone) };
    (>) => { Punct::new('>', Spacing::Alone) };
    (&) => { Punct::new('&', Spacing::Alone) };
    (=) => { Punct::new('=', Spacing::Alone) };
    ($i:ident) => { Ident::new(stringify!($i), Span::def_site()) };
}

macro_rules! quote_ts {
    ((@ $($t:tt)*)) => { $($t)* };
    (::) => {
        [
            TokenTree::from(Punct::new(':', Spacing::Joint)),
            TokenTree::from(Punct::new(':', Spacing::Alone)),
        ].iter()
            .cloned()
            .map(|mut x| {
                x.set_span(Span::def_site());
                x
            })
            .collect::<TokenStream>()
    };
    ($t:tt) => { TokenTree::from(quote_tt!($t)) };
}

/// Simpler version of the real `quote!` macro, implemented solely
/// through `macro_rules`, for bootstrapping the real implementation
/// (see the `quote` function), which does not have access to the
/// real `quote!` macro due to the `proc_macro` crate not being
/// able to depend on itself.
///
/// Note: supported tokens are a subset of the real `quote!`, but
/// unquoting is different: instead of `$x`, this uses `(@ expr)`.
macro_rules! quote {
    () => { TokenStream::new() };
    ($($t:tt)*) => {
        [
            $(TokenStream::from(quote_ts!($t)),)*
        ].iter().cloned().collect::<TokenStream>()
    };
}

/// Quote a `TokenStream` into a `TokenStream`.
/// This is the actual implementation of the `quote!()` proc macro.
///
/// It is loaded by the compiler in `register_builtin_macros`.
pub fn quote(stream: TokenStream) -> TokenStream {
    if stream.is_empty() {
        return quote!(crate::TokenStream::new());
    }
    let proc_macro_crate = quote!(crate);
    let mut after_dollar = false;
    let tokens = stream
        .into_iter()
        .filter_map(|tree| {
            if after_dollar {
                after_dollar = false;
                match tree {
                    TokenTree::Ident(_) => {
                        return Some(quote!(Into::<crate::TokenStream>::into(
                        Clone::clone(&(@ tree))),));
                    }
                    TokenTree::Punct(ref tt) if tt.as_char() == '$' => {}
                    _ => panic!("`$` must be followed by an ident or `$` in `quote!`"),
                }
            } else if let TokenTree::Punct(ref tt) = tree {
                if tt.as_char() == '$' {
                    after_dollar = true;
                    return None;
                }
            }

            Some(quote!(crate::TokenStream::from((@ match tree {
                TokenTree::Punct(tt) => quote!(crate::TokenTree::Punct(crate::Punct::new(
                    (@ TokenTree::from(Literal::character(tt.as_char()))),
                    (@ match tt.spacing() {
                        Spacing::Alone => quote!(crate::Spacing::Alone),
                        Spacing::Joint => quote!(crate::Spacing::Joint),
                    }),
                ))),
                TokenTree::Group(tt) => quote!(crate::TokenTree::Group(crate::Group::new(
                    (@ match tt.delimiter() {
                        Delimiter::Parenthesis => quote!(crate::Delimiter::Parenthesis),
                        Delimiter::Brace => quote!(crate::Delimiter::Brace),
                        Delimiter::Bracket => quote!(crate::Delimiter::Bracket),
                        Delimiter::None => quote!(crate::Delimiter::None),
                    }),
                    (@ quote(tt.stream())),
                ))),
                TokenTree::Ident(tt) => quote!(crate::TokenTree::Ident(crate::Ident::new(
                    (@ TokenTree::from(Literal::string(&tt.to_string()))),
                    (@ quote_span(proc_macro_crate.clone(), tt.span())),
                ))),
                TokenTree::Literal(tt) => quote!(crate::TokenTree::Literal({
                    let mut iter = (@ TokenTree::from(Literal::string(&tt.to_string())))
                        .parse::<crate::TokenStream>()
                        .unwrap()
                        .into_iter();
                    if let (Some(crate::TokenTree::Literal(mut lit)), None) =
                        (iter.next(), iter.next())
                    {
                        lit.set_span((@ quote_span(proc_macro_crate.clone(), tt.span())));
                        lit
                    } else {
                        unreachable!()
                    }
                }))
            })),))
        })
        .collect::<TokenStream>();

    if after_dollar {
        panic!("unexpected trailing `$` in `quote!`");
    }

    quote!([(@ tokens)].iter().cloned().collect::<crate::TokenStream>())
}

/// Quote a `Span` into a `TokenStream`.
/// This is needed to implement a custom quoter.
pub fn quote_span(proc_macro_crate: TokenStream, span: Span) -> TokenStream {
    let id = span.save_span();
    quote!((@ proc_macro_crate ) ::Span::recover_proc_macro_span((@ TokenTree::from(Literal::usize_unsuffixed(id)))))
}
