use std::sync::Arc;
use std::time;
use std::time::Duration;
use tokio::sync::RwLock;
use context_async::{Context, Timer, With};

/// In this lib, our Timer doesn't store any data.
/// However, we usually need to share data with our context.
///
/// In this example, we provide a simple extension implementation.
/// We shore a `User` in our context.

#[derive(Debug)]
#[allow(dead_code)]
struct User {
    username: String,
    user_id: u64,
}

#[derive(Debug, Clone)]
struct MyContext {
    timer: Timer,
    user: Arc<RwLock<Option<User>>>, // use Arc to cheep clone. use RwLock to modify user.
}

#[context_async::async_trait] // re-export from context_async
impl Context for MyContext {
    type SubContext = Self;
    
    fn timer(&self) -> Timer {
        self.timer.clone() // cheep clone
    }

    async fn spawn(&self) -> Self {
        Self {
            timer: self.timer.spawn().await,
            user: self.user.clone(),
        }
    }

    async fn spawn_with_timeout(&self, timeout: Duration) -> Self {
        Self {
            timer: self.timer.spawn_with_timeout(timeout).await,
            user: self.user.clone(),
        }
    }
}

/// when your function needs data, pass your defined Context
async fn update_user(ctx: &MyContext) {
    *ctx.user.write().await = None;
}

/// when your function don't need a data,
/// for example, do some HTTP request, you can use Context as receiver:
async fn fetch_https<Ctx: Context>(ctx: Ctx, url: &str) {
    reqwest::get(url)
        .with(ctx)
        .await
        .unwrap() // -> error from context: timeout or cancelled?
        .unwrap(); // -> error from reqwest
}

#[tokio::main]
async fn main() {
    let ctx = MyContext {
        timer: Timer::with_timeout(time::Duration::from_secs(10)),
        user: Arc::new(RwLock::new(Some(User {
            username: String::from("jack"),
            user_id: 1,
        })))
    };

    update_user(&ctx).await;
    fetch_https(ctx.clone(), "https://www.baidu.com").await;
}