use parking_lot::Mutex;
use shared::core::geometry::{Bounds2i, Point2i};
use shared::utils::containers::DeviceArray2D;
use std::collections::HashSet;
use std::hash::Hash;
use std::ops::{Deref, DerefMut};
use std::sync::Arc;

pub struct InternCache<T> {
    cache: Mutex<HashSet<Arc<T>>>,
}

impl<T> InternCache<T>
where
    T: Eq + Clone + Hash,
{
    pub fn new() -> Self {
        Self {
            cache: Mutex::new(HashSet::new()),
        }
    }

    pub fn lookup(&self, value: T) -> Arc<T> {
        let mut lock = self.cache.lock();

        if let Some(existing) = lock.get(&value) {
            return existing.clone();
        }

        let new_item = Arc::new(value);
        lock.insert(Arc::clone(&new_item));
        new_item
    }
}

#[derive(Debug, Clone)]
pub struct Array2D<T> {
    pub device: DeviceArray2D<T>,
    values: Vec<T>,
}

impl<T> Deref for Array2D<T> {
    type Target = DeviceArray2D<T>;
    fn deref(&self) -> &Self::Target {
        &self.device
    }
}

impl<T> DerefMut for Array2D<T> {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.device
    }
}

impl<T> Array2D<T> {
    pub fn as_slice(&self) -> &[T] {
        &self.values
    }

    fn from_vec(extent: Bounds2i, mut values: Vec<T>) -> Self {
        let width = extent.p_max.x() - extent.p_min.x();
        let device = DeviceArray2D {
            extent,
            values: values.as_mut_ptr().into(),
            stride: width,
        };
        Self { device, values }
    }
}

impl<T: Default + Clone> Array2D<T> {
    pub fn new(extent: Bounds2i) -> Self {
        let n = extent.area() as usize;
        let storage = vec![T::default(); n];
        Self::from_vec(extent, storage)
    }

    pub fn new_dims(nx: i32, ny: i32) -> Self {
        let extent = Bounds2i::from_points(Point2i::new(0, 0), Point2i::new(nx, ny));
        Self::new(extent)
    }

    pub fn new_filled(nx: i32, ny: i32, val: T) -> Self {
        let extent = Bounds2i::from_points(Point2i::new(0, 0), Point2i::new(nx, ny));
        let n = (nx * ny) as usize;
        let storage = vec![val; n];
        Self::from_vec(extent, storage)
    }
}
