use std::any::TypeId;

use ahash::AHashMap;

use crate::di_container::BindingOptions;

pub struct DIContainerBindingStorage<Provider>
where
    Provider: 'static + ?Sized,
{
    inner: AHashMap<BindingId<'static>, Box<Provider>>,
}

impl<Provider> DIContainerBindingStorage<Provider>
where
    Provider: 'static + ?Sized,
{
    pub fn new() -> Self
    {
        Self {
            inner: AHashMap::new(),
        }
    }

    #[allow(clippy::borrowed_box)]
    pub fn get<'this, Interface>(
        &'this self,
        options: BindingOptions<'this>,
    ) -> Option<&'this Box<Provider>>
    where
        Interface: 'static + ?Sized,
    {
        self.inner.get(&BindingId::new::<Interface>(options))
    }

    pub fn set<Interface>(
        &mut self,
        options: BindingOptions<'static>,
        provider: Box<Provider>,
    ) where
        Interface: 'static + ?Sized,
    {
        self.inner
            .insert(BindingId::new::<Interface>(options), provider);
    }

    pub fn remove<Interface>(
        &mut self,
        options: BindingOptions<'static>,
    ) -> Option<Box<Provider>>
    where
        Interface: 'static + ?Sized,
    {
        self.inner.remove(&BindingId::new::<Interface>(options))
    }

    pub fn has<Interface>(&self, options: BindingOptions) -> bool
    where
        Interface: 'static + ?Sized,
    {
        self.inner
            .contains_key(&BindingId::new::<Interface>(options))
    }
}

impl<Provider> Default for DIContainerBindingStorage<Provider>
where
    Provider: 'static + ?Sized,
{
    fn default() -> Self
    {
        Self::new()
    }
}

#[derive(Debug, PartialEq, Eq, Hash)]
struct BindingId<'opts>
{
    type_id: TypeId,
    options: BindingOptions<'opts>,
}

impl<'opts> BindingId<'opts>
{
    fn new<Interface>(options: BindingOptions<'opts>) -> Self
    where
        Interface: ?Sized + 'static,
    {
        Self {
            type_id: TypeId::of::<Interface>(),
            options,
        }
    }
}

#[cfg(test)]
mod tests
{
    use super::*;

    mod subjects
    {
        pub trait SomeProvider
        {
            fn get_id(&self) -> u8;
        }

        pub struct SomeProviderImpl
        {
            pub id: u8,
        }

        impl SomeProvider for SomeProviderImpl
        {
            fn get_id(&self) -> u8
            {
                self.id
            }
        }
    }

    #[test]
    fn can_get()
    {
        type Interface = ();

        let mut binding_map =
            DIContainerBindingStorage::<dyn subjects::SomeProvider>::new();

        binding_map.inner.insert(
            BindingId::new::<Interface>(BindingOptions::new()),
            Box::new(subjects::SomeProviderImpl { id: 20 }),
        );

        assert!(binding_map
            .get::<Interface>(BindingOptions::new())
            .map_or_else(|| false, |provider| provider.get_id() == 20));
    }

    #[test]
    fn can_get_with_name()
    {
        type Interface = ();

        let mut binding_map =
            DIContainerBindingStorage::<dyn subjects::SomeProvider>::new();

        binding_map.inner.insert(
            BindingId::new::<Interface>(BindingOptions::new().name("hello")),
            Box::new(subjects::SomeProviderImpl { id: 11 }),
        );

        assert!(binding_map
            .get::<Interface>(BindingOptions::new().name("hello"))
            .map_or_else(|| false, |provider| provider.get_id() == 11));

        assert!(binding_map
            .get::<Interface>(BindingOptions::new())
            .is_none());
    }

    #[test]
    fn can_set()
    {
        type Interface = ();

        let mut binding_map =
            DIContainerBindingStorage::<dyn subjects::SomeProvider>::new();

        binding_map.set::<Interface>(
            BindingOptions::new(),
            Box::new(subjects::SomeProviderImpl { id: 65 }),
        );

        let expected_key = BindingId::new::<Interface>(BindingOptions::new());

        assert!(binding_map.inner.contains_key(&expected_key));

        assert_eq!(binding_map.inner[&expected_key].get_id(), 65);
    }

    #[test]
    fn can_set_with_name()
    {
        type Interface = ();

        let mut binding_map =
            DIContainerBindingStorage::<dyn subjects::SomeProvider>::new();

        binding_map.set::<Interface>(
            BindingOptions::new().name("special"),
            Box::new(subjects::SomeProviderImpl { id: 3 }),
        );

        let expected_key =
            BindingId::new::<Interface>(BindingOptions::new().name("special"));

        assert!(binding_map.inner.contains_key(&expected_key));

        assert_eq!(binding_map.inner[&expected_key].get_id(), 3);
    }

    #[test]
    fn can_remove()
    {
        type Interface = ();

        let mut binding_map =
            DIContainerBindingStorage::<dyn subjects::SomeProvider>::new();

        binding_map.inner.insert(
            BindingId::new::<Interface>(BindingOptions::new()),
            Box::new(subjects::SomeProviderImpl { id: 103 }),
        );

        binding_map.remove::<Interface>(BindingOptions::new());

        assert!(!binding_map
            .inner
            .contains_key(&BindingId::new::<Interface>(BindingOptions::new())));
    }

    #[test]
    fn can_remove_with_name()
    {
        type Interface = ();

        let mut binding_map =
            DIContainerBindingStorage::<dyn subjects::SomeProvider>::new();

        binding_map.inner.insert(
            BindingId::new::<Interface>(BindingOptions::new().name("cool")),
            Box::new(subjects::SomeProviderImpl { id: 42 }),
        );

        binding_map.remove::<Interface>(BindingOptions::new().name("cool"));

        assert!(
            !binding_map.inner.contains_key(&BindingId::new::<Interface>(
                BindingOptions::new().name("cool")
            ))
        );
    }

    #[test]
    fn can_get_has()
    {
        type Interface = ();

        let mut binding_map =
            DIContainerBindingStorage::<dyn subjects::SomeProvider>::new();

        assert!(!binding_map.has::<Interface>(BindingOptions::new()));

        binding_map.inner.insert(
            BindingId::new::<Interface>(BindingOptions::new()),
            Box::new(subjects::SomeProviderImpl { id: 103 }),
        );

        assert!(binding_map.has::<Interface>(BindingOptions::new()));
    }

    #[test]
    fn can_get_has_with_name()
    {
        type Interface = ();

        let mut binding_map =
            DIContainerBindingStorage::<dyn subjects::SomeProvider>::new();

        assert!(!binding_map.has::<Interface>(BindingOptions::new().name("awesome")));

        binding_map.inner.insert(
            BindingId::new::<Interface>(BindingOptions::new().name("awesome")),
            Box::new(subjects::SomeProviderImpl { id: 101 }),
        );

        assert!(binding_map.has::<Interface>(BindingOptions::new().name("awesome")));
    }
}