extern crate proc_macro;
use proc_macro::{TokenStream,TokenTree,Ident,Delimiter};

#[proc_macro_derive(CrimtagMappable)]
pub fn derive_crimtag_mappable(s: TokenStream) -> TokenStream {
    let mut i = s.into_iter();
    while let Some(t) = i.next() {
        if let TokenTree::Ident(ident) = t &&
            ident.to_string() == "struct" {
            break;
        }
    }
    let name = if let Some(TokenTree::Ident(ident)) = i.next() {
        ident.to_string()
    } else {
        panic!("No name found in struct.");
    };

    // TODO: Expand this to include the generic parameters and the where? at
    // least the parameters.
    println!("struct name: {:?}", name);

    let mut newfunc = format!(
r#"impl<'a> crimtag::MappedStructure<'a> for {} {{
    fn get_value(&'a self, id: &str) -> Option<MappedValue<'a>> {{
        match id {{
"#, name);

    // We're going to cheat. Every field name is followed directly by a colon,
    // so all we really need to do is hunt for colons and then take the
    // previous item.

    while let Some(n) = i.next() {
        if let TokenTree::Group(g) = &n &&
            g.delimiter() == Delimiter::Brace {
            // Operate on the struct members.
            let mut gi = g.stream().into_iter();
            let mut cur = 0usize;
            let mut tok = [gi.next(), gi.next()];
            loop {
                if let Some(TokenTree::Punct(ch)) = &tok[(cur+1)%2] &&
                    ch.as_char() == ':' &&
                    let Some(TokenTree::Ident(id)) = &tok[cur] {
                    println!("!!!!!!!!!! -> {}", id);
                    newfunc.push_str(&format!(
r#"            "{}" => Some(self.{}.into()),
"#, id, id));
                }
                println!(" - {:?} {:?}", tok[cur], tok[(cur+1)%2] );
                // Update next.
                if let Some(ni) = gi.next() {
                    tok[cur].replace( ni );
                    cur = (cur+1)%2;
                } else {
                    break;
                }
            }
        }
    }
    
    newfunc.push_str(
r#"            _ => None,
        }
    }
}"#);

    println!();
    println!();
    println!("{}", newfunc);
    println!();
    println!();

    newfunc.parse().unwrap()
}

