Documentation
use crate::{Attrs, Ty, V7MAX, V21MAX, ident_case, parse_fields, ty};
use alloc::string::String;
use alloc::vec::Vec;
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 fields = parse_fields(fields)?;
    let write = fields
        .iter()
        .map(|(field, attrs, member)| write_ty(&cratename, field, attrs, member));
    let len_s = fields
        .iter()
        .map(|(field, attrs, member)| len_ty(&cratename, field, attrs, member));

    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
            }
        }
    })
}

fn len_ty(
    cratename: &syn::Path,
    field: &&syn::Field,
    attrs: &crate::FieldAttrs,
    member: &syn::Member,
) -> proc_macro2::TokenStream {
    let t = ty(&field.ty);
    if attrs.varint {
        let w = match t {
            Ty::I32 => quote!(::#cratename::V32(self.#member as u32)),
            Ty::U32 => quote!(::#cratename::V32(self.#member)),
            Ty::I64 => quote!(::#cratename::V64(self.#member as u64)),
            _ => quote!(::#cratename::V64(self.#member)),
        };
        quote!(::#cratename::Write::len_s(&#w))
    } else if matches!(t, Ty::U8Array) {
        quote!(self.#member.len())
    } else {
        quote!(::#cratename::Write::len_s(&self.#member))
    }
}

fn write_ty(
    cratename: &syn::Path,
    field: &&syn::Field,
    attrs: &crate::FieldAttrs,
    member: &syn::Member,
) -> proc_macro2::TokenStream {
    let t = ty(&field.ty);
    if attrs.varint {
        let w = match t {
            Ty::I32 => quote!(::#cratename::V32(self.#member as u32)),
            Ty::U32 => quote!(::#cratename::V32(self.#member)),
            Ty::I64 => quote!(::#cratename::V64(self.#member as u64)),
            _ => quote!(::#cratename::V64(self.#member)),
        };
        quote!(::#cratename::Write::write(&#w, __w))
    } else if matches!(t, Ty::U8Array) {
        quote!(__w.write(&self.#member))
    } else {
        quote!(::#cratename::Write::write(&self.#member, __w))
    }
}

fn write_ty_enum(
    cratename: &syn::Path,
    field: &syn::Field,
    attrs: &crate::FieldAttrs,
    member: &syn::Ident,
) -> proc_macro2::TokenStream {
    let t = ty(&field.ty);
    if attrs.varint {
        let w = match t {
            Ty::I32 => quote!(::#cratename::V32(*#member as u32)),
            Ty::U32 => quote!(::#cratename::V32(*#member)),
            Ty::I64 => quote!(::#cratename::V64(*#member as u64)),
            _ => quote!(::#cratename::V64(*#member)),
        };
        quote!(::#cratename::Write::write(&#w, __w))
    } else if matches!(t, Ty::U8Array) {
        quote!(__w.write(#member))
    } else {
        quote!(::#cratename::Write::write(#member, __w))
    }
}

fn len_ty_enum(
    cratename: &syn::Path,
    field: &&syn::Field,
    attrs: &crate::FieldAttrs,
    member: &syn::Ident,
) -> proc_macro2::TokenStream {
    let t = ty(&field.ty);
    if attrs.varint {
        let w = match t {
            Ty::I32 => quote!(::#cratename::V32(*#member as u32)),
            Ty::U32 => quote!(::#cratename::V32(*#member)),
            Ty::I64 => quote!(::#cratename::V64(*#member as u64)),
            _ => quote!(::#cratename::V64(*#member)),
        };
        quote!(::#cratename::Write::len_s(&#w))
    } else if matches!(t, Ty::U8Array) {
        quote!(#member.len())
    } else {
        quote!(::#cratename::Write::len_s(#member))
    }
}

pub fn serialize_enum(
    input: syn::DeriveInput,
    cratename: syn::Path,
    attrs: Attrs,
) -> syn::Result<proc_macro2::TokenStream> {
    let name = &input.ident;
    let mut repr = None;
    let varint = attrs.varint;
    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("repr") {
            attr.parse_nested_meta(|meta| {
                let path = meta.path.get_ident().unwrap();
                repr = Some(if !varint {
                    quote!(*self as #path)
                } else if len > u32::MAX as usize {
                    quote!(::#cratename::V64(*self as u64))
                } else if len > V21MAX {
                    quote!(::#cratename::V32(*self as u32))
                } else if len > V7MAX {
                    quote!(::#cratename::V21(*self as u32))
                } else {
                    quote!(*self as u8)
                });
                Ok(())
            })?;
        }
    }
    let (write, len_s) = match repr {
        Some(x) => (
            quote!(::#cratename::Write::write(&(#x), __w);),
            quote!(
            ::#cratename::Write::len_s(&(#x))),
        ),
        None => {
            let header = match &attrs.header {
                Some(x) => x,
                None => return Err(syn::Error::new_spanned(input, "expected header")),
            };
            let header = variants
                .iter()
                .map(|variant| {
                    let variant_name = &variant.ident;
                    let header_variant = syn::Ident::new(
                        &ident_case(&attrs, variant_name),
                        proc_macro2::Span::call_site(),
                    );
                    quote! {
                        Self::#variant_name { .. } => #header::#header_variant,
                    }
                })
                .collect::<Vec<_>>();
            let header = &header[..];
            let mut write = Vec::with_capacity(variants.len());

            for variant in variants.iter() {
                let variant_name = &variant.ident;
                let fields = variant.fields.members();
                let fields2 = parse_fields(&variant.fields)?;
                write.push(match &variant.fields {
                    syn::Fields::Named(_) => {
                        let fields2 = fields2.iter().map(|(field, attr, _)| {
                            write_ty_enum(&cratename, field, attr, field.ident.as_ref().unwrap())
                        });
                        quote! {
                            Self::#variant_name { #(#fields),* } => {
                                #(#fields2);*
                            }
                        }
                    }
                    syn::Fields::Unnamed(_) => {
                        let mut s = String::with_capacity(8);
                        s += "__self_";
                        let mut ss = Vec::new();
                        let fields = fields.map(|m| match m {
                            syn::Member::Unnamed(x) => {
                                let mut s = String::with_capacity(8);
                                s += "__self_";
                                s += itoa::Buffer::new().format(x.index);
                                ss.push(s);
                                syn::Ident::new(ss.last().unwrap(), proc_macro2::Span::call_site())
                            }
                            _ => unreachable!(),
                        });
                        let fields2 = fields2.iter().map(|(field, attr, m)| {
                            write_ty_enum(
                                &cratename,
                                field,
                                attr,
                                &match m {
                                    syn::Member::Unnamed(x) => {
                                        s.truncate(7);
                                        s += itoa::Buffer::new().format(x.index);
                                        syn::Ident::new(&s, proc_macro2::Span::call_site())
                                    }
                                    _ => unreachable!(),
                                },
                            )
                        });
                        quote! {
                            Self::#variant_name(#(#fields),*) => {
                                #(#fields2;)*
                            }
                        }
                    }
                    syn::Fields::Unit => quote! {
                        Self::#variant_name {} => {}
                    },
                });
            }
            let mut len = Vec::with_capacity(variants.len());
            for variant in variants.iter() {
                let variant_name = &variant.ident;
                let fields = variant.fields.members();
                let fields2 = parse_fields(&variant.fields)?;
                len.push(match variant.fields {
                    syn::Fields::Named(_) if !variant.fields.is_empty() => {
                        let fields2 = fields2.iter().map(|(field, attr, _)| {
                            len_ty_enum(&cratename, field, attr, field.ident.as_ref().unwrap())
                        });
                        quote! {
                            Self::#variant_name { #(#fields),* } => {
                                #(#fields2)+*
                            }
                        }
                    }
                    syn::Fields::Unnamed(_) if !variant.fields.is_empty() => {
                        let mut s = String::with_capacity(8);
                        s += "__self_";
                        let mut ss = Vec::new();
                        let fields = fields.map(|m| match m {
                            syn::Member::Unnamed(x) => {
                                let mut s = String::with_capacity(8);
                                s += "__self_";
                                s += itoa::Buffer::new().format(x.index);
                                ss.push(s);
                                syn::Ident::new(ss.last().unwrap(), proc_macro2::Span::call_site())
                            }
                            _ => unreachable!(),
                        });
                        let fields2 = fields2.iter().map(|(field, attr, m)| {
                            len_ty_enum(
                                &cratename,
                                field,
                                attr,
                                &match m {
                                    syn::Member::Unnamed(x) => {
                                        s.truncate(7);
                                        s += itoa::Buffer::new().format(x.index);
                                        syn::Ident::new(&s, proc_macro2::Span::call_site())
                                    }
                                    _ => unreachable!(),
                                },
                            )
                        });
                        quote! {
                            Self::#variant_name(#(#fields),*) => {
                                #(#fields2)+*
                            }
                        }
                    }
                    _ => {
                        quote! {
                            Self::#variant_name {} => 0,
                        }
                    }
                });
            }
            (
                quote! {
                    ::#cratename::Write::write(&match self {
                        #(#header)*
                    }, __w);
                    match self {
                        #(#write)*
                    }
                },
                quote! {
                    #cratename::Write::len_s(&match self {
                        #(#header)*
                    }) + match self {
                        #(#len)*
                    }
                },
            )
        }
    };
    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 {
                    #write
                }
            }

            #[inline]
            fn len_s(&self) -> usize {
                #len_s
            }
        }
    })
}