use proc_macro2::TokenStream;
use quote::quote;
use syn::{
  parse::{ParseStream, Parser}, Expr, LitStr, Token
};
use truth::{
  context::RootEmitter, llir::{ArgEncoding, InstrAbi, StringArgSize}, pos::SourceStr
};

#[proc_macro]
pub fn decode_args(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
  match blob_impl.parse(input.clone()) {
    Ok(data) => data.into(),
    Err(error) => {
      // error.span().unwrap().error(error.to_string());
      error.into_compile_error().into()
    }
  }
}

fn blob_impl(input: ParseStream) -> syn::Result<TokenStream> {
  let reader: Expr = input.parse()?;
  let _: Token![,] = input.parse()?;
  let sig_str: LitStr = input.parse()?;
  let sig = InstrAbi::parse(
    SourceStr::from_full_source(None, sig_str.value().as_str()),
    &RootEmitter::new_stderr(),
  )
  .unwrap();
  let encodings: Vec<_> = sig
    .arg_encodings()
    .map(|enc| {
      Ok(match enc {
        ArgEncoding::Float => quote!(cursor.read_f32().unwrap()),
        ArgEncoding::Integer { size, .. } => {
          let size = *size as usize;

          match size {
            2 => quote!(cursor.read_i16().unwrap()),
            4 => quote!(cursor.read_i32().unwrap()),
            _ => panic!("invalid integer size {size}"),
          }
        }
        ArgEncoding::JumpTime => quote!(cursor.read_i32().unwrap()),
        ArgEncoding::JumpOffset => quote!(cursor.read_i32().unwrap()),
        ArgEncoding::String {
          size: StringArgSize::Fixed { len, .. },
          ..
        } => quote!(cursor.read_cstring_exact(#len)),
        _ => return Err(syn::Error::new(sig_str.span(), "failed...")),
      })
    })
    .collect::<syn::Result<_>>()?;

  Ok(quote! {
    {
      use truth::io::BinRead;
      let mut cursor = ::std::io::Cursor::new(#reader);
      (
        #(#encodings),*
      )
    }
  })
}
