use chrono::{DateTime, Utc};
use futures::Future;
use std::{
    collections::HashMap,
    sync::{Arc, RwLock},
};

#[derive(Clone)]
pub struct CacheRecord<T> {
    pub timeout: DateTime<Utc>,
    pub value: T,
}

pub struct Cache<T>(Arc<RwLock<HashMap<String, CacheRecord<T>>>>);

impl<T: Clone> Cache<T> {
    pub fn new() -> Cache<T> {
        Cache(Arc::new(RwLock::new(HashMap::new())))
    }

    pub async fn find(&self, key: &str, f: impl Future<Output = (DateTime<Utc>, T)>) -> T {
        let val = {
            let cache = self.0.read().unwrap();
            cache.get(key).cloned()
        };
        match val {
            Some(ref val) if val.timeout > Utc::now() => val.value.clone(),
            _ => {
                let response = f.await;
                let mut cache = self.0.write().unwrap();
                let record = CacheRecord {
                    timeout: response.0,
                    value: response.1.clone(),
                };
                cache
                    .entry(key.to_owned())
                    .and_modify(|rec| *rec = record.clone())
                    .or_insert(record);
                response.1
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::Duration;
    use std::sync::{Arc, RwLock};

    #[derive(Clone, Debug, PartialEq)]
    struct Value(i64);

    #[tokio::test]
    async fn it_calls_function_when_element_does_not_exist() {
        let cache = Cache::new();
        let value = cache
            .find("my_key", async { (Utc::now(), Value(15)) })
            .await;
        assert_eq!(value, Value(15));
    }

    #[tokio::test]
    async fn it_calls_function_when_element_is_old() {
        let calls = Arc::new(RwLock::new(false));
        let cache = Cache::new();
        let _ = cache
            .find("my_key", async {
                (Utc::now() - Duration::seconds(10), Value(15))
            })
            .await;
        let value = cache
            .find("my_key", async {
                *calls.write().unwrap() = true;
                (Utc::now(), Value(16))
            })
            .await;
        assert_eq!(value, Value(16));
        assert_eq!(*calls.read().unwrap(), true);
    }

    #[tokio::test]
    async fn it_does_not_call_function_when_element_is_not_old() {
        let calls = Arc::new(RwLock::new(false));
        let cache = Cache::new();
        let _ = cache
            .find("my_key", async {
                (Utc::now() + Duration::seconds(10), Value(15))
            })
            .await;
        let value = cache
            .find("my_key", async {
                *calls.write().unwrap() = true;
                (Utc::now(), Value(16))
            })
            .await;
        assert_eq!(value, Value(15));
        assert_eq!(*calls.read().unwrap(), false);
    }
}