
#[cfg(feature = "with_async")]
pub mod async_used
{
    use std::num::NonZeroU32;

    #[cfg(not(feature = "std"))]
    extern crate alloc;

    use futures::{StreamExt, executor::{self, ThreadPool}, task::SpawnExt};
    use scram_rs::
    {
        SCRAM_TYPES, 
        ScramNonce, 
        ScramPassword, 
        ScramResult, 
        ScramResultClient, 
        ScramSha256RustNative, 
        async_trait, 
        scram_async::{AsyncScramAuthClient, AsyncScramAuthServer, AsyncScramCbHelper, AsyncScramClient, AsyncScramServer}
    };

    #[derive(Debug)]
    struct AuthDB
    {
    }

    impl AuthDB
    {
        pub fn new() -> Self
        {
            return AuthDB{};
        }
    }

    #[async_trait]
    impl AsyncScramCbHelper for &AuthDB
    {
        async 
        fn get_tls_server_endpoint(&self) -> ScramResult<Vec<u8>> 
        {
            scram_rs::HELPER_UNSUP_SERVER!("endpoint");
        }

        async 
        fn get_tls_unique(&self) -> ScramResult<Vec<u8>> {
            scram_rs::HELPER_UNSUP_SERVER!("unique");
        }

        async 
        fn get_tls_exporter(&self) -> ScramResult<Vec<u8>> 
        {
            scram_rs::HELPER_UNSUP_SERVER!("exporter");
        }
    }

    #[async_trait]
    impl AsyncScramAuthServer<ScramSha256RustNative> for &AuthDB
    {
        async 
        fn get_password_for_user(&self, username: &str, _authzid: Option<&str>) -> ScramResult<ScramPassword>
        {
            return 
                if username == "user"
                {
                    Ok(ScramPassword::found_secret_base64_password(
                        "xeR41ZKIyEGqUw22hFxMjZYok6ABzk4RpJY4c6qYE0o=".to_string(),
                            "c2FsdA==".to_string(), 
                            unsafe { NonZeroU32::new_unchecked(4096) },
                        None
                    )?)
                }
                else 
                {
                    ScramPassword::not_found::<ScramSha256RustNative>()
                };       
        }
    }

    #[derive(Debug)]
    struct AuthClient
    {
        username: String,
        password: String,
    }

    #[async_trait]
    impl AsyncScramCbHelper for &AuthClient
    {
        async 
        fn get_tls_server_endpoint(&self) -> ScramResult<Vec<u8>> 
        {
            scram_rs::HELPER_UNSUP_CLIENT!("endpoint");
        }

        async 
        fn get_tls_unique(&self) -> ScramResult<Vec<u8>> {
            scram_rs::HELPER_UNSUP_CLIENT!("unique");
        }

        async 
        fn get_tls_exporter(&self) -> ScramResult<Vec<u8>> 
        {
            scram_rs::HELPER_UNSUP_CLIENT!("exporter");
        }
    }

    #[async_trait]
    impl AsyncScramAuthClient for &AuthClient
    {
        async fn get_username(&self) -> &str
        {
            return &self.username;
        }

        async fn get_password(&self) -> &str
        {
            return &self.password;
        }

        async fn get_scram_keys(&self) -> Option<&scram_rs::ScramKey> 
        {
            return None;
        }
    }

    impl AuthClient
    {
        pub 
        fn new(u: &'static str, p: &'static str) -> Self
        {
            return AuthClient{ username: u.to_string(), password: p.to_string()};
        }
    }

    /// This example will not run, because it requires server, see tests in scram_sync.rs
    pub fn main1()
    {
        let pool = ThreadPool::new().expect("Failed to build pool");

        let client = AuthClient::new("user5", "password44");
        
        let (clie_send, mut serv_recv) = futures::channel::mpsc::unbounded::<String>();
        let (serv_send, mut clie_recv) = futures::channel::mpsc::unbounded::<String>();


        pool
            .spawn_ok(async move 
            { 
                let authdb = AuthDB::new();
                let scramtype = SCRAM_TYPES.get_scramtype("SCRAM-SHA-256").unwrap();
            
                let mut server = 
                    AsyncScramServer
                    ::<ScramSha256RustNative, &AuthDB, &AuthDB>
                    ::new(&authdb, &authdb, ScramNonce::none().unwrap(), scramtype, false)
                        .unwrap();
            
                loop
                {
                    let client_data = serv_recv.next().await.unwrap();

                    let serv_data = server.parse_response_base64(client_data).await;
                    serv_send.unbounded_send(serv_data.encode_base64()).unwrap();

                    match serv_data
                    {
                        scram_rs::ScramResultServer::Error(e) => 
                        {
                            println!("server: error: {}", e);
                            break;
                        },
                        scram_rs::ScramResultServer::Final(_) =>
                        {
                            println!("server: final!");
                            break;
                        },
                        _ => {}
                    }
                }
            });

        let hndl = 
            pool
                .spawn_with_handle(async move 
                    {
                        let mut client =
                            AsyncScramClient
                                ::<ScramSha256RustNative, &AuthClient, &AuthClient>
                                ::new(&client, ScramNonce::none().unwrap(), scram_rs::ChannelBindType::None, &client, false)
                                    .unwrap();

                        // client sends initial message: cli -> serv
                        let ci = client.init_client().await.encode_output_base64().unwrap();
                        clie_send.unbounded_send(ci).unwrap();

                        
                        loop
                        {
                            let serv_data = clie_recv.next().await.unwrap();

                            match client.parse_response_base64(serv_data).await
                            {
                                Ok(ScramResultClient::Completed) => 
                                {
                                    println!("client: completed auth successfully");
                                    break;
                                },
                                Ok(out) => clie_send.unbounded_send(out.encode_output_base64().unwrap()).unwrap(),
                                Err(e) =>
                                {
                                    println!("client: error: {}", e);
                                    break;
                                }
                            }

                        }
                    }
                )
                .unwrap();

        executor::block_on(hndl);

        return;
        
    }
}


#[cfg(feature = "with_async")]
use self::async_used::*;


/// This example will not run, because it requires server, see tests in scram_sync.rs
pub fn main()
{
    #[cfg(feature = "with_async")]
    main1();

    #[cfg(not(feature = "with_async"))]
    eprintln!("Ring support was not enabled!");
}