use core::fmt;
use std::rc::Rc;

use proc_macro2::TokenStream;
use quote::quote;

use crate::{
    abi::ty::{AbiPrimitive, SimpleType, Type},
    TargetEnv,
};

use super::ty::ComplexType;

pub struct Syscall {
    pub name: syn::Ident,
    pub args: Vec<(syn::Ident, Rc<ComplexType>)>,
    pub return_type: Option<Rc<ComplexType>>,
}

impl fmt::Debug for Syscall {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("Syscall")
            .field("name", &self.name)
            .field("args", &self.args)
            .field("return_type", &self.return_type)
            .finish_non_exhaustive()
    }
}

impl Syscall {
    // Syntax:
    //
    //  syscall_handler!("abi-document.abi", pub syscall_handler {
    //      read(fd, buffer) => ...,
    //      write(fd, buffer) => ...,
    //  })
    pub fn emit_handler_branch(
        &self,
        named_args: &[syn::Ident],
        raw_args: &syn::Ident,
        body: TokenStream,
        enum_variant: &syn::Ident,
        env: &TargetEnv,
    ) -> TokenStream {
        if self.args.len() != named_args.len() {
            todo!(
                "Argument length mismatch: got {} expectd {}",
                named_args.len(),
                self.args.len()
            )
        }

        let mut extracted_arguments = TokenStream::new();
        let mut index = 0;

        for ((_, arg), name) in std::iter::zip(self.args.iter(), named_args) {
            let (arg_expr, arg_width) = arg.emit_from_syscall_arguments(env, raw_args, index);
            let arg_assignment = quote! {
                let #name = #arg_expr;
            };
            index += arg_width;

            extracted_arguments.extend(arg_assignment);
        }

        quote! {
            SyscallFunction::#enum_variant => {
                #extracted_arguments

                #body
            }
        }
    }

    pub fn emit_stub(&self, enum_variant: &syn::Ident, env: &TargetEnv) -> TokenStream {
        let Self {
            name,
            args,
            return_type,
        } = self;

        let syscall_args = args
            .iter()
            .map(|(name, ty)| {
                let arg = ty.emit_to_syscall_arguments(env, name);
                quote!(#arg,)
            })
            .collect::<TokenStream>();
        let args = args
            .iter()
            .map(|(name, ty)| {
                let ty = ty.as_rust_type();
                quote!(#name: #ty,)
            })
            .collect::<TokenStream>();
        let return_type_arrow = return_type.as_ref().map(|ty| {
            let ty = ty.as_rust_type();
            quote!(-> #ty)
        });

        let sys_call = quote!(syscall!(SyscallFunction::#enum_variant, #syscall_args));

        let wrapped = match return_type.as_deref() {
            Some(ComplexType::Simple(SimpleType::Never)) => {
                quote! {
                    #sys_call;
                    unreachable!()
                }
            }
            Some(ComplexType::Simple(SimpleType::Primitive(AbiPrimitive::Unit))) => {
                quote! {
                    #sys_call;
                }
            }
            Some(ty) => {
                let ty = ty.as_rust_type();
                quote!(<#ty>::from_syscall_register(#sys_call))
            }
            None => quote!(#sys_call;),
        };

        let v = quote! {
            #[allow(clippy::missing_safety_doc)]
            pub unsafe fn #name(#args) #return_type_arrow {
                #wrapped
            }
        };
        v
    }
}
