use proc_macro2::{Delimiter, Group, Span, TokenStream};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::{parse_macro_input, Attribute, DeriveInput, Ident};

#[proc_macro_derive(CharacterSheet, attributes(Input, InputExpr, Field, FieldExpr, Seperator, Access, AccessSet))]
pub fn derive(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let DeriveInput { ident, data, .. } = parse_macro_input!(input as DeriveInput);
    let mut output = quote! {
        impl CharacterSheet for #ident 
    };

    // Create inputs impl
    let mut impls = impl_inputs(&data);
    impls.extend(impl_fields(&data));
    impls.extend(impl_display(&data));
    impls.extend(impl_get(&data));
    impls.extend(impl_set(&data));
    output.append(Group::new(Delimiter::Brace, impls));

    
    output.into()
}

fn get_type_ident(ty: &syn::Type) -> Option<Ident> {
    match ty {
        syn::Type::Path(p) => {
            if p.path.is_ident("String") { 
                Some(Ident::new("Text", Span::call_site()))
            } else if p.path.is_ident("i32") {
                Some(Ident::new("Number", Span::call_site()))
            } else if p.path.is_ident("bool") {
                Some(Ident::new("Bool", Span::call_site()))
            } else { None }           
        }
        _ => panic!("Invalid data  type"),
    }
}
fn get_type_ident_set(ty: &syn::Type) -> Option<Ident> {
    match ty {
        syn::Type::Path(p) => {
            if p.path.is_ident("String") { 
                Some(Ident::new("as_text", Span::call_site()))
            } else if p.path.is_ident("i32") {
                Some(Ident::new("as_num", Span::call_site()))
            } else if p.path.is_ident("bool") {
                Some(Ident::new("as_bool", Span::call_site()))
            } else { None }
        }
        _ => panic!("Invalid data  type"),
    }
}
fn get_access_level(attrs: &Vec<Attribute>, ident: &str) -> Ident {
    attrs.iter()
        .find(|a| a.path.is_ident(ident))
        .map(|a| a.parse_args().unwrap_or(None))
        .flatten()
        .unwrap_or(Ident::new(&"Owner", Span::call_site()))
}

