#[cfg(target_os = "linux")]
pub mod eds;

pub mod generic;

// Convenient alias for the platform's default backend
#[cfg(target_os = "linux")]
pub type DefaultContactResolverBackend = eds::EDSContactResolverBackend;
#[cfg(not(target_os = "linux"))]
pub type DefaultContactResolverBackend = generic::GenericContactResolverBackend;

#[cfg(not(target_os = "linux"))]
#[derive(Clone)]
pub struct EDSContactResolverBackend;

#[cfg(not(target_os = "linux"))]
impl Default for EDSContactResolverBackend {
    fn default() -> Self {
        EDSContactResolverBackend
    }
}

#[cfg(not(target_os = "linux"))]
impl ContactResolverBackend for EDSContactResolverBackend {
    type ContactID = String;

    fn resolve_contact_id(&self, _address: &str) -> Option<Self::ContactID> {
        None
    }

    fn get_contact_display_name(&self, _contact_id: &Self::ContactID) -> Option<String> {
        None
    }
}

use std::collections::HashMap;

pub trait ContactResolverBackend {
    type ContactID;

    fn resolve_contact_id(&self, address: &str) -> Option<Self::ContactID>;
    fn get_contact_display_name(&self, contact_id: &Self::ContactID) -> Option<String>;
}

pub type AnyContactID = String;

#[derive(Clone)]
pub struct ContactResolver<T: ContactResolverBackend> {
    backend: T,
    display_name_cache: HashMap<AnyContactID, Option<String>>,
    contact_id_cache: HashMap<String, Option<AnyContactID>>,
}

impl<T: ContactResolverBackend> ContactResolver<T>
where
    T::ContactID: From<AnyContactID>,
    T::ContactID: Into<AnyContactID>,
    T: Default,
{
    pub fn new(backend: T) -> Self {
        Self {
            backend,
            display_name_cache: HashMap::new(),
            contact_id_cache: HashMap::new(),
        }
    }

    pub fn resolve_contact_id(&mut self, address: &str) -> Option<AnyContactID> {
        if let Some(id) = self.contact_id_cache.get(address) {
            return id.clone();
        }

        let id = self.backend.resolve_contact_id(address).map(|id| id.into());
        self.contact_id_cache
            .insert(address.to_string(), id.clone());

        id
    }

    pub fn get_contact_display_name(&mut self, contact_id: &AnyContactID) -> Option<String> {
        if let Some(display_name) = self.display_name_cache.get(contact_id) {
            return display_name.clone();
        }

        let backend_contact_id: T::ContactID = T::ContactID::from((*contact_id).clone());
        let display_name = self.backend.get_contact_display_name(&backend_contact_id);
        self.display_name_cache
            .insert(contact_id.to_string(), display_name.clone());

        display_name
    }
}

impl<T: ContactResolverBackend> Default for ContactResolver<T>
where
    T::ContactID: From<AnyContactID>,
    T::ContactID: Into<AnyContactID>,
    T: Default,
{
    fn default() -> Self {
        Self::new(T::default())
    }
}
