<commit_before>use proc_macro2::{Span, TokenStream};

use syn::{self, Data, Field, Ident, Lit, Meta};

use super::{Context, RustlerAttr};

pub fn transcoder_decorator(ast: &syn::DeriveInput) -> TokenStream {
    let ctx = Context::from_ast(ast);

    let elixir_module = get_module(ast, &ctx);

    let struct_fields = match ast.data {
        Data::Struct(ref data_struct) => &data_struct.fields,
        Data::Enum(_) => panic!("NifStruct can only be used with structs"),
        Data::Union(_) => panic!("NifStruct can only be used with structs"),
    };

    let num_lifetimes = ast.generics.lifetimes().count();
    if num_lifetimes > 1 {
        panic!("Struct can only have one lifetime argument");
    }
    let has_lifetime = num_lifetimes == 1;

    let field_atoms: Vec<TokenStream> = struct_fields
        .iter()
        .map(|field| {
            let ident = field.ident.as_ref().unwrap();
            let ident_str = ident.to_string();

            let atom_fun = Ident::new(&format!("atom_{}", ident_str), Span::call_site());

            quote! {
                atom #atom_fun = #ident_str;
            }
        })
        .collect();

    let atom_defs = quote! {
        ::rustler::rustler_atoms! {
            atom atom_struct = "__struct__";
            atom atom_module = #elixir_module;
            #(#field_atoms)*
        }
    };

    let struct_fields: Vec<_> = struct_fields.iter().collect();

    let decoder = if ctx.decode() {
        gen_decoder(&ast.ident, &struct_fields, &atom_defs, has_lifetime)
    } else {
        quote! {}
    };

    let encoder = if ctx.encode() {
        gen_encoder(&ast.ident, &struct_fields, &atom_defs, has_lifetime)
    } else {
        quote! {}
    };

    let gen = quote! {
        #decoder
        #encoder
    };

    gen
}

pub fn gen_decoder(
    struct_name: &Ident,
    fields: &[&Field],
    atom_defs: &TokenStream,
    has_lifetime: bool,
) -> TokenStream {
    let struct_type = if has_lifetime {
        quote! { #struct_name <'a> }
    } else {
        quote! { #struct_name }
    };

    let field_defs: Vec<TokenStream> = fields
        .iter()
        .map(|field| {
            let ident = field.ident.as_ref().unwrap();
            let ident_str = ident.to_string();
            let atom_fun = Ident::new(&format!("atom_{}", ident_str), Span::call_site());
            let error_message = format!("Could not decode field :{} on %{}{{}}", ident.to_string(), struct_name.to_string());
            quote! {
                #ident: match ::rustler::Decoder::decode(term.map_get(#atom_fun().encode(env))?) {
                    Err(_) => return Err(::rustler::Error::RaiseTerm(Box::new(#error_message))),
                    Ok(stuff) => stuff
                }<commit_msg>Better variable name<commit_after>use proc_macro2::{Span, TokenStream};

use syn::{self, Data, Field, Ident, Lit, Meta};

use super::{Context, RustlerAttr};

pub fn transcoder_decorator(ast: &syn::DeriveInput) -> TokenStream {
    let ctx = Context::from_ast(ast);

    let elixir_module = get_module(ast, &ctx);

    let struct_fields = match ast.data {
        Data::Struct(ref data_struct) => &data_struct.fields,
        Data::Enum(_) => panic!("NifStruct can only be used with structs"),
        Data::Union(_) => panic!("NifStruct can only be used with structs"),
    };

    let num_lifetimes = ast.generics.lifetimes().count();
    if num_lifetimes > 1 {
        panic!("Struct can only have one lifetime argument");
    }
    let has_lifetime = num_lifetimes == 1;

    let field_atoms: Vec<TokenStream> = struct_fields
        .iter()
        .map(|field| {
            let ident = field.ident.as_ref().unwrap();
            let ident_str = ident.to_string();

            let atom_fun = Ident::new(&format!("atom_{}", ident_str), Span::call_site());

            quote! {
                atom #atom_fun = #ident_str;
            }
        })
        .collect();

    let atom_defs = quote! {
        ::rustler::rustler_atoms! {
            atom atom_struct = "__struct__";
            atom atom_module = #elixir_module;
            #(#field_atoms)*
        }
    };

    let struct_fields: Vec<_> = struct_fields.iter().collect();

    let decoder = if ctx.decode() {
        gen_decoder(&ast.ident, &struct_fields, &atom_defs, has_lifetime)
    } else {
        quote! {}
    };

    let encoder = if ctx.encode() {
        gen_encoder(&ast.ident, &struct_fields, &atom_defs, has_lifetime)
    } else {
        quote! {}
    };

    let gen = quote! {
        #decoder
        #encoder
    };

    gen
}

pub fn gen_decoder(
    struct_name: &Ident,
    fields: &[&Field],
    atom_defs: &TokenStream,
    has_lifetime: bool,
) -> TokenStream {
    let struct_type = if has_lifetime {
        quote! { #struct_name <'a> }
    } else {
        quote! { #struct_name }
    };

    let field_defs: Vec<TokenStream> = fields
        .iter()
        .map(|field| {
            let ident = field.ident.as_ref().unwrap();
            let ident_str = ident.to_string();
            let atom_fun = Ident::new(&format!("atom_{}", ident_str), Span::call_site());
            let error_message = format!("Could not decode field :{} on %{}{{}}", ident.to_string(), struct_name.to_string());
            quote! {
                #ident: match ::rustler::Decoder::decode(term.map_get(#atom_fun().encode(env))?) {
                    Err(_) => return Err(::rustler::Error::RaiseTerm(Box::new(#error_message))),
                    Ok(value) => value
                }
{'fraction_non_alphanumeric': 0.1467657184272513, 'fraction_numerical': 0.0010871534698314912, 'mean_word_length': 2.339382940108893, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 27, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6119282', 'n_tokens_mistral': 1761, 'n_tokens_neox': 1715, 'n_words': 372}