use std::{
    any::{Any, TypeId},
    collections::HashMap,
    mem::transmute,
};

#[derive(Default)]
pub struct Resources {
    map: HashMap<TypeId, Box<dyn Any + Send + Sync>>,
}

impl Resources {
    pub fn insert<T: Any + Send + Sync>(&mut self, value: T) -> Option<T> {
        self.map
            .insert(TypeId::of::<T>(), Box::new(value))
            .map(|any| *Self::downcast_unchecked(any))
    }

    pub fn insert_if_not_exists<T: Default + Any + Send + Sync>(&mut self) {
        if !self.contains::<T>() {
            self.insert(T::default());
        }
    }

    pub fn remove<T: Any + Send + Sync>(&mut self) -> Option<T> {
        self.map
            .remove(&TypeId::of::<T>())
            .map(|any| *Self::downcast_unchecked(any))
    }

    pub fn get<T: Any + Send + Sync>(&self) -> &T {
        self.map
            .get(&TypeId::of::<T>())
            .map(|any| Self::downcast_ref_unchecked(any))
            .unwrap()
    }

    pub fn get_mut<T: Any + Send + Sync>(&mut self) -> &mut T {
        self.map
            .get_mut(&TypeId::of::<T>())
            .map(|any| Self::downcast_mut_unchecked(any))
            .unwrap()
    }

    pub fn multi_mut(&mut self) -> ResourceMultiMut<'_> {
        ResourceMultiMut::new(&mut self.map)
    }

    pub fn contains<T: Any + Send + Sync>(&self) -> bool {
        self.map.contains_key(&TypeId::of::<T>())
    }
}

// helper
impl Resources {
    fn downcast_unchecked<T: Any + Send + Sync>(boxed: Box<dyn Any + Send + Sync>) -> Box<T> {
        unsafe { Box::from_raw(Box::into_raw(boxed) as *mut T) }
    }

    fn downcast_ref_unchecked<T: Any + Send + Sync>(boxed_ref: &Box<dyn Any + Send + Sync>) -> &T {
        unsafe {
            let ptr_to_ptr: *const *const T =
                transmute(destructure_traitobject::data(boxed_ref as *const _));

            &**ptr_to_ptr
        }
    }

    fn downcast_mut_unchecked<T: Any + Send + Sync>(
        boxed_ref: &mut Box<dyn Any + Send + Sync>,
    ) -> &mut T {
        unsafe {
            let ptr_to_ptr: *mut *mut T =
                transmute(destructure_traitobject::data(boxed_ref as *mut _));

            &mut **ptr_to_ptr
        }
    }
}

/// Allows mutable access to multiple components at once
pub struct ResourceMultiMut<'a> {
    map: &'a mut HashMap<TypeId, Box<dyn Any + Send + Sync>>,
    buffer: Vec<*mut Box<dyn Any + Send + Sync>>,
}

impl<'a> ResourceMultiMut<'a> {
    fn new(map: &'a mut HashMap<TypeId, Box<dyn Any + Send + Sync>>) -> Self {
        ResourceMultiMut {
            map,
            buffer: Vec::new(),
        }
    }

    /// Returns requested type on success
    pub fn get<T: Any + Send + Sync>(&mut self) -> &'a mut T {
        self.get_by_type_id(&TypeId::of::<T>())
            .map(|component| Resources::downcast_mut_unchecked(component))
            .unwrap()
    }

    /// Returns requested type behind this type id on success
    pub fn get_by_type_id(
        &mut self,
        type_id: &TypeId,
    ) -> Option<&'a mut Box<dyn Any + Send + Sync>> {
        self.map.get_mut(type_id).map(|v| {
            let ptr = v as *mut _;

            match self.buffer.iter().find(|v| **v == ptr) {
                Some(_) => {
                    panic!("This key has already been borrowed!");
                }
                None => {
                    self.buffer.push(ptr);
                }
            }

            let t: Option<&'a mut Box<dyn Any + Send + Sync>> = unsafe { transmute(ptr) };

            t.unwrap()
        })
    }

    /// # Safety
    ///
    /// use this only when there are no references left
    pub unsafe fn clear_all_usages(&mut self) {
        self.buffer.clear();
    }
}