use proc_macro2::{Ident, Span, TokenStream};
use quote::{quote, ToTokens};
use syn::token::{Brace, Paren, Semi};
use syn::{Attribute, LitInt, LitStr, ReturnType, Type};

pub struct ProtoMethodData {
    pub id: LitInt,
    pub name: Ident,
    pub parameters: Vec<(Ident, Type)>,
    pub ret_val: ReturnType,
}

/// This is a representation of the code generated by `rmc_proto` it serves to split the logic of
/// acquiring data from the actual generation to tidy up the process into first getting then
/// generating.
///
/// Use the [`ToTokens`] trait to generate the actual code.
pub struct RmcProtocolData {
    pub has_returns: bool,
    pub id: LitInt,
    pub name: Ident,
    pub methods: Vec<ProtoMethodData>,
}

impl RmcProtocolData {
    fn generate_raw_trait(&self, tokens: &mut TokenStream) {
        let Self {
            has_returns,
            name,
            id,
            methods,
        } = self;

        // this gives us the name which the identifier of the corresponding Raw trait
        let raw_name = Ident::new(&format!("Raw{}", name), name.span());

        // boilerplate tokens which all raw traits need
        quote! {
            #[doc(hidden)]
            #[allow(unused_must_use)]
            pub trait #raw_name: #name
        }
        .to_tokens(tokens);

        // generate the body of the raw protocol trait
        Brace::default().surround(tokens, |tokens|{
            //generate each raw method
            for method in methods{
                let ProtoMethodData {
                    name,
                    parameters,
                    ..
                } = method;

                let raw_name = Ident::new(&format!("raw_{}", name), name.span());
                quote!{
                    #[inline(always)]
                    async fn #raw_name
                }.to_tokens(tokens);

                Paren::default().surround(tokens, |tokens|{
                    quote!{ &self, data: ::std::vec::Vec<u8> }.to_tokens(tokens);
                });

                if self.has_returns {
                    quote! {
                        -> ::core::result::Result<Vec<u8>, ErrorCode>
                    }.to_tokens(tokens);
                }

                Brace::default().surround(tokens, |tokens|{
                    quote! { let mut cursor = ::std::io::Cursor::new(data); }.to_tokens(tokens);

                    for (param_name, param_type) in parameters{
                        quote!{
                            let Ok(#param_name) =
                                <#param_type as rnex_core::rmc::structures::RmcSerialize>::deserialize(
                                &mut cursor
                            ) else
                        }.to_tokens(tokens);

                        let error_msg = LitStr::new(&format!("an error occurred whilest deserializing {}", param_name), Span::call_site());

                        if self.has_returns {
                            quote! {
                                {
                                    log::error!(#error_msg);
                                    return Err(rnex_core::rmc::response::ErrorCode::Core_InvalidArgument);
                                };
                            }.to_tokens(tokens)
                        } else {
                            quote! {
                                {
                                    log::error!(#error_msg);
                                    return;
                                };
                            }.to_tokens(tokens)
                        }

                    }

                    quote!{
                        let retval = self.#name
                    }.to_tokens(tokens);

                    Paren::default().surround(tokens, |tokens|{
                        for (paren_name, _) in parameters{
                            quote!{#paren_name,}.to_tokens(tokens);
                        }
                    });

                    quote!{
                        .await;
                    }.to_tokens(tokens);

                    if *has_returns{
                        quote!{
                            let retval = retval?;
                            let mut vec = Vec::new();
                            rnex_core::rmc::structures::RmcSerialize::serialize(&retval, &mut vec).ok();
                            Ok(vec)
                        }.to_tokens(tokens);
                    }
                })
            }

            quote!{
                #[inline(always)]
                async fn rmc_call_proto(
                    &self,
                    remote_response_connection: &rnex_core::util::SendingBufferConnection,
                    method_id: u32,
                    call_id: u32,
                    data: Vec<u8>,
                )
            }.to_tokens(tokens);

            Brace::default().surround(tokens, |tokens|{
                quote! {
                    let ret = match method_id
                }.to_tokens(tokens);

                Brace::default().surround(tokens, |tokens|{
                    for method in methods{
                        let ProtoMethodData{
                            id,
                            name,
                            ..
                        } = method;

                        let raw_name = Ident::new(&format!("raw_{}", name), name.span());


                        quote!{
                            #id => self.#raw_name(data).await,
                        }.to_tokens(tokens);
                    }
                    quote!{
                        v =>
                    }.to_tokens(tokens);



                    Brace::default().surround(tokens, |tokens|{
                        quote!{
                            log::error!("(protocol {})unimplemented method id called on protocol: {}", #id, v);
                        }.to_tokens(tokens);
                        if self.has_returns {
                            quote! {
                                Err(rnex_core::rmc::response::ErrorCode::Core_NotImplemented)
                            }.to_tokens(tokens);
                        }
                    });

                });

                Semi::default().to_tokens(tokens);

                if *has_returns{
                    quote!{
                        rnex_core::rmc::response::send_result(
                            remote_response_connection,
                            ret,
                            #id,
                            method_id,
                            call_id,
                        ).await
                    }.to_tokens(tokens);
                }
            });
        });

        quote! {
            impl<T: #name> #raw_name for T{}
        }
        .to_tokens(tokens);
    }

    fn generate_raw_remote_trait(&self, tokens: &mut TokenStream) {
        let Self {
            has_returns,
            name,
            id: proto_id,
            methods,
            ..
        } = self;

        // this gives us the name which the identifier of the corresponding Raw trait
        let remote_name = Ident::new(&format!("Remote{}", name), name.span());

        // boilerplate tokens which all raw traits need
        quote! {
            #[doc(hidden)]
            #[allow(unused_must_use)]
            pub trait #remote_name: rnex_core::rmc::protocols::HasRmcConnection
        }
        .to_tokens(tokens);

        // generate the body of the raw protocol trait
        Brace::default().surround(tokens, |tokens|{
            //generate each raw method
            for method in methods{
                let ProtoMethodData {
                    name,
                    parameters,
                    ret_val,
                    id: method_id,
                    ..
                } = method;

                quote!{
                    async fn #name
                }.to_tokens(tokens);

                Paren::default().surround(tokens, |tokens|{
                    quote!{ &self, }.to_tokens(tokens);
                    for (param_ident, param_type) in parameters{
                        quote!{ #param_ident: #param_type, }.to_tokens(tokens);
                    }
                });

                quote!{
                    #ret_val
                }.to_tokens(tokens);

                Brace::default().surround(tokens, |tokens|{
                    quote! {
                        let mut send_data = Vec::new();
                        let mut cursor = ::std::io::Cursor::new(&mut send_data);
                    }.to_tokens(tokens);

                    for (param_name, param_type) in parameters{
                        quote!{
                            rnex_core::result::ResultExtension::display_err_or_some(
                                <#param_type as rnex_core::rmc::structures::RmcSerialize>::serialize(
                                    &#param_name,
                                    &mut cursor
                                )
                            ).ok_or(rnex_core::rmc::response::ErrorCode::Core_InvalidArgument)
                        }.to_tokens(tokens);
                        if self.has_returns {
                            quote! {
                                ?;
                            }.to_tokens(tokens)
                        } else {
                            quote! {
                                ;
                            }.to_tokens(tokens)
                        }
                    }

                    quote!{
                        let call_id = rand::random();

                        let message = rnex_core::rmc::message::RMCMessage{
                            call_id,
                            method_id: #method_id,
                            protocol_id: #proto_id,
                            rest_of_data: send_data
                        };

                        let rmc_conn = <Self as rnex_core::rmc::protocols::HasRmcConnection>::get_connection(self);
                    }.to_tokens(tokens);

                    if *has_returns{
                        quote!{
                            rnex_core::result::ResultExtension::display_err_or_some(
                                rmc_conn.make_raw_call(&message).await
                            ).ok_or(rnex_core::rmc::response::ErrorCode::Core_Exception)
                        }.to_tokens(tokens);
                    } else {
                        quote!{
                            rnex_core::result::ResultExtension::display_err_or_some(
                                rmc_conn.make_raw_call_no_response(&message).await
                            );
                        }.to_tokens(tokens);
                    }

                })
            }
        });
    }

    fn generate_raw_info(&self, tokens: &mut TokenStream) {
        let Self { name, id, .. } = self;

        let raw_info_name = Ident::new(&format!("Raw{}Info", name), Span::call_site());

        quote! {
            #[doc(hidden)]
            pub struct #raw_info_name;

            impl #raw_info_name {
                pub const PROTOCOL_ID: u16 = #id;
            }
        }
        .to_tokens(tokens);
    }
}

impl ToTokens for RmcProtocolData {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.generate_raw_trait(tokens);
        self.generate_raw_info(tokens);
        self.generate_raw_remote_trait(tokens);
    }
}
