/*
Async memoization on the async-std runtime: `#[cached]` / `#[once]` on
`async fn`s (including `ttl` + `result`). Demonstrates the proc macros are
runtime-agnostic (the `async` feature; async-std runs `main`).

Run:
    cargo run --example async_std --features "async,proc_macro"
*/

use async_std::task::sleep;
use cached::macros::cached;
use cached::macros::once;
use cached::time::Duration;

async fn sleep_secs(secs: u64) {
    sleep(Duration::from_secs(secs)).await;
}

/// should only sleep the first time it's called
#[cached]
async fn cached_sleep_secs(secs: u64) {
    sleep(Duration::from_secs(secs)).await;
}

/// should only cache the result for a second, and only when
/// the result is `Ok`
#[cached(ttl = 1, key = "bool", convert = r#"{ true }"#)]
async fn only_cached_a_second(
    s: String,
) -> std::result::Result<Vec<String>, &'static dyn std::error::Error> {
    Ok(vec![s])
}

/// should only cache the _first_ `Ok` returned.
/// all arguments are ignored for subsequent calls.
#[once]
async fn only_cached_result_once(s: String, error: bool) -> std::result::Result<Vec<String>, u32> {
    if error { Err(1) } else { Ok(vec![s]) }
}

/// should only cache the _first_ `Ok` returned for 1 second.
/// all arguments are ignored for subsequent calls until the
/// cache expires after a second.
#[once(ttl = 1)]
async fn only_cached_result_once_per_second(
    s: String,
    error: bool,
) -> std::result::Result<Vec<String>, u32> {
    if error { Err(1) } else { Ok(vec![s]) }
}

/// should only cache the _first_ `Some` returned .
/// all arguments are ignored for subsequent calls
#[once]
async fn only_cached_option_once(s: String, none: bool) -> Option<Vec<String>> {
    if none { None } else { Some(vec![s]) }
}

/// should only cache the _first_ `Some` returned for 1 second.
/// all arguments are ignored for subsequent calls until the
/// cache expires after a second.
#[once(ttl = 1)]
async fn only_cached_option_once_per_second(s: String, none: bool) -> Option<Vec<String>> {
    if none { None } else { Some(vec![s]) }
}

/// should only cache the _first_ value returned for 1 second.
/// all arguments are ignored for subsequent calls until the
/// cache expires after a second.
#[once(ttl = 1)]
async fn only_cached_once_per_second(s: String) -> Vec<String> {
    vec![s]
}

/// should only cache the _first_ value returned for 2 seconds.
/// all arguments are ignored for subsequent calls until the
/// cache expires after a second.
/// when multiple un-cached tasks are running concurrently, only
/// _one_ call will be "executed" and all others will be synchronized
/// to return the cached result of the one call instead of all
/// concurrently un-cached tasks executing and writing concurrently.
#[once(ttl = 2, sync_writes = true)]
async fn only_cached_once_per_second_sync_writes(s: String) -> Vec<String> {
    vec![s]
}

#[async_std::main]
async fn main() {
    let a = only_cached_a_second("a".to_string()).await.unwrap();
    let b = only_cached_a_second("b".to_string()).await.unwrap();
    assert_eq!(a, b);
    sleep_secs(1).await;
    let b = only_cached_a_second("b".to_string()).await.unwrap();
    assert_ne!(a, b);

    println!("cached sleeping for 1 seconds");
    cached_sleep_secs(1).await;
    println!("cached sleeping for 1 seconds");
    cached_sleep_secs(1).await;

    println!("cached result once");
    assert!(
        only_cached_result_once("z".to_string(), true)
            .await
            .is_err()
    );
    let a = only_cached_result_once("a".to_string(), false)
        .await
        .unwrap();
    let b = only_cached_result_once("b".to_string(), false)
        .await
        .unwrap();
    assert_eq!(a, b);
    sleep_secs(1).await;
    let b = only_cached_result_once("b".to_string(), false)
        .await
        .unwrap();
    assert_eq!(a, b);

    println!("cached result once per second");
    assert!(
        only_cached_result_once_per_second("z".to_string(), true)
            .await
            .is_err()
    );
    let a = only_cached_result_once_per_second("a".to_string(), false)
        .await
        .unwrap();
    let b = only_cached_result_once_per_second("b".to_string(), false)
        .await
        .unwrap();
    assert_eq!(a, b);
    sleep_secs(1).await;
    let b = only_cached_result_once_per_second("b".to_string(), false)
        .await
        .unwrap();
    assert_eq!(vec!["b".to_string()], b);

    println!("cached option once");
    assert!(
        only_cached_option_once("z".to_string(), true)
            .await
            .is_none()
    );
    let a = only_cached_option_once("a".to_string(), false)
        .await
        .unwrap();
    let b = only_cached_option_once("b".to_string(), false)
        .await
        .unwrap();
    assert_eq!(a, b);
    sleep_secs(1).await;
    let b = only_cached_option_once("b".to_string(), false)
        .await
        .unwrap();
    assert_eq!(a, b);

    println!("cached option once per second");
    assert!(
        only_cached_option_once_per_second("z".to_string(), true)
            .await
            .is_none()
    );
    let a = only_cached_option_once_per_second("a".to_string(), false)
        .await
        .unwrap();
    let b = only_cached_option_once_per_second("b".to_string(), false)
        .await
        .unwrap();
    assert_eq!(a, b);
    sleep_secs(1).await;
    let b = only_cached_option_once_per_second("b".to_string(), false)
        .await
        .unwrap();
    assert_eq!(vec!["b".to_string()], b);

    println!("cached once per second");
    let a = only_cached_once_per_second("a".to_string()).await;
    let b = only_cached_once_per_second("b".to_string()).await;
    assert_eq!(a, b);
    sleep_secs(1).await;
    let b = only_cached_once_per_second("b".to_string()).await;
    assert_eq!(vec!["b".to_string()], b);

    println!("cached once per second synchronized writes");
    let a = async_std::task::spawn(only_cached_once_per_second_sync_writes("a".to_string()));
    sleep_secs(1).await;
    let b = async_std::task::spawn(only_cached_once_per_second_sync_writes("b".to_string()));
    assert_eq!(a.await, b.await);

    println!("done!");
}