fn impl_inputs(data: &syn::Data) -> TokenStream {
    let mut output = TokenStream::new();
    output.extend(quote! {
        fn inputs(&self, access: AccessLevel) -> Vec<(String, EntryType)>
    });
    let mut items = TokenStream::new();
    let mut count = 0_usize;
    if let syn::Data::Struct(ds) = data {
        if let syn::Fields::Named(fs) = &ds.fields {
            for f in &fs.named {
                let name = f.ident.clone().unwrap();
                let attr = f.attrs.iter().find(|a| a.path.is_ident("Input"));
                if let Some(attr) = attr {
                    let arg: syn::LitStr = attr.parse_args().expect("No arguments supplied for Input attribute, usage: `Input(\"Name\")`");
                    let exp: Option<&syn::Attribute> = f.attrs.iter().find(|a| a.path.is_ident("InputExpr"));
                    let access = get_access_level(&f.attrs, "AccessSet");
                    count += 1;
                    if let Some(attr) = exp {
                        let exp: syn::Meta = attr.parse_meta().expect("Failed to parse MetaList!");
                        if let syn::Meta::List(l) = exp {
                            let from_input = &l.nested[1];
                            items.extend(quote! {
                                if AccessLevel::#access.has_access(access) {
                                    v.push((#arg.to_string(), self.#from_input()));
                                }
                            });
                        }
                        else {
                            panic!("Failed parsing InputExpr attribute, expected `(&mut self, EntryType), (&self) -> EntryType`");
                        }
                    }
                    else {
                        let t = get_type_ident(&f.ty).expect(&format!("Invalid type for input: {}", name));
                        items.extend(quote! {
                            if AccessLevel::#access.has_access(access) {
                                v.push((#arg.to_string(), EntryType::#t(self.#name.clone())));
                            }
                        });
                    }
                }
            }
        }
    }
    let mut vec = quote! { let mut v = Vec::with_capacity(#count); };
    vec.extend(items);
    vec.extend(quote! { v });
    output.append(Group::new(Delimiter::Brace, vec));

    output
}

fn impl_fields(data: &syn::Data) -> TokenStream {
    let mut output = TokenStream::new();
    output.extend(quote! {
        fn fields(&self) -> Vec<(String, EntryType)>
    });

    let mut items = TokenStream::new();
    if let syn::Data::Struct(ds) = data {
        if let syn::Fields::Named(fs) = &ds.fields {
            for f in &fs.named {
                let name = f.ident.clone().unwrap();
                let input_attr = f.attrs.iter().find(|a| a.path.is_ident("Input"));
                if let Some(attr) = input_attr {
                    let arg: syn::LitStr = attr.parse_args().expect("No arguments supplied for Input attribute, usage: `Input(\"Name\")`");
                    let exp = f.attrs.iter().find(|a| a.path.is_ident("InputExpr"));
                    if let Some(attr) = exp {
                        let exp: syn::Meta = attr.parse_meta().expect("Failed to parse MetaList!");
                        if let syn::Meta::List(l) = exp {
                            let from_input = &l.nested[1];
                            items.extend(quote! {
                                (#arg.to_string(), self.#from_input()),
                            })
                        }
                        else {
                            panic!("Failed parsing InputExpr attribute, expected `(&mut self, EntryType), (&self) -> EntryType`");
                        }
                    }
                    else {
                        let t = get_type_ident(&f.ty).expect(&format!("Invalid type for input: {}", name));
                        items.extend(quote! {
                            (#arg.to_string(), EntryType::#t(self.#name.clone())),
                        });
                    }
                }
                let field_attr = f.attrs.iter().find(|a| a.path.is_ident("Field"));
                if let Some(attr) = field_attr {
                    let t = get_type_ident(&f.ty).expect(&format!("Invalid type for field: {}", name));
                    let arg: syn::LitStr = attr.parse_args().expect("Invalid arguments for Field attribute");
                    let field_expr = f.attrs.iter().find(|a| a.path.is_ident("FieldExpr"));
                    if let Some(exp) = field_expr {
                        let exp: syn::Expr = exp.parse_args().expect("Invalid expression");
                        items.extend(quote! {
                            (#arg.to_string(), EntryType::Number(#exp)),
                        })
                    }
                    else {
                        // No expression, guess we just push this and nothing special (should prob be an input but mehhh)
                        items.extend(quote! {
                            (#arg.to_string(), EntryType::#t(self.#name.clone())),
                        });
                    }
                    
                }
            }
        }
    }
    let mut vec = quote! { Vec::from };
    vec.append(Group::new(Delimiter::Parenthesis, Group::new(Delimiter::Bracket, items).to_token_stream()));
    output.append(Group::new(Delimiter::Brace, vec));

    output
}

fn impl_get(data: &syn::Data) -> TokenStream {
    let mut output = TokenStream::new();
    output.extend(quote! {
        fn get(&self, entry: &str, access: AccessLevel) -> Option<EntryType>
    });
    let mut match_hands = TokenStream::new();
    if let syn::Data::Struct(ds) = data {
        if let syn::Fields::Named(fs) = &ds.fields {
            for f in &fs.named {
                let name = f.ident.clone().unwrap();
                let access = get_access_level(&f.attrs, "Access");
                let input_attr = f.attrs.iter().find(|a| a.path.is_ident("Input"));
                if let Some(attr) = input_attr {
                    let arg: syn::LitStr = attr.parse_args().expect("No arguments supplied for Input attribute, usage: `Input(\"Name\")`");
                    let exp: Option<&syn::Attribute> = f.attrs.iter().find(|a| a.path.is_ident("InputExpr"));
                    if let Some(attr) = exp {
                        let exp: syn::Meta = attr.parse_meta().expect("Failed to parse MetaList!");
                        if let syn::Meta::List(l) = exp {
                            let from_input = &l.nested[1];
                            match_hands.extend(quote! {
                                #arg => if AccessLevel::#access.has_access(access) { Some(self.#from_input()) } else { None },
                            });
                        }
                        else {
                            panic!("Failed parsing InputExpr attribute, expected `(&mut self, EntryType), (&self) -> EntryType`");
                        }
                    }
                    else {
                        let t = get_type_ident(&f.ty).expect(&format!("Invalid type for input: {}", name));
                        match_hands.extend(quote! {
                            #arg => if AccessLevel::#access.has_access(access) { Some(EntryType::#t(self.#name.clone())) } else { None },
                        });
                    }
                }
                let field_attr = f.attrs.iter().find(|a| a.path.is_ident("Field"));
                if let Some(attr) = field_attr {
                    let t = get_type_ident(&f.ty).expect(&format!("Invalid type for field: {}", name));
                    let arg: syn::LitStr = attr.parse_args().expect("Invalid arguments for Field attribute");
                    let field_expr = f.attrs.iter().find(|a| a.path.is_ident("FieldExpr"));
                    if let Some(exp) = field_expr {
                        let exp: syn::Expr = exp.parse_args().expect("Invalid expression");
                        match_hands.extend(quote! {
                            #arg => if AccessLevel::#access.has_access(access) { Some(EntryType::Number(#exp)) } else { None },
                        })
                    }
                    else {
                        // No expression, guess we just push this and nothing special (should prob be an input but mehhh)
                        match_hands.extend(quote! {
                            #arg => if AccessLevel::#access.has_access(access) { Some(EntryType::#t(self.#name.clone())) } else { None },
                        });
                    }
                }
            }
        }
    }
    match_hands.extend(quote! { _ => None, });
    let mut vec = quote! { match(entry) };
    vec.append(Group::new(Delimiter::Brace, match_hands));
    output.append(Group::new(Delimiter::Brace, vec));

    output
}

fn impl_set(data: &syn::Data) -> TokenStream {
    let mut output = TokenStream::new();
    output.extend(quote! {
        fn set(&mut self, entry: &str, value: EntryType, access: AccessLevel)
    });

    let mut match_hands = TokenStream::new();
    if let syn::Data::Struct(ds) = data {
        if let syn::Fields::Named(fs) = &ds.fields {
            for f in &fs.named {
                let name = f.ident.clone().unwrap();
                let input_attr = f.attrs.iter().find(|a| a.path.is_ident("Input"));
                let access = get_access_level(&f.attrs, "AccessSet");
                if let Some(attr) = input_attr {
                    let arg: syn::LitStr = attr.parse_args().expect("No arguments supplied for Input attribute, usage: `Input(\"Name\")`");
                    let exp = f.attrs.iter().find(|a| a.path.is_ident("InputExpr"));
                    if let Some(attr) = exp {
                        let exp: syn::Meta = attr.parse_meta().expect("Failed to parse MetaList!");
                        if let syn::Meta::List(l) = exp {
                            let to_input = &l.nested[0];
                            match_hands.extend(quote! {
                                #arg => if AccessLevel::#access.has_access(access) { self.#to_input(value) },
                            });
                        }
                        else {
                            panic!("Failed parsing InputExpr attribute, expected `(&mut self, EntryType), (&self) -> EntryType`");
                        }
                    }
                    else {
                        let t = get_type_ident_set(&f.ty).expect(&format!("Invalid type for input: {}", name));
                        match_hands.extend(quote! {
                            #arg => if AccessLevel::#access.has_access(access) { self.#name = value.#t() },
                        });
                    }
                }
            }
        }
    }
    match_hands.extend(quote! { _ => {} });
    let mut vec = quote! { match(entry) };
    vec.append(Group::new(Delimiter::Brace, match_hands));
    output.append(Group::new(Delimiter::Brace, vec));

    output
}

fn impl_display(data: &syn::Data) -> TokenStream {
    let mut output = TokenStream::new();
    output.extend(quote! {
        fn display(&self, access: AccessLevel) -> Vec<Option<(String, EntryType)>>
    });

    let mut items = TokenStream::new();
    let mut count = 0_usize;
    if let syn::Data::Struct(ds) = data {
        if let syn::Fields::Named(fs) = &ds.fields {
            for f in &fs.named {
                let name = f.ident.clone().unwrap();
                if f.attrs.iter().any(|a| a.path.is_ident("Seperator")) {
                    items.extend(quote! { v.push(None); })
                }
                let access = get_access_level(&f.attrs, "Access");
                count += 1;
                let input_attr = f.attrs.iter().find(|a| a.path.is_ident("Input"));
                if let Some(attr) = input_attr {
                    let arg: syn::LitStr = attr.parse_args().expect("No arguments supplied for Input attribute, usage: `Input(\"Name\")`");
                    let exp = f.attrs.iter().find(|a| a.path.is_ident("InputExpr"));
                    if let Some(attr) = exp {
                        let exp: syn::Meta = attr.parse_meta().expect("Failed to parse MetaList!");
                        if let syn::Meta::List(l) = exp {
                            let from_input = &l.nested[1];
                            items.extend(quote! {
                                if AccessLevel::#access.has_access(access) {
                                    v.push(Some((#arg.to_string(), self.#from_input())));
                                }
                            })
                        }
                        else {
                            panic!("Failed parsing InputExpr attribute, expected `(&mut self, EntryType), (&self) -> EntryType`");
                        }
                    }
                    else {
                        let t = get_type_ident(&f.ty).expect(&format!("Invalid type for input: {}", name));
                        items.extend(quote! {
                            if AccessLevel::#access.has_access(access) {
                                Some((#arg.to_string(), EntryType::#t(self.#name.clone())));
                            }
                        });
                    }
                }
                let field_attr = f.attrs.iter().find(|a| a.path.is_ident("Field"));
                if let Some(attr) = field_attr {
                    let t = get_type_ident(&f.ty).expect(&format!("Invalid type for field: {}", name));
                    let arg: syn::LitStr = attr.parse_args().expect("Invalid arguments for Field attribute");
                    let field_expr = f.attrs.iter().find(|a| a.path.is_ident("FieldExpr"));
                    if let Some(exp) = field_expr {
                        let exp: syn::Expr = exp.parse_args().expect("Invalid expression");
                        items.extend(quote! {
                            if AccessLevel::#access.has_access(access) {
                                Some((#arg.to_string(), EntryType::Number(#exp)));
                            }
                        })
                    }
                    else {
                        // No expression, guess we just push this and nothing special (should prob be an input but mehhh)
                        items.extend(quote! {
                            if AccessLevel::#access.has_access(access) {
                                Some((#arg.to_string(), EntryType::#t(self.#name.clone())));
                            }
                        });
                    }
                    
                }
            }
        }
    }
    let mut vec = quote! { let mut v = Vec::with_capacity(#count); };
    vec.extend( items);
    vec.extend(quote! { v });
    output.append(Group::new(Delimiter::Brace, vec));

    output
}
