Documentation
use crate::{V7MAX, V21MAX, kw};
use quote::quote;

pub fn serialize_struct(
    input: syn::DeriveInput,
    cratename: syn::Path,
) -> syn::Result<proc_macro2::TokenStream> {
    let name = &input.ident;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
    let fields = match &input.data {
        syn::Data::Struct(data) => &data.fields,
        _ => unreachable!(),
    };
    let write = fields
        .iter()
        .enumerate()
        .map(|(idx, field)| match &field.ident {
            Some(ident) => quote!(::#cratename::Write::write(&self.#ident, __w);),
            None => {
                let l = proc_macro2::Literal::usize_unsuffixed(idx);
                quote!(::#cratename::Write::write(&self.#l, __w);)
            }
        });
    let len_s = fields
        .iter()
        .enumerate()
        .map(|(idx, field)| match &field.ident {
            Some(ident) => quote!(::#cratename::Write::len_s(&self.#ident)),
            None => {
                let l = proc_macro2::Literal::usize_unsuffixed(idx);
                quote!(::#cratename::Write::len_s(&self.#l))
            }
        });

    Ok(quote! {
        #[automatically_derived]
        impl #impl_generics ::#cratename::Write for #name #ty_generics #where_clause {
            #[inline]
            unsafe fn write(&self, __w: &mut ::#cratename::Writer) {
                unsafe {
                    #(#write)*
                }
            }

            #[inline]
            fn len_s(&self) -> usize {
                let mut __l = 0usize;
                #(__l += #len_s;)*
                __l
            }
        }
    })
}

pub fn serialize_enum(
    input: syn::DeriveInput,
    cratename: syn::Path,
) -> syn::Result<proc_macro2::TokenStream> {
    let name = &input.ident;
    let mut repr = None;
    let mut varint = false;
    let variants = match &input.data {
        syn::Data::Enum(data) => &data.variants,
        _ => unreachable!(),
    };
    let len = variants.len();
    for attr in &input.attrs {
        if attr.path().is_ident("mser") {
            attr.meta.require_list()?.parse_args::<kw::varint>()?;
            varint = true;
        }
    }
    for attr in &input.attrs {
        if attr.path().is_ident("repr") {
            attr.parse_nested_meta(|meta| {
                if meta.path.is_ident("u8") {
                    if varint && len > V7MAX {
                        repr = Some(quote!(::#cratename::V21(*self as u32)));
                    } else {
                        repr = Some(quote!(*self as u8));
                    }
                } else if meta.path.is_ident("u16") {
                    if varint && len > V7MAX {
                        repr = Some(quote!(::#cratename::V21(*self as u32)));
                    } else {
                        repr = Some(quote!(*self as u16));
                    }
                } else if meta.path.is_ident("u32") {
                    if varint && len > V21MAX {
                        repr = Some(quote!(::#cratename::V32(*self as u32)));
                    } else if varint && len > V7MAX {
                        repr = Some(quote!(::#cratename::V21(*self as u32)));
                    } else {
                        repr = Some(quote!(*self as u32));
                    }
                } else if meta.path.is_ident("u64") {
                    if varint && len > u32::MAX as usize {
                        repr = Some(quote!(::#cratename::V64(*self as u64)));
                    } else if varint && len > V21MAX {
                        repr = Some(quote!(::#cratename::V32(*self as u64 as u32)));
                    } else if varint && len > V7MAX {
                        repr = Some(quote!(::#cratename::V21(*self as u64 as u32)));
                    } else {
                        repr = Some(quote!(*self as u64));
                    }
                }
                Ok(())
            })?;
        }
    }

    let repr =
        repr.ok_or_else(|| syn::Error::new_spanned(&input, "expected `#[repr(...)]` attribute"))?;
    let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();

    Ok(quote! {
        #[automatically_derived]
        impl #impl_generics ::#cratename::Write for #name #ty_generics #where_clause {
            #[inline]
            unsafe fn write(&self, w: &mut ::#cratename::Writer) {
                unsafe {
                    ::#cratename::Write::write(&(#repr), w);
                }
            }

            #[inline]
            fn len_s(&self) -> usize {
                ::#cratename::Write::len_s(&(#repr))
            }
        }
    })
}