use std::{
    num::NonZeroUsize,
    path::PathBuf,
    sync::{
        Arc, Mutex,
        atomic::{AtomicUsize, Ordering},
    },
};

use lru::LruCache;

use crate::util::ImageData;

pub struct Cache {
    lru: Mutex<LruCache<PathBuf, Arc<ImageData>>>,
    total_size: AtomicUsize,
    max_size: usize,
}

impl Cache {
    pub fn new(max_size: usize) -> Cache {
        Self {
            lru: Mutex::new(LruCache::new(NonZeroUsize::new(30).unwrap())),
            total_size: AtomicUsize::new(0),
            max_size,
        }
    }

    pub fn put(&self, path: PathBuf, mut image: Arc<ImageData>) {
        Arc::make_mut(&mut image);
        let size: usize = image
            .frames
            .iter()
            .map(|image| image.buffer().as_bytes().len())
            .sum();
        if size >= self.max_size {
            return;
        }

        let mut guard = self.lru.lock().unwrap();
        while size + self.total_size.load(Ordering::SeqCst) > self.max_size {
            let removed = guard.pop_lru();
            match removed {
                Some((_, value)) => {
                    let removed: usize = value
                        .frames
                        .iter()
                        .map(|image| image.buffer().as_bytes().len())
                        .sum();
                    self.total_size.fetch_sub(removed, Ordering::SeqCst);
                }
                None => break,
            }
        }

        self.total_size.fetch_add(size, Ordering::SeqCst);
        guard.put(path.canonicalize().unwrap_or(path), image);
    }

    #[allow(clippy::ptr_arg)]
    pub fn pop(&self, path: &PathBuf) {
        self.lru
            .lock()
            .unwrap()
            .pop(&path.canonicalize().unwrap_or_else(|_| path.clone()));
    }

    #[allow(clippy::ptr_arg)]
    pub fn get(&self, path: &PathBuf) -> Option<Arc<ImageData>> {
        self.lru
            .lock()
            .unwrap()
            .get(&path.canonicalize().unwrap_or_else(|_| path.clone()))
            .cloned()
    }

    #[allow(clippy::ptr_arg)]
    pub fn contains(&self, path: &PathBuf) -> bool {
        self.lru
            .lock()
            .unwrap()
            .contains(&path.canonicalize().unwrap_or_else(|_| path.clone()))
    }

    pub fn clear(&self) {
        self.total_size.store(0, Ordering::SeqCst);
        self.lru.lock().unwrap().clear();
    }
}
