mod parse;

struct UnpackArgs {
    fmt: String,
    data: syn::Expr,
}

impl syn::parse::Parse for UnpackArgs {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let fmt = input.parse::<syn::LitStr>()?.value();
        _ = input.parse::<syn::Token![,]>()?;
        let data = input.parse()?;

        Ok(Self { fmt, data })
    }
}

struct PackArgs {
    fmt: String,
    args: Vec<syn::Expr>,
}

impl syn::parse::Parse for PackArgs {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let fmt = input.parse::<syn::LitStr>()?.value();
        _ = input.parse::<syn::Token![,]>()?;
        let args = input
            .parse_terminated(syn::Expr::parse, syn::Token![,])?
            .into_iter()
            .collect();
        Ok(Self { fmt, args })
    }
}

#[proc_macro]
pub fn pack(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let PackArgs { fmt, args } = syn::parse_macro_input!(input as PackArgs);

    let (little_endian, types) =
        parse::fmt_parser::fmt_pack(&fmt).unwrap_or_else(|e| panic!("Invalid format string: {e}"));

    if types.len() != args.len() {
        panic!(
            "Number of format specifiers ({}) does not match number of arguments ({})",
            types.len(),
            args.len()
        );
    }

    let arg_names = (0..args.len())
        .map(|i| syn::Ident::new(&format!("arg{i}"), proc_macro2::Span::call_site()))
        .collect::<Vec<_>>();
    let fn_args = arg_names
        .iter()
        .zip(types.iter())
        .map(|(arg, ty)| quote::quote! { #arg: #ty })
        .collect::<Vec<_>>();

    quote::quote! {{
        fn pack( #( #fn_args ),* ) -> Vec<u8>
        {
            let mut buf = Vec::new();
            #(
                if #little_endian {
                    <#types as ::bunpack::Pack>::pack_le(#arg_names, &mut buf);
                } else {
                    <#types as ::bunpack::Pack>::pack_be(#arg_names, &mut buf);
                }
            )*
            buf
        }

        pack( #( #args ),* )
    }}
    .into()
}

#[proc_macro]
pub fn unpack(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let UnpackArgs { fmt, data } = syn::parse_macro_input!(input as UnpackArgs);

    let (little_endian, types) = parse::fmt_parser::fmt_unpack(&fmt)
        .unwrap_or_else(|e| panic!("Invalid format string: {e}"));

    quote::quote! {{
        fn unpack<T: AsRef<[u8]>>(data: T) -> ( #( #types ),* )
        {
            let mut data: &[u8] = data.as_ref();
            (#(
                if #little_endian {
                    <#types as ::bunpack::Unpack>::unpack_le(&mut data)
                } else {
                    <#types as ::bunpack::Unpack>::unpack_be(&mut data)
                }
            ),*)
        }

        unpack(#data)
    }}
    .into()
}
