use crate::device::{PageOps, ReadPage};
use linked_hash_map::LinkedHashMap;
use std::time::{Duration, Instant};

struct CacheEntry {
    page: ReadPage,
    instant: Instant,
}

impl CacheEntry {
    fn new(page: ReadPage) -> Self {
        Self {
            page,
            instant: Instant::now(),
        }
    }
}

pub struct Cache {
    cache: LinkedHashMap<u64, CacheEntry>,
    size: u64,
    limit: u64,
    age_limit: Duration,
}

impl Cache {
    pub fn new(limit: u64, age_limit: Duration) -> Cache {
        Cache {
            cache: LinkedHashMap::new(),
            size: 0,
            limit,
            age_limit,
        }
    }

    pub fn get(&mut self, key: u64) -> Option<ReadPage> {
        self.cache.get_refresh(&key).map(|val| val.page.clone_read())
    }

    pub fn put(&mut self, key: u64, value: ReadPage) {
        self.size += 1 << value.get_size_exp();
        if let Some(pre) = self.cache.insert(key, CacheEntry::new(value)) {
            self.size -= 1 << pre.page.get_size_exp();
        }
        while self.size > self.limit {
            if let Some(en) = self.cache.pop_front() {
                self.size -= 1 << en.1.page.get_size_exp();
            } else {
                break;
            }
        }

        loop {
            if self
                .cache
                .front()
                .map(|(_, e)| e.instant.elapsed() > self.age_limit)
                .unwrap_or(false)
            {
                if let Some(en) = self.cache.pop_front() {
                    self.size -= 1 << en.1.page.get_size_exp();
                }
            } else {
                break;
            }
        }
    }

    pub fn remove(&mut self, key: u64) {
        if let Some(removed) = self.cache.remove(&key) {
            self.size -= 1 << removed.page.get_size_exp();
        }
    }

    pub fn remove_all(&mut self, keys: &[u64]) {
        for key in keys {
            if let Some(removed) = self.cache.remove(key) {
                self.size -= 1 << removed.page.get_size_exp();
            }
        }
    }

    #[allow(unused)]
    pub fn size(&self) -> u64 {
        self.size
    }

    #[allow(unused)]
    pub fn entry_count(&self) -> usize {
        self.cache.len()
    }
}
