use crate::error::Error;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use syn::parse_quote;

enum FuncToBuild {
    NValues,
    GenInput,
    FromValues,
    HandleOutputs,
    IsSimilar,
    NBools,
    FromBools,
    DataSize,
    Pack,
    Unpack,
}

impl FuncToBuild {
    fn signature(&self) -> TokenStream {
        match self {
            FuncToBuild::NValues => {
                quote! { fn n_values() -> usize }
            }
            FuncToBuild::GenInput => {
                quote! { fn gen_input(values: &mut Vec<EvalValue>) -> Self }
            }
            FuncToBuild::FromValues => {
                quote! { fn from_values(mut values: &[EvalValue]) -> Self }
            }
            FuncToBuild::HandleOutputs => {
                quote! { fn handle_outputs(&self, outputs: &mut Vec<EvalValue>) }
            }
            FuncToBuild::IsSimilar => {
                quote! { fn is_similar(&self, other: &Self) -> bool }
            }
            FuncToBuild::NBools => {
                quote! { fn n_bools() -> usize }
            }
            FuncToBuild::FromBools => {
                quote! { fn from_bools(mut bools: &[bool]) -> Self }
            }
            FuncToBuild::DataSize => {
                quote! { fn data_size(acc: &mut Vec<DataSize>) }
            }
            FuncToBuild::Pack => {
                quote! { fn pack(&self, locations: &mut &[PackLocation], containers: &mut [BaseField])}
            }
            FuncToBuild::Unpack => {
                quote! { fn unpack(locations: &mut &[PackLocation], containers: &[BaseField]) -> Self }
            }
        }
    }

    pub fn build(&self, data: &syn::Data) -> TokenStream {
        match data {
            syn::Data::Struct(s) => {
                let signature = self.signature();
                let types = s.fields.iter().map(|f| &f.ty);
                let inside = match &s.fields {
                    syn::Fields::Named(f) => {
                        let name = f.named.iter().map(|f| f.ident.as_ref().unwrap());
                        match self {
                            FuncToBuild::NValues => {
                                if f.named.is_empty() {
                                    quote! { 0 }
                                } else {
                                    quote! {
                                        #(<#types>::n_values())+*
                                    }
                                }
                            }
                            FuncToBuild::GenInput => {
                                quote! {
                                    Self {
                                        #(#name: <#types>::gen_input(values)),*
                                    }
                                }
                            }
                            FuncToBuild::FromValues => {
                                quote! {
                                    Self {
                                        #(#name: <#types>::from_mut_values(&mut values)),*
                                    }
                                }
                            }
                            FuncToBuild::HandleOutputs => {
                                quote! {
                                    #(self.#name.handle_outputs(outputs);)*
                                }
                            }
                            FuncToBuild::IsSimilar => {
                                if f.named.is_empty() {
                                    quote! { true }
                                } else {
                                    quote! {
                                        #(self.#name.is_similar(&other.#name))&&*
                                    }
                                }
                            }
                            FuncToBuild::NBools => {
                                if f.named.is_empty() {
                                    quote! { 0 }
                                } else {
                                    quote! {
                                        #(<#types>::n_bools())+*
                                    }
                                }
                            }
                            FuncToBuild::FromBools => {
                                quote! {
                                    Self {
                                        #(#name: <#types>::from_mut_bools(&mut bools)),*
                                    }
                                }
                            }
                            FuncToBuild::DataSize => {
                                quote! {
                                    #(<#types>::data_size(acc);)*
                                }
                            }
                            FuncToBuild::Pack => {
                                quote! {
                                    #(self.#name.pack(locations, containers);)*
                                }
                            }
                            FuncToBuild::Unpack => {
                                quote! {
                                    Self {
                                        #(#name: <#types>::unpack(locations, containers)),*
                                    }
                                }
                            }
                        }
                    }
                    syn::Fields::Unnamed(f) => {
                        let name = (0..f.unnamed.len()).map(|x| {
                            syn::LitInt::new(&x.to_string(), proc_macro2::Span::call_site())
                        });
                        match self {
                            FuncToBuild::NValues => {
                                if f.unnamed.is_empty() {
                                    quote! { 0 }
                                } else {
                                    quote! {
                                        #(<#types>::n_values())+*
                                    }
                                }
                            }
                            FuncToBuild::GenInput => {
                                quote! {
                                    Self(
                                        #(<#types>::gen_input(values)),*
                                    )
                                }
                            }
                            FuncToBuild::FromValues => {
                                quote! {
                                    Self(
                                        #(<#types>::from_mut_values(&mut values)),*
                                    )
                                }
                            }
                            FuncToBuild::HandleOutputs => {
                                quote! {
                                    #(self.#name.handle_outputs(outputs);)*
                                }
                            }
                            FuncToBuild::IsSimilar => {
                                if f.unnamed.is_empty() {
                                    quote! { true }
                                } else {
                                    quote! {
                                        #(self.#name.is_similar(&other.#name))&&*
                                    }
                                }
                            }
                            FuncToBuild::NBools => {
                                if f.unnamed.is_empty() {
                                    quote! { 0 }
                                } else {
                                    quote! {
                                        #(<#types>::n_bools())+*
                                    }
                                }
                            }
                            FuncToBuild::FromBools => {
                                quote! {
                                    Self(
                                        #(<#types>::from_mut_bools(&mut bools)),*
                                    )
                                }
                            }
                            FuncToBuild::DataSize => {
                                quote! {
                                    #(<#types>::data_size(acc);)*
                                }
                            }
                            FuncToBuild::Pack => {
                                quote! {
                                    #(self.#name.pack(locations, containers);)*
                                }
                            }
                            FuncToBuild::Unpack => {
                                quote! {
                                    Self (
                                        #(<#types>::unpack(locations, containers)),*
                                    )
                                }
                            }
                        }
                    }
                    syn::Fields::Unit => match self {
                        FuncToBuild::NValues => {
                            quote! {
                                0
                            }
                        }
                        FuncToBuild::GenInput => {
                            quote! {
                                Self
                            }
                        }
                        FuncToBuild::FromValues => {
                            quote! {
                                Self
                            }
                        }
                        FuncToBuild::HandleOutputs => {
                            quote! {}
                        }
                        FuncToBuild::IsSimilar => {
                            quote! {true}
                        }
                        FuncToBuild::NBools => {
                            quote! {
                                0
                            }
                        }
                        FuncToBuild::FromBools => {
                            quote! {
                                Self
                            }
                        }
                        FuncToBuild::DataSize => {
                            quote! {}
                        }
                        FuncToBuild::Pack => {
                            quote! {}
                        }
                        FuncToBuild::Unpack => {
                            quote! {
                                Self
                            }
                        }
                    },
                };
                quote! {
                    #signature {
                        #inside
                    }
                }
            }
            syn::Data::Enum(e) => {
                Error::new_spanned(e.enum_token, "Enums are not supported.").to_token_stream()
            }
            syn::Data::Union(u) => {
                Error::new_spanned(u.union_token, "Unions are not supported.").to_token_stream()
            }
        }
    }
}

pub fn arcis_type_macro(item: TokenStream) -> TokenStream {
    let input = match syn::parse2::<syn::DeriveInput>(item) {
        Ok(i) => i,
        Err(e) => {
            return e.to_compile_error();
        }
    };
    let generics = add_trait_bounds(input.generics);
    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    let name = input.ident;
    let n_values = FuncToBuild::NValues.build(&input.data);
    let gen_input = FuncToBuild::GenInput.build(&input.data);
    let handle_outputs = FuncToBuild::HandleOutputs.build(&input.data);
    let from_values = FuncToBuild::FromValues.build(&input.data);
    let is_similar = FuncToBuild::IsSimilar.build(&input.data);
    let n_bools = FuncToBuild::NBools.build(&input.data);
    let from_bools = FuncToBuild::FromBools.build(&input.data);
    let data_size = FuncToBuild::DataSize.build(&input.data);
    let pack = FuncToBuild::Pack.build(&input.data);
    let unpack = FuncToBuild::Unpack.build(&input.data);

    quote! {
        // The generated impl.
        impl #impl_generics arcis_type::ArcisType for #name #ty_generics #where_clause {
            #n_values
            #gen_input
            #handle_outputs
            #from_values
            #is_similar
            #n_bools
            #from_bools
            #data_size
            #pack
            #unpack
        }
    }
}
fn add_trait_bounds(mut generics: syn::Generics) -> syn::Generics {
    for param in &mut generics.params {
        if let syn::GenericParam::Type(ref mut type_param) = *param {
            type_param.bounds.push(parse_quote!(arcis_type::ArcisType));
        }
    }
    generics
}

#[cfg(test)]
mod tests {
    use super::*;
    #[ignore = "used for debugging only"]
    #[test]
    fn test_arcis_type_macro() {
        let input = quote! {
        struct TupleStruct(u8);

            };
        let output = arcis_type_macro(input);
        println!("{}", output);
        let syntax_tree = syn::parse_file(&output.to_string()).unwrap();
        let formatted = prettyplease::unparse(&syntax_tree);
        print!("{}", formatted);
    }
}
