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

    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
        }
    };

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



    #[derive(Debug)]
    struct AuthDB
    {
    }

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

    #[derive(Debug)]
    struct AuthDBCb
    {
    }

    #[async_trait]
    impl AsyncScramCbHelper for AuthDBCb
    {
        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("user", "password");

        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 authdbcb = AuthDBCb{};
                    let scramtype = SCRAM_TYPES.get_scramtype("SCRAM-SHA-256").unwrap();
                
                    let server = 
                        AsyncScramServer
                            ::<ScramSha256RustNative, AuthDB, AuthDBCb>
                            ::new(authdb, authdbcb, ScramNonce::none().unwrap(), scramtype, false).unwrap();

                                let mut dyn_server = server.make_dyn(NonZeroUsize::new(150).unwrap());
                            
                                loop
                                {
                                    let client_data = serv_recv.next().await.unwrap();

                                    let serv_data = dyn_server.parse_response_base64(client_data.as_bytes()).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!");
}

