extern crate proc_macro2;
extern crate proc_quote;

use proc_macro2::{Ident, Span, TokenStream};
use proc_quote::{quote, TokenStreamExt};

struct X;

impl quote::ToTokens for X {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        tokens.append(Ident::new("X", Span::call_site()));
    }
}

#[test]
fn test_repetition_simple() {
    let primes = &[X, X, X, X];

    assert_eq!("X X X X", quote!(#(#primes)*).to_string());

    assert_eq!("X , X , X , X ,", quote!(#(#primes,)*).to_string());

    assert_eq!("X , X , X , X", quote!(#(#primes),*).to_string());
}

#[test]
fn test_repetition_two_vars() {
    let foo = vec!["a", "b"];
    let bar = vec![true, false];

    let tokens = quote! {
        #(#foo: #bar),*
    };

    let expected = r#""a" : true , "b" : false"#;
    assert_eq!(expected, tokens.to_string());
}

#[test]
fn test_repetition_nested() {
    let nested = vec![vec!['a', 'b', 'c'], vec!['x', 'y', 'z']];

    let tokens = quote! {
        #(
            #(#nested)*
        ),*
    };

    let expected = "'a' 'b' 'c' , 'x' 'y' 'z'";
    assert_eq!(expected, tokens.to_string());
}

#[test]
fn test_var_name_conflict() {
    // The implementation of `#(...),*` uses the variable `__i` but it should be
    // fine, if a little confusing when debugging.
    let __i = vec!['a', 'b'];
    let tokens = quote! {
        #(#__i),*
    };
    let expected = "'a' , 'b'";
    assert_eq!(expected, tokens.to_string());
}

#[test]
fn test_repetition_same_iter_twice() {
    let a = 1..=3;
    let b = quote! {
        #(#a #a)*
    };
    assert_eq!("1i32 1i32 2i32 2i32 3i32 3i32", b.to_string());
}

#[test]
fn test_repetition_iterators() {
    let a = vec!["a", "b", "c"].into_iter();
    let b = 2..6;
    let c = [5, 6, 7].into_iter().map(|_| X);
    let q = quote! {
        #(#a #b #c)*
    };
    assert_eq!("\"a\" 2i32 X \"b\" 3i32 X \"c\" 4i32 X", q.to_string());
}

#[test]
fn test_repetition_slices() {
    let a = vec!["a", "b", "c"];
    let b = &[2, 3, 4, 5];
    let c = [5, 6, 7];

    let q = quote! {
        #(#a #b #c)*
    };
    assert_eq!(
        "\"a\" 2i32 5i32 \"b\" 3i32 6i32 \"c\" 4i32 7i32",
        q.to_string()
    );
}

#[test]
fn test_repetition_totokens() {
    let i = 1..3; // Iterator just to avoid infinite loop
    let a = 5;
    let b = "a";
    let c = X;

    let q = quote! {
        #(#i #a #b #c)*
    };
    assert_eq!("1i32 5i32 \"a\" X 2i32 5i32 \"a\" X", q.to_string());
}

#[test]
fn test_repetition_without_consuming() {
    // The following types should not be consumed by `quote!`
    let a = [1, 2, 3];
    let b = vec!['a', 'b', 'c'];
    let c = 5.0;

    let q1 = quote! {
        #(#a #b #c)* #(#a #b #c)* // Usable in two different patterns
    };
    let q2 = quote! {
        #(#a #b #c)* // Usable in a different `quote!`
    };
    let d = (a[0], b[0], c); // Still usable in the following code

    assert_eq!(
        "1i32 'a' 5f64 2i32 'b' 5f64 3i32 'c' 5f64 1i32 'a' 5f64 2i32 'b' 5f64 3i32 'c' 5f64",
        q1.to_string()
    );
    assert_eq!("1i32 'a' 5f64 2i32 'b' 5f64 3i32 'c' 5f64", q2.to_string());
    assert_eq!(1, d.0);
    assert_eq!('a', d.1);
    assert_eq!(5.0, d.2);
}

#[test]
fn test_repetition_trait_name_collision() {
    trait Repeat {}
    impl Repeat for X {}

    let a = 0..3; // Avoid infinite loop
    let x = X;

    let q = quote! {
        #(#a #x)*
    };

    assert_eq!("0i32 X 1i32 X 2i32 X", q.to_string());
}

#[test]
#[ignore] // TODO(#7) trait fn collision
fn test_repetition_trait_fn_collision() {
    unimplemented!("doesn't compile")

    // trait Repeat {
    //     fn __proc_quote__as_repeat(self) -> !;
    // }
    // impl Repeat for X {
    //     fn __proc_quote__as_repeat(self) -> ! { panic!("Wrong trait called.") }
    // }

    // let a = 0..3; // Avoid infinite loop
    // let x = X;

    // let q = quote! {
    //     #(#a #x)*
    // };

    // assert_eq!("0i32 X 1i32 X 2i32 X", q.to_string());
}

#[test]
#[ignore] // TODO(#7) struct method name collision
fn test_repetition_impl_fn_collision() {
    unimplemented!("doesn't compile")

    // struct R;
    // impl R {
    //     fn __proc_quote__as_repeat(self) -> ! { panic!("Wrong trait called.") }
    // }
    // impl quote::ToTokens for R {
    //     fn to_tokens(&self, tokens: &mut TokenStream) {
    //         tokens.append(Ident::new("X", Span::call_site()));
    //     }
    // }

    // let a = 0..3; // Avoid infinite loop
    // let r = R;

    // let q = quote! {
    //     #(#a #r)*
    // };

    // assert_eq!("0i32 X 1i32 X 2i32 X", q.to_string());
}
