//! All proxy class to devices, classes and servers.

use std::hash::{BuildHasherDefault, DefaultHasher, Hash, Hasher};
use std::sync::{Arc, OnceLock, Weak, Mutex};
use std::time::{Duration, SystemTime};
use std::collections::{HashMap, hash_map};
use std::marker::PhantomData;
use std::fmt::{self, Debug};
use std::str::FromStr;
use std::borrow::Cow;
use std::env;

use tokio::sync::{RwLock, RwLockReadGuard, RwLockWriteGuard};

use tracing::{debug, instrument};

use crate::backend::{self, AttrReadyChannelBackend, AttrValueChannelBackend, Backend, DevBackend, DevCommandBackend, DevGetAttrInfoBackend, DevGetCommandInfoBackend, DevReadAttrBackend, DevWriteAttrBackend};
use crate::database::{DatabaseProxy, GetManyProp, GetPropSpec, SetPropBuilder, SetPropSpec, get_prop_spec};
use crate::data::{Data, DataKind, DataLayoutKind, FromData, IntoData};
use crate::error::{Error, ErrorKind, Result, reason};
use crate::trl::{IntoTrl, Trl, TrlObject};


/// The internal cache for all backends, we are using a custom build hasher that is not
/// randomly generated, which allows us to construct the hashmap statically and not rely
/// on something like a OnceCell!
static PROXY_CACHE: Mutex<ProxyCache> = Mutex::new(ProxyCache::with_hasher(BuildHasherDefault::new()));

/// A device proxy is an object that allows querying a remote device.
/// 
/// Equality and hash between two device proxies is defined by if their target is similar
/// or not, not the actual backend being identical!
/// 
/// The [`Eq`] and [`Hash`] implementation is entirely based on the internal target of 
/// of the device, not its `trl`, nor its actual implementation details.
#[derive(Debug, Clone)]
pub struct DevProxy {
    /// The inner, shared state of the device proxy.
    /// When initialized, this DevProxy instance will initialize it with None as the 
    /// backend, therefore on the first initialization, we will be able to replace this
    /// dummy Arc with the real shared one!
    shared: Arc<ProxyShared>,
}

/// The inner, shared structure, of any device proxy. Once initialized, this will be 
/// shared within the proxy cache to avoid further requests.
#[derive(Debug)]
pub struct ProxyShared {
    /// The real target for connection of this proxy.
    target: ProxyTarget,
    /// After the first initialization, this inner will be shared among all device proxies
    /// pointing to the same target. The backend is behind a read-write lock, because most
    /// of the time we access the proxy backend in shared manner, but if the backend is
    /// disconnected, then we set this to none, to trigger a reconnection at some point.
    backend: RwLock<Option<ProxyBackend>>,
}

/// Information about the backend, when set, of the proxy inner structure.
#[derive(Debug)]
pub(crate) struct ProxyBackend {
    /// The actual backend.
    inner: Arc<dyn DevBackend>,
    /// In case the target is directly a database, this contains he host and port of the
    /// database connected to, this is used to construct the TRL.
    database_host_and_port: Option<(Arc<str>, u16)>,
}

/// Target for a device proxy, this is used so that the DevProxy can also be responsible
/// for connecting to a database as well as regular devices. The device name might be an
/// alias!
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) enum ProxyTarget {
    /// A local device backend, the backend is not specified here.
    Local {
        name: Arc<str>,
    },
    /// Database device from environment.
    DatabaseFromEnv,
    /// Database device from host and port.
    DatabaseAt {
        host: Arc<str>,
        port: u16,
    },
    /// Regular device from a database.
    RegularFromDatabase {
        database: Box<DatabaseProxy>,  // Boxed to avoid cyclic structure!
        name: Arc<str>,
    },
    /// A regular device accessible directly from a given service on a host, no database
    /// is involved!
    RegularAt {
        host: Arc<str>,
        port: u16,
        name: Arc<str>,
    },
}

impl DevProxy {

    /// Same as [`Self::try_new`] but panics if the TRL is invalid for a device proxy.
    #[inline]
    pub fn new(trl: impl IntoTrl) -> Self {
        Self::try_new(trl).expect("The given TRL does not target a device")
    }

    /// Create a new device proxy located by the given Tango Resource Locator, the given
    /// TRL should not target an attribute or property, or else this function will return
    /// None.
    pub fn try_new(trl: impl IntoTrl) -> Option<Self> {

        let trl = trl.into_trl().ok()?;

        // We can't target a property!
        if trl.property().is_some() {
            return None;
        }

        let name = match trl.object() {
            TrlObject::Alias(name) => name,
            TrlObject::Dev(name) => name,
            TrlObject::Attr(_, _) => return None,
        };

        Some(Self::new_with_target(match (trl.db(), trl.host_and_port()) {
            (true, Some((host, port))) => ProxyTarget::RegularFromDatabase { 
                database: Box::new(DatabaseProxy::new(host, port)),
                name: Arc::from(name),
            },
            (true, None) => ProxyTarget::RegularFromDatabase { 
                database: Box::new(DatabaseProxy::new_from_env()), 
                name: Arc::from(name),
            },
            (false, Some((host, port))) => ProxyTarget::RegularAt { 
                host: Arc::from(host), 
                port, 
                name: Arc::from(name),
            },
            (false, None) => ProxyTarget::Local { 
                // No database but no host/port specified, therefore we assume that we
                // should target a local served device.
                name: Arc::from(name),
            }
        }))
        
    }

    /// Create a new device from a database and the device's name.
    pub fn new_from_database(database: &DatabaseProxy, name: &str) -> Self {
        Self::new_with_target(ProxyTarget::RegularFromDatabase { 
            database: Box::new(database.clone()), 
            name: Arc::from(name),
        })
    }

    /// Internal function to create a device proxy for the given target.
    pub(crate) fn new_with_target(target: ProxyTarget) -> Self {

        // When create a device proxy, we just ensure that we are using the same 
        // ProxyShared for every device using the same target.
        let shared = match PROXY_CACHE.lock().unwrap().entry(target) {
            hash_map::Entry::Occupied(mut o) => {
                // If we can't upgrade, we just replace by a new entry!
                if let Some(shared) = o.get_mut().upgrade() {
                    shared
                } else {
                    let shared = Arc::new(ProxyShared {
                        target: o.key().clone(),
                        backend: RwLock::const_new(None),
                    });
                    o.insert(Arc::downgrade(&shared));
                    shared
                }
            }
            hash_map::Entry::Vacant(v) => {
                let shared = Arc::new(ProxyShared {
                    target: v.key().clone(),
                    backend: RwLock::const_new(None),
                });
                v.insert(Arc::downgrade(&shared));
                shared
            }
        };

        Self {
            shared,
        }

    }

    /// Internal function that tries to apply a function to each backend and return the
    /// first one that is valid, ignoring disconnected error by falling back to the next
    /// backend, or returning Ok(None) if no more fallback.
    async fn try_backends<'a, A, F>(backends: &'a [Arc<dyn Backend>], async_func: A) -> Result<Option<Arc<dyn DevBackend>>>
    where 
        A: Fn(&'a dyn Backend) -> F,
        F: Future<Output = Result<Arc<dyn DevBackend>>> + Send + 'a,
    {
        
        for backend in backends.iter() {
            match async_func(&**backend).await {
                Err(err) => match err.kind() {
                    // If the backend does not support connecting to a database...
                    ErrorKind::NotHandled => continue,
                    // This error should not be used when connecting to database, but
                    // we ignore it!
                    ErrorKind::DevNotFound => continue,
                    // If the device server at the given address is not connected for
                    // that backend, maybe it's not of the right protocol!
                    ErrorKind::ServerNotConnected => continue,
                    // The database device server is connected but has no database to
                    // return, don't try other backends, only break if.
                    ErrorKind::DevNotConnected => continue,
                    // Other error kinds are forwarded.
                    _ => return Err(err), 
                },
                Ok(dev) => return Ok(Some(dev)),
            };
        }

        Ok(None)

    }

    async fn ensure_backend(&self) -> Result<RwLockReadGuard<'_, ProxyBackend>> {

        // Try first returning
        let backend = self.shared.backend.read().await;
        let backend = match RwLockReadGuard::try_map(backend, |b| b.as_ref()) {
            Ok(ret) => return Ok(ret),
            Err(backend) => backend,
        };

        debug!("No backend, initializing");
        
        // Here we need to reconnect, so we'll need to write the backend, therefore we
        // acquire the lock here in order to prevent any other proxy to initialize it,
        // as it would be useless.
        drop(backend);
        let backend = self.shared.backend.write().await;

        // Once we have write-locked the backend, we check if it has been initialized 
        // since then! This can happen if two proxies tries to reconnect at the same time.
        let backend = match RwLockWriteGuard::try_downgrade_map(backend, |b| b.as_ref()) {
            Ok(ret) => return Ok(ret),
            Err(backend) => backend,
        };
        
        // Here we are sure that we are the only one to initialize a backend for that
        // specific target... So read the backends.
        let Some(backends) = backend::read_backends() else {
            return Err(Error::new(ErrorKind::DevNotConnected)
                .with_append_description("There are no backends, see backend::register_backend"));
        };

        let mut database_host_and_port = None;
        let new_backend = match self.shared.target {
            ProxyTarget::Local { .. } => {
                todo!()  // TODO: Go find the device from the local served devices.
            }
            ProxyTarget::DatabaseFromEnv => {

                debug!("Initializing database backend from env");
                let mut backend = None;
                for (host, port) in env_databases() {

                    backend = Self::try_backends(&backends[..], |backend| {
                        backend.connect_database(host, *port)
                    }).await?;

                    if backend.is_some() {
                        database_host_and_port = Some((host, *port));
                        break;
                    }

                }

                backend

            }
            ProxyTarget::DatabaseAt { ref host, port } => {
                debug!("Initializing database backend for {host}:{port}");
                database_host_and_port = Some((host, port));
                Self::try_backends(&backends[..], |backend| {
                    backend.connect_database(host, port)
                }).await?
            }
            ProxyTarget::RegularFromDatabase { ref database, ref name } => {
                
                debug!("Initializing backend for {name} from database");

                // NOTE: Pinning to avoid recursion in async code.
                let info = match Box::pin(database.import_dev(name)).await {
                    Ok(info) => info,
                    Err(err) if err.kind() == ErrorKind::DevNotConnected => 
                        return Err(Error::new(ErrorKind::DatabaseNotConnected)
                            .with_parent(err)),
                    Err(err) => return Err(err),
                };

                Self::try_backends(&backends[..], |backend| {
                    backend.connect_device(&info.itr)
                }).await?

            }
            ProxyTarget::RegularAt { ref host, port, ref name } => {
                debug!("Initializing backend for {name} at {host}:{port}");
                Self::try_backends(&backends[..], |backend| {
                    backend.connect_device_at(name, host, port)
                }).await?
            }
        };

        match new_backend {
            None => Err(Error::new(ErrorKind::DevNotConnected)),
            Some(new_backend) => {

                let mut backend = backend;
                *backend = Some(ProxyBackend {
                    inner: new_backend,
                    database_host_and_port: database_host_and_port.map(|(host, port)| (host.clone(), port)),
                });

                // Note: We unwrap because we just set the value above!
                Ok(RwLockWriteGuard::downgrade_map(backend, |backend| backend.as_ref().unwrap()))

            }
        }
        
    }

    /// Reset the internal backend by clearing it, thus forcing next call to re-request.
    async fn reset_backend(&self) {

        debug!("Reset backend");

        // We write-lock the backend in order to set it to None.
        let mut backend = self.shared.backend.write().await;
        *backend = None;

    }

    /// Internal utility function that wraps a call to the backend, ensuring that the 
    /// backend is initialized. If that call to the backend returns a "device not 
    /// connected" error, then the backend is reset for later reconnection.
    #[inline]
    async fn with_backend<'a, A, F, R>(&self, async_func: A) -> Result<R>
    where
        A: FnOnce(Arc<dyn DevBackend>) -> F,
        F: Future<Output = Result<R>> + Send + 'a,
    {
        let backend = self.ensure_backend().await?;
        let backend_inner = backend.inner.clone();
        drop(backend);
        match async_func(backend_inner).await {
            Ok(ret) => Ok(ret),
            Err(err) => {
                if err.kind() == ErrorKind::DevNotConnected {
                    self.reset_backend().await;
                }
                Err(err)
            }
        }
    }

    /// Return the [`DatabaseProxy`] this device has been requested from! If the device
    /// is no db and is not a database, then it returns None.
    pub fn database(&self) -> Result<&DatabaseProxy> {
        match self.shared.target {
            ProxyTarget::DatabaseFromEnv |
            ProxyTarget::DatabaseAt { .. } => 
                Ok(DatabaseProxy::from_dev(self)),
            ProxyTarget::RegularFromDatabase { ref database, .. } => 
                Ok(&**database),
            ProxyTarget::Local { .. } |
            ProxyTarget::RegularAt { .. } => 
                Err(Error::new(ErrorKind::DatabaseNeeded)),
        }
    }

    /// Internal method to get the name as a Arc<str>.
    async fn name_shared(&self) -> Result<Arc<str>> {
        self.with_backend(async move |backend| {
            backend.name().await
        }).await
    }

    /// Resolve this device's fully qualified device name.
    pub async fn trl(&self) -> Result<Trl> {
        match self.shared.target {
            ProxyTarget::Local { ref name } => {
                Ok(format!("tango://{name}#dbase=no").parse::<Trl>().unwrap())
            }
            ProxyTarget::DatabaseFromEnv => {

                let backend = self.ensure_backend().await?;
                let (host, port) = backend.database_host_and_port.as_ref()
                    .expect("This should be defined if the target is a database");

                let name = self.name_shared().await?;
                Ok(format!("tango://{host}:{port}/{name}").parse::<Trl>().unwrap())

            }
            ProxyTarget::DatabaseAt { ref host, port } => {
                let name = self.name_shared().await?;
                Ok(format!("tango://{host}:{port}/{name}").parse::<Trl>().unwrap())
            }
            ProxyTarget::RegularFromDatabase { ref database, ref name } => {

                let db_backend = database.ensure_backend().await?;
                let (host, port) = db_backend.database_host_and_port.as_ref()
                    .expect("This should be defined if the target is a database");

                Ok(format!("tango://{host}:{port}/{name}").parse::<Trl>().unwrap())

            }
            ProxyTarget::RegularAt { ref host, port, ref name } => {
                Ok(format!("tango://{host}:{port}/{name}#dbase=no").parse::<Trl>().unwrap())
            }
        }
    }

    /// Resolve this device's name.
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn name(&self) -> Result<String> {
        self.name_shared().await.map(|s| s.to_string())
    }

    /// Resolve this device's admin device and return it!
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn admin_dev(&self) -> Result<DevProxy> {
        
        let name = self.with_backend(async move |backend| {
            backend.admin_dev_name().await
        }).await?;

        Ok(DevProxy::new_with_target(match &self.shared.target {
            ProxyTarget::Local { .. } => todo!(),
            ProxyTarget::DatabaseFromEnv => todo!(),
            ProxyTarget::DatabaseAt { .. } => {
                // Self can be a DatabaseProxy, so we use it!
                ProxyTarget::RegularFromDatabase { 
                    database: Box::new(DatabaseProxy::with_dev(self.clone())), 
                    name,
                }
            }
            ProxyTarget::RegularFromDatabase { database, .. } => {
                ProxyTarget::RegularFromDatabase { 
                    database: database.clone(), 
                    name,
                }
            }
            ProxyTarget::RegularAt { host, port, .. } => {
                ProxyTarget::RegularAt { 
                    host: Arc::clone(host), 
                    port: *port, 
                    name,
                }
            }
        }))

    }

    /// Ping this device.
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn ping(&self) -> Result<()> {
        self.with_backend(async move |backend| {
            backend.ping().await
        }).await
    }

    /// Get the device state.
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn state(&self) -> Result<DevState> {
        self.with_backend(async move |backend| {
            backend.state().await
        }).await
    }

    /// Get the device status.
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn status(&self) -> Result<String> {
        self.with_backend(async move |backend| {
            backend.status().await
        }).await
    }

    /// Request the device black box.
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn black_box(&self, max: Option<usize>) -> Result<Vec<String>> {
        self.with_backend(async |backend| {
            backend.black_box(max).await
        }).await
    }

    /// Execute a remote command and return the raw owned result, from which you can try
    /// to parse a type that implements [`FromData`], which may borrow from the output.
    #[instrument(skip(self, data), fields(target = %self.shared.target))]
    pub async fn command_raw<'i, I>(&self, name: &str, data: I) -> Result<CommandOutput>
    where
        I: IntoData<'i>,
    {

        let data_in = data.into_data();
        let res = self.with_backend(async |backend| {
            backend.command(name, data_in).await
        }).await;

        // We treat any returned error in order to filter and set the proper rustTango 
        // error kind, associated to that standard reason.
        let backend = match res {
            Ok(backend) => backend,
            Err(err) => match err.kind() {
                ErrorKind::Reason(reason::API_CommandNotFound) => {
                    return Err(err.with_kind(ErrorKind::CommandNotFound));
                }
                ErrorKind::Reason(reason::API_IncompatibleCmdArgumentType) => {

                    let expected_kind = err.description().split_ascii_whitespace()
                        .find_map(|word| word.strip_prefix("Tango::"))
                        .and_then(data_kind_from_cpp_tango)
                        .unwrap_or(DataKind::Void);

                    return Err(
                        err.with_kind(ErrorKind::CommandArgumentKindInvalid)
                        .with_append_description(format_args!("Expected {expected_kind:?}")));
                        
                }
                _ => return Err(err),
            }
        };

        // TODO: Support for API_CommandTimedOut
        //   It happens when we execute a command on a server with device-level 
        //   serialization, which prevent it from executing our command because another
        //   action is holding the lock on the device monitor thread (another long 
        //   command or attribute).

        Ok(CommandOutput { 
            backend,
        })

    }

    /// Execute a remote command and directly parse the result into a type that 
    /// implements [`FromData`], it cannot borrow from the output.
    /// 
    /// If you want to borrow the output, use [`Self::command_raw`].
    pub async fn command<'i, I, O>(&self, name: &str, data: I) -> Result<O>
    where
        I: IntoData<'i>,
        O: for<'o> FromData<'o>,
    {
        let output = self.command_raw(name, data).await?;
        output.get::<O>()
    }

    /// Read all attributes on the device, thus returning an array to read each attribute
    /// with its type, note that the 'static lifetimes on the array means that the 
    /// returned names will be owned.
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn read_all_attr(&self) -> Result<ReadManyAttr> {
        
        let backend = self.with_backend(async |backend| {
            backend.read_attr(None).await
        }).await?;

        Ok(ReadManyAttr {
            len: backend.len(),
            backend,
        })

    }

    /// Read attributes and return a raw access to the underlying array, allowing to read
    /// every attribute with its own type that implements [`FromData`], thus allowing to
    /// borrow from the array itself.
    #[instrument(skip(self, names), fields(target = %self.shared.target))]
    pub async fn read_many_attr<Names: AsNames>(&self, names: Names) -> Result<ReadManyAttr> {
        
        let names = names.as_names();
        let names = names.as_ref();
        
        let backend = self.with_backend(async |backend| {
            backend.read_attr(Some(names)).await
        }).await?;

        let len = {
            if backend.len() != names.len() {
                return Err(Error::new(ErrorKind::Internal)
                    .with_description(format!("Expected {} attributes, got {}", names.len(), backend.len())));
            }
            names.len()
        };
        
        Ok(ReadManyAttr {
            len,
            backend,
        })

    }

    /// Read attributes given a specification of attributes. This specification is really
    /// flexible and allows to request a variable amount of attributes:
    /// - With a type `T` and a single name `&'name str`, you receive the value directly;
    /// - With a type `T` and an array of names `[&'name str; N]`, you receive a 
    ///   tuple with each result of read attribute;
    /// - With a tuple of types `(A, ...)` and a tuple of names `(&'name str, ...)`, 
    ///   you have tuple of result of the read attribute.
    pub async fn read_attr<Spec, Names: AsNames>(&self, names: Names) -> Result<Spec::Output>
    where
        Spec: ReadAttrSpec<Names>,
    {
        self.read_many_attr(names).await.and_then(|many| Spec::output(many, read_attr_spec::Token))
    }

    /// Create a builder to have a raw access to the list of attributes sent to be 
    /// written.
    #[inline]
    pub fn write_attr_builder<'data>(&self) -> WriteAttrBuilder<'_, 'data> {
        WriteAttrBuilder {
            dev: self,
            array: Vec::new(),
        }
    }

    /// Write attributes given a specification of [`AttrWrite`]-like values. This 
    /// specification is really flexible and allow single to many attributes to be written
    /// at once:
    /// - With a single `AttrWrite<'name, T>` value;
    /// - With a single tuple `(&'name str, T)`, with name as first component and value as
    ///   second component, the AttrWrite value is created on the fly with attribute 
    ///   quality [`AttrQuality::Valid`] and current time;
    /// - Some IntoIterator that yields one of the two first types;
    /// - A tuple of the two first types.
    pub async fn write_attr<Spec>(&self, attrs: Spec) -> Result<WriteManyAttr>
    where
        for<'data> Spec: WriteAttrSpec<'data>,
    {
        let mut builder = self.write_attr_builder();
        attrs.push_to_builder(&mut builder);
        builder.write().await
    }

    /// Get all command info on a device.
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn get_all_command_info(&self) -> Result<GetManyCommandInfo> {
        
        let backend = self.with_backend(async |backend| {
            backend.get_command_info(None).await
        }).await?;

        Ok(GetManyCommandInfo {
            len: backend.len(),
            backend,
        })

    }

    /// Get all command info on a device.
    #[instrument(skip(self, names), fields(target = %self.shared.target))]
    pub async fn get_many_command_info<Names: AsNames>(&self, names: Names) -> Result<GetManyCommandInfo> {
        
        let names = names.as_names();
        let names = names.as_ref();

        let backend = self.with_backend(async |backend| {
            backend.get_command_info(Some(names)).await
        }).await?;

        let len = {
            if backend.len() != names.len() {
                return Err(Error::new(ErrorKind::Internal)
                    .with_description(format!("Expected {} commands info, got {}", names.len(), backend.len())));
            }
            names.len()
        };

        Ok(GetManyCommandInfo {
            backend,
            len,
        })

    }
    
    /// Read all attributes info on a device.
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn get_all_attr_info(&self) -> Result<GetManyAttrInfo> {
        
        let backend = self.with_backend(async |backend| {
            backend.get_attr_info(None).await
        }).await?;

        Ok(GetManyAttrInfo {
            len: backend.len(),
            backend,
        })

    }

    /// Read many attribute info on a device.
    #[instrument(skip(self, names), fields(target = %self.shared.target))]
    pub async fn get_many_attr_info<Names: AsNames>(&self, names: Names) -> Result<GetManyAttrInfo> {
        
        let names = names.as_names();
        let names = names.as_ref();

        let backend = self.with_backend(async |backend| {
            backend.get_attr_info(Some(names)).await
        }).await?;

        let len = {
            if backend.len() != names.len() {
                return Err(Error::new(ErrorKind::Internal)
                    .with_description(format!("Expected {} attributes info, got {}", names.len(), backend.len())));
            }
            names.len()
        };

        Ok(GetManyAttrInfo {
            backend,
            len,
        })

    }

    // pub async fn get_attr_info<'name, Names>(&mut self, names: Names) -> Result<Names::Output>
    // where
    //     Names: GetAttrInfoNames<'name>,
    // {
        
    // }

    /// Subscribe to an attribute value for a specific event kind: Change, Periodic,
    /// Archive, User or Alarm. When awaited, this channel returns the latest event.
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn subscribe_attr_value<T>(&self, name: &str, event: AttrValueEvent) -> Result<AttrChannel<T>>
    where
        T: for<'d> FromData<'d>,
    {

        let Some(backends) = backend::read_event_backends() else {
            return Err(Error::new(ErrorKind::EventNotConnected)
                .with_append_description("There are no event backends, see backend::register_event_backend"));
        };

        for backend in &backends[..] {
            
            let backend = match backend.subscribe_attr_value(self, name, event).await {
                Ok(channel) => channel,
                Err(err) if err.kind() == ErrorKind::NotHandled => continue,
                Err(err) => return Err(err),
            };

            return Ok(AttrChannel { 
                backend, 
                name: name.to_string(),
                _phantom: PhantomData,
            });

        }
        
        Err(Error::new(ErrorKind::EventNotConnected)
            .with_append_description("None of the event backend handled the subscription"))

    }

    /// Subscribe to an attribute ready event. 
    /// When awaited, this channel returns the latest event.
    #[instrument(skip(self), fields(target = %self.shared.target))]
    pub async fn subscribe_attr_ready(&self, name: &str) -> Result<AttrReadyChannel> {
        
        let Some(backends) = backend::read_event_backends() else {
            return Err(Error::new(ErrorKind::EventNotConnected)
                .with_append_description("There are no event backends, see backend::register_event_backend"));
        };

        for backend in &backends[..] {
            
            let backend = match backend.subscribe_attr_ready(self, name).await {
                Ok(channel) => channel,
                Err(err) if err.kind() == ErrorKind::NotHandled => continue,
                Err(err) => return Err(err),
            };

            return Ok(AttrReadyChannel { 
                backend, 
                name: name.to_string(),
            });

        }

        Err(Error::new(ErrorKind::EventNotConnected)
            .with_append_description("None of the event backend handled the subscription"))

    }

    pub async fn subscribe_attr_conf(&self, name: &str) -> Result<()> {
        let _ = name;
        todo!()
    }

    pub async fn list_prop(&self, filter: &str) -> Result<Vec<String>> {
        let dev_name = self.name_shared().await?;
        self.database()?.list_dev_prop(&dev_name, filter).await
    }

    pub async fn get_all_prop(&self) -> Result<GetManyProp> {
        let dev_name = self.name_shared().await?;
        self.database()?.get_all_dev_prop(&dev_name).await
    }

    /// Get many properties on this device.
    pub async fn get_many_prop<Names: AsNames>(&self, names: Names) -> Result<GetManyProp> {
        let dev_name = self.name_shared().await?;
        self.database()?.get_many_dev_prop::<Names>(&dev_name, names).await
    }

    /// Get some properties for that device.
    pub async fn get_prop<Spec, Names: AsNames>(&self, names: Names) -> Result<Spec::Output>
    where
        Spec: GetPropSpec<Names>,
    {
        self.get_many_prop(names).await.and_then(|many| Spec::output(many, get_prop_spec::Token))
    }

    /// Create a builder for setting device properties with `DbPutDeviceProperty`.
    pub fn set_prop_builder<'data>(&self) -> SetPropBuilder<'_, 'data> {
        // We internally don't call the database method, because we need to pass an empty
        // object name as parameter, which indicate that the name of the device should be
        // queried just after.
        SetPropBuilder::new(self, "DbPutDeviceProperty", Cow::Borrowed(""))
    }

    /// Set some properties for that device.
    pub async fn set_prop<Spec>(&self, props: Spec) -> Result<()>
    where
        for<'data> Spec: SetPropSpec<'data>,
    {
        let mut builder = self.set_prop_builder();
        props.push_to_builder(&mut builder);
        builder.set().await
    }

    /// Delete device properties.
    pub async fn del_prop<Names: AsNames>(&self, names: Names) -> Result<()> {
        let dev_name = self.name_shared().await?;
        self.database()?.del_dev_prop(&dev_name, names).await
    }

    // pub async fn get_attr_properties<T>(&self, name: &str) -> Result<AttrProperties<T>, DevError> {
    //     todo!()
    // }

    // pub async fn set_attr_properties<T>(&self, name: &str, properties: AttrProperties<T>) -> Result<(), DevError> {
    //     todo!()
    // }

}

impl PartialEq for DevProxy {
    #[inline]
    fn eq(&self, other: &Self) -> bool {
        self.shared.target == other.shared.target
    }
}

impl Eq for DevProxy { }

impl Hash for DevProxy {
    #[inline]
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.shared.target.hash(state);
    }
}

/// The struct is internal so we just impl Display on it, in order to properly display it
/// in the tracing spans!
impl fmt::Display for ProxyTarget {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            ProxyTarget::Local { name } => 
                write!(f, "{name}@local"),
            ProxyTarget::DatabaseFromEnv => 
                write!(f, "db"),
            ProxyTarget::DatabaseAt { host, port } => 
                write!(f, "db@{host}:{port}"),
            ProxyTarget::RegularFromDatabase { name, .. } => 
                write!(f, "{name}"),
            ProxyTarget::RegularAt { host, port, name } => 
                write!(f, "{name}@{host}:{port}"),
        }
    }
}

// =========================== //
//         Proxy cache         //
// =========================== //

/// An internal type for the build hasher of the cache's hashmap.
/// We are currently using this custom hasher, because it can be const-initialized.
type CacheBuildHasher = BuildHasherDefault<DefaultHasher>;

/// Type alias for a hashmap of weak ProxyShared references.
type ProxyCache = HashMap<ProxyTarget, Weak<ProxyShared>, CacheBuildHasher>;

// =========================== //
//       AsNames support       //
// =========================== //


/// Internal sealed module that defines the internals of [`AsNames`].
mod as_names {
    
    /// Internal implementation details for the `AsNames` trait. The goal here is just to
    /// produce something that can be interpreted as an slice of name.
    pub trait Internal {
        /// Create some object that can be interpreted as a slice of names.
        fn as_names(&self) -> impl AsRef<[&'_ str]>;
    }

}

/// Supporting trait for being generic over the different types that can be interpreted 
/// as names, either single or many names.
pub trait AsNames: as_names::Internal { }
impl<T: as_names::Internal + ?Sized> AsNames for T { }

impl as_names::Internal for &'_ str {
    fn as_names(&self) -> impl AsRef<[&'_ str]> {
        [*self]
    }
}

impl as_names::Internal for &'_ [&'_ str] {
    fn as_names(&self) -> impl AsRef<[&'_ str]> {
        self
    }
}

impl<const N: usize> as_names::Internal for [&'_ str; N] {
    fn as_names(&self) -> impl AsRef<[&'_ str]> {
        &self[..]
    }
}

macro_rules! impl_as_names_tuple {
    ( @name: $comp:ident ) => { &'_ str };
    ( @one: $comp:ident ) => { 1 };
    ( $( $comp:ident )* ) => {
        #[allow(unused_variables)]
        impl as_names::Internal for ( $(impl_as_names_tuple!(@name: $comp),)* ) {
            fn as_names(&self) -> impl AsRef<[&'_ str]> {
                let ( $($comp,)* ) = *self;
                [ $($comp,)* ]
            }
        }
    };
}

impl_as_names_tuple!();
impl_as_names_tuple!(a);
impl_as_names_tuple!(a b);
impl_as_names_tuple!(a b c);
impl_as_names_tuple!(a b c d);
impl_as_names_tuple!(a b c d e);
impl_as_names_tuple!(a b c d e f);

// =========================== //
//           Command           //
// =========================== //

/// A view over the output of a command, this structure allows to get raw [`Data<'_>`]
/// borrowing the output itself and therefore avoid copying.
#[derive(Debug)]
pub struct CommandOutput {
    backend: Box<dyn DevCommandBackend>,
}

impl CommandOutput {

    /// Get the value from the command output, trying to parse it to the generic output
    /// type `O`, that type can borrow self.
    pub fn get<'d, T>(&'d self) -> Result<T>
    where
        T: FromData<'d>,
    {
        let data = self.backend.get()?;
        T::from_data(data)
            .map_err(|err| Error::new(ErrorKind::InvalidData)
                .with_append_description("Failed to parse command output value")
                .with_append_description(err))
    }

}

// =========================== //
//       Read attributes       //
// =========================== //

/// A view over an array of potential [`AttrRead`], this structure allows to get raw 
/// [`Data<'_>`] borrowing the array itself and therefore avoid copying. Thus allowing
/// to parse data while borrowing the array.
pub struct ReadManyAttr {
    backend: Box<dyn DevReadAttrBackend>,
    len: usize,
}

impl ReadManyAttr {

    /// Get the length of this array.
    #[inline]
    pub fn len(&self) -> usize {
        self.len
    }

    /// Get the [`AttrRead`] at the given index in the array, trying to parse it to the
    /// generic type `T`, that type can borrow self.
    pub fn get<'d, T>(&'d self, index: usize) -> Result<AttrRead<'d, T>>
    where
        T: FromData<'d>,
    {

        let attr_read = self.backend.get(index)?;

        let value = match attr_read.value {
            Some(val) => match T::from_data(val) {
                Ok(val) => Some(val),
                Err(err) => return Err(Error::new(ErrorKind::InvalidData)
                    .with_append_description("Failed to parse read value")
                    .with_append_description(err)),
            }
            None => None,
        };

        let w_value = match attr_read.w_value {
            Some(val) => match T::from_data(val) {
                Ok(val) => Some(val),
                Err(err) => return Err(Error::new(ErrorKind::InvalidData)
                    .with_append_description("Failed to parse write value")
                    .with_append_description(err)),
            }
            None => None,
        };

        Ok(AttrRead {
            name: attr_read.name,
            value,
            w_value,
            quality: attr_read.quality,
            time: attr_read.time,
        })

    }

}

impl Debug for ReadManyAttr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "ReadManyAttr(")?;
        f.debug_list().entries((0..self.len()).map(|i| self.get::<Data>(i))).finish()?;
        write!(f, ")")
    }
}

/// Internal sealed module that defines the internals of [`AttrReadSpec`].
pub(crate) mod read_attr_spec {
    
    use super::{ReadManyAttr, Result};
    
    pub struct Token;
    pub trait Internal<Names> {
        /// Kind of output type returned.
        type Output;
        /// Create the output from the given array.
        fn output(array: ReadManyAttr, _: Token) -> Result<Self::Output>;
    }
    
}

/// A trait for attribute specifiers, which allows a great flexibility in how 
/// [`DevProxy::read_attr`] works.
pub trait ReadAttrSpec<Names>: read_attr_spec::Internal<Names> { }
impl<Names, T: read_attr_spec::Internal<Names>> ReadAttrSpec<Names> for T { }

/// We implement the spec for all types that also implements [`for<'d> FromData<'d>`],
/// this requires just the name of the attribute and output directly the value's result.
impl<T> read_attr_spec::Internal<&'_ str> for T
where
    T: for<'d> FromData<'d>,
{

    type Output = AttrRead<'static, T>;

    #[inline]
    fn output(array: ReadManyAttr, _: read_attr_spec::Token) -> Result<Self::Output> {
        array.get(0).map(AttrRead::into_owned)
    }

}

impl<T> read_attr_spec::Internal<&'_ [&'_ str]> for T
where
    T: for<'d> FromData<'d>,
{

    type Output = Vec<Result<AttrRead<'static, T>>>;

    #[inline]
    fn output(array: ReadManyAttr, _: read_attr_spec::Token) -> Result<Self::Output> {
        Ok((0..array.len()).map(|i| array.get::<T>(i).map(AttrRead::into_owned)).collect())
    }

}

impl<T, const N: usize> read_attr_spec::Internal<[&'_ str; N]> for T
where
    T: for<'d> FromData<'d>,
{

    type Output = [Result<AttrRead<'static, T>>; N];

    #[inline]
    fn output(array: ReadManyAttr, _: read_attr_spec::Token) -> Result<Self::Output> {
        Ok(std::array::from_fn::<_, N, _>(|i| {
            array.get(i).map(AttrRead::into_owned)
        }))
    }

}

macro_rules! impl_read_attr_spec_tuple {
    ( @name: $comp:ident ) => { &'_ str };
    ( $( $comp:ident )* ) => {

        #[allow(non_snake_case, unused_assignments)]
        impl<$($comp,)* > read_attr_spec::Internal<( $( impl_read_attr_spec_tuple!(@name: $comp), )* )> for ( $($comp,)* )
        where
            $(
            $comp: for<'d> FromData<'d>,
            )*
        {
            
            type Output = ( $( Result<AttrRead<'static, $comp>>,)* );

            #[inline]
            fn output(array: ReadManyAttr, _: read_attr_spec::Token) -> Result<Self::Output> {
                let mut i = 0;
                $(
                    let $comp = array.get(i).map(AttrRead::into_owned);
                    i += 1;
                )*
                Ok( ( $($comp,)* ) )
            }

        }

    };
}

impl_read_attr_spec_tuple!(A);
impl_read_attr_spec_tuple!(A B);
impl_read_attr_spec_tuple!(A B C);
impl_read_attr_spec_tuple!(A B C D);
impl_read_attr_spec_tuple!(A B C D E);
impl_read_attr_spec_tuple!(A B C D E F);

// =========================== //
//      Write attributes       //
// =========================== //

/// A building for writing multiple attributes at once.
#[derive(Debug)]
pub struct WriteAttrBuilder<'dev, 'd> {
    dev: &'dev DevProxy,
    array: Vec<AttrWrite<'d, Data<'d>>>,
}

impl<'dev, 'd> WriteAttrBuilder<'dev, 'd> {

    /// Get the number of attributes currently in the builder.
    #[inline]
    pub fn len(&self) -> usize {
        self.array.len()
    }

    /// Push a new attribute write value to this builder.
    pub fn push<'dd: 'd, T>(&mut self, attr: AttrWrite<'dd, T>)
    where
        T: IntoData<'dd>,
    {
        self.array.push(AttrWrite {
            name: attr.name,
            value: attr.value.map(|v| v.into_data()),
            quality: attr.quality,
            time: attr.time,
        });
    }

    /// Execute the write and consume this builder, thus returning an 
    #[instrument(name = "write_attr_raw", skip(self), fields(target = %self.dev.shared.target))]
    pub async fn write(self) -> Result<WriteManyAttr> {

        let len = self.array.len();

        let backend = self.dev.with_backend(async |backend| {
            backend.write_attr(&self.array).await
        }).await?;

        Ok(WriteManyAttr {
            backend,
            len,
        })

    }

}

/// A view over the result of writing attributes, allowing to check the result of each 
/// write individually.
pub struct WriteManyAttr {
    backend: Box<dyn DevWriteAttrBackend>,
    len: usize,
}

impl WriteManyAttr {

    /// Get the length of this array.
    #[inline]
    pub fn len(&self) -> usize {
        self.len
    }

    /// Check the result for writing an attribute at the given index.
    pub fn check(&self, index: usize) -> Result<()> {
        
        let err = match self.backend.check(index) {
            Ok(()) => return Ok(()),
            Err(err) => err,
        };

        Err(match err.kind() {
            ErrorKind::Reason(reason::API_AttrNotFound) => {
                err.with_kind(ErrorKind::AttrNotFound)
            }
            ErrorKind::Reason(reason::API_AttrNotWritable) => {
                err.with_kind(ErrorKind::AttrNotWritable)
            }
            ErrorKind::Reason(reason::API_IncompatibleAttrDataType) => {

                let expected_kind = err.description().split_ascii_whitespace()
                    .find_map(|word| word.strip_prefix("Tango::"))
                    .and_then(data_kind_from_cpp_tango)
                    .unwrap_or(DataKind::Void);

                err.with_kind(ErrorKind::AttrKindInvalid)
                    .with_append_description(format_args!("Expected {expected_kind:?}"))
                
            }
            _ => err
        })

    }

}

impl Debug for WriteManyAttr {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "WriteManyAttr(")?;
        f.debug_list().entries((0..self.len()).map(|i| self.check(i))).finish()?;
        write!(f, ")")
    }
}

/// Internal sealed module that defines the internals of [`AttrWriteSpec`].
pub(crate) mod write_attr_spec {

    use super::WriteAttrBuilder;

    pub trait Internal<'d>: Sized {
        /// Push this write spec to the given write builder.
        fn push_to_builder(self, builder: &mut WriteAttrBuilder<'_, 'd>);
    }

    /// We use this trait to filter which IntoIterator types we accept as IntoIterator,
    /// we do that so no external crate could interfere into our type constraint. If we
    /// remove this bound, some extern crate could implement IntoIterator for (...) at
    /// some point, here it is still possible but this filter prevents us from breaking.
    pub trait IntoIteratorFilter {}
    impl<T, const N: usize> IntoIteratorFilter for [T; N] {}
    impl<T> IntoIteratorFilter for Vec<T> {}

}

/// An abstract specification of single to many AttrWrite-like, which is used by the
/// [`DevProxy::write_attr`] in order to be very flexible.
pub trait WriteAttrSpec<'d>: write_attr_spec::Internal<'d> { }
impl<'d, T: write_attr_spec::Internal<'d>> WriteAttrSpec<'d> for T { }

/// For a single attribute.
impl<'d, 'dd: 'd, T> write_attr_spec::Internal<'d> for AttrWrite<'dd, T>
where
    T: IntoData<'dd>,
{

    #[inline]
    fn push_to_builder(self, builder: &mut WriteAttrBuilder<'_, 'd>) {
        builder.push(self);
    }

}

/// For a singe attribute, but simplified to just a tuple with the name and the value.
impl<'d, 'dd: 'd, T> write_attr_spec::Internal<'d> for (&'dd str, T)
where
    T: IntoData<'dd>,
{

    #[inline]
    fn push_to_builder(self, builder: &mut WriteAttrBuilder<'_, 'd>) {
        builder.push(AttrWrite {
            name: Cow::Borrowed(self.0),
            value: Some(self.1),
            quality: AttrQuality::Valid,
            time: SystemTime::now(),
        });
    }

}

impl<'d, T, I> write_attr_spec::Internal<'d> for I
where
    T: WriteAttrSpec<'d>,
    I: IntoIterator<Item = T>,
    I: write_attr_spec::IntoIteratorFilter,
{

    #[inline]
    fn push_to_builder(self, builder: &mut WriteAttrBuilder<'_, 'd>) {
        for attr in self {
            attr.push_to_builder(&mut *builder);
        }
    }

}

macro_rules! impl_write_attr_spec_tuple {
    ( @name: $comp:ident ) => { &'name str };
    ( $( $comp:ident )* ) => {

        #[allow(non_snake_case, unused_assignments)]
        impl<'name, 'd, $($comp,)*> write_attr_spec::Internal<'d> for ( $( $comp, )* )
        where
            $(
                $comp: WriteAttrSpec<'d>,
            )*
        {

            #[inline]
            fn push_to_builder(self, builder: &mut WriteAttrBuilder<'_, 'd>) {
                let ( $($comp,)* ) = self;
                $(
                    $comp.push_to_builder(&mut *builder);
                )*
            }

        }

    };
}

impl_write_attr_spec_tuple!(A);
impl_write_attr_spec_tuple!(A B);
impl_write_attr_spec_tuple!(A B C);
impl_write_attr_spec_tuple!(A B C D);
impl_write_attr_spec_tuple!(A B C D E);
impl_write_attr_spec_tuple!(A B C D E F);

// =========================== //
//      Get command info       //
// =========================== //

pub struct GetManyCommandInfo {
    backend: Box<dyn DevGetCommandInfoBackend>,
    len: usize,
}

impl GetManyCommandInfo {

    #[inline]
    pub fn len(&self) -> usize {
        self.len
    }

    #[inline]
    pub fn get(&self, index: usize) -> Result<CommandInfo<'_>> {
        self.backend.get(index)
    }

}

impl Debug for GetManyCommandInfo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "GetManyCommandInfo(")?;
        f.debug_list().entries((0..self.len()).map(|i| self.get(i))).finish()?;
        write!(f, ")")
    }
}

// =========================== //
//  Get/Set attribute config   //
// =========================== //

/// A view over an array of potential [`AttrInfo`].
pub struct GetManyAttrInfo {
    backend: Box<dyn DevGetAttrInfoBackend>,
    len: usize,
}

impl GetManyAttrInfo {

    #[inline]
    pub fn len(&self) -> usize {
        self.len
    }

    #[inline]
    pub fn get(&self, index: usize) -> Result<AttrInfo<'_>> {
        self.backend.get(index)
    }

}

impl Debug for GetManyAttrInfo {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "GetManyAttrInfo(")?;
        f.debug_list().entries((0..self.len()).map(|i| self.get(i))).finish()?;
        write!(f, ")")
    }
}

// =========================== //
//          Commands           //
// =========================== //

/// Static information about a command.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommandInfo<'name> {
    /// The name of the read attribute.
    pub name: Cow<'name, str>,
    /// The display level of the attribute.
    pub display_level: DisplayLevel,
    /// Input data kind.
    pub in_kind: DataKind,
    /// Output data kind.
    pub out_kind: DataKind,
    /// Description for the output data.
    pub in_description: Option<String>,
    /// Description for the input data.
    pub out_description: Option<String>,
}

// =========================== //
//         Attributes          //
// =========================== //

/// Represent the reading for a single attribute.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttrRead<'name, T> {
    /// The name of the read attribute.
    pub name: Cow<'name, str>,
    /// The read value of the attribute, if any. Absent if quality is Invalid or if the
    /// attribute is not readable.
    pub value: Option<T>,
    /// The write value of the attribute, if any. Absent if quality is Invalid or if the
    /// attribute is not writable.
    pub w_value: Option<T>,
    /// The quality of the value.
    pub quality: AttrQuality,
    /// The timestamp of the value.
    pub time: SystemTime,
}

impl<'name, T> AttrRead<'name, T> {

    #[inline]
    fn into_owned(self) -> AttrRead<'static, T> {
        AttrRead {
            name: Cow::Owned(self.name.into_owned()),
            value: self.value,
            w_value: self.w_value,
            quality: self.quality,
            time: self.time,
        }
    }

}

/// Represent the writing for a single attribute.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttrWrite<'name, T> {
    /// The name of the attribute to write.
    pub name: Cow<'name, str>,
    /// The value of the attribute to write, if the value is invalid it could be none.
    pub value: Option<T>,
    /// The quality of the value.
    pub quality: AttrQuality,
    /// The timestamp of the value
    pub time: SystemTime,
}

/// Quality factor of the attribute.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrQuality {
    Valid,
    Invalid,
    Alarm,
    Changing,
    Warning,
}

/// An extension to AttrConfig with static information about the attribute.
#[derive(Debug, Clone, PartialEq)]
pub struct AttrInfo<'name> {
    /// The real runtime-modifiable configuration of the attribute.
    pub config: AttrConfig<'name>,
    /// The attribute mode.
    pub mode: AttrMode<'name>,
    /// The data kind to be expected.
    pub data_kind: DataKind,
}

/// Attribute read/write mode.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AttrMode<'name> {
    /// Read-only attribute
    Read,
    /// The attribute is technically read-only on the server side, but is associated to
    /// another write-only attribute that acts are the setpoint for that read-only value.
    ReadWithWrite {
        /// The name of the associated write attribute.
        write_attr_name: Cow<'name, str>,
    },
    /// Write-only attribute.
    Write,
    /// Read/write attribute.
    ReadWrite,
}

/// Represent the current configuration of an attribute
#[derive(Debug, Clone, PartialEq)]
pub struct AttrConfig<'name> {
    /// The name of the attribute this config belongs to. This cannot be changed!
    pub name: Cow<'name, str>,
    /// The optional description of the attribute. This cannot be changed!
    pub description: Option<String>,
    /// The optional label of the attribute.
    pub label: Option<String>,
    /// The optional real unit of the attribute: how the raw attribute value is 
    /// represented.
    pub unit: Option<String>,
    /// The conversion factor for going from the raw attribute value to the international
    /// unit system.
    pub standard_unit: Option<f32>,
    /// The optional unit for displaying to the user.
    pub display_unit: Option<String>,
    /// The display level of the attribute.
    pub display_level: DisplayLevel,
    /// The display format of this value. That format is specified using printf formatting
    /// symbols using `%`.
    pub display_format: Option<String>,
    /// The allowed range for this attribute's value.
    pub range: AttrRangeConfig,
    /// The alarm range for this attribute's value.
    pub alarm: AttrRangeConfig,
    /// The warning range for this attribute's value.
    pub warning: AttrRangeConfig,
    /// The optional delta alarm.
    pub delta: Option<AttrDeltaConfig>,
    /// Configuration of the change event.
    pub change_event: AttrChangeEventConfig,
    /// Configuration of the periodic event.
    pub periodic_event: AttrPeriodicEventConfig,
    /// Configuration of the periodic event.
    pub archive_event: AttrArchiveEventConfig,
    /// The memorized mode.
    pub memorized: Option<AttrMemorizedMode>,
    /// Possible extensions.
    pub extensions: Vec<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AttrRangeConfig {
    pub min: Option<String>,
    pub max: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AttrDeltaConfig {
    pub value: String,
    pub time: Option<Duration>,
}

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AttrChangeEventConfig {
    pub rel_change: Option<String>,
    pub abs_change: Option<String>,
}

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AttrPeriodicEventConfig {
    pub period: Option<Duration>,
}

#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct AttrArchiveEventConfig {
    pub rel_change: Option<String>,
    pub abs_change: Option<String>,
    pub period: Option<Duration>,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrMemorizedMode {
    Light,
    WriteAtInit,
}

// =========================== //
//        Various enums        //
// =========================== //

/// All possible device states.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DevState {
    On,
    Off,
    Close,
    Open,
    Insert,
    Extract,
    Moving,
    Standby,
    Fault,
    Init,
    Running,
    Alarm,
    Disable,
    Unknown,
}

/// Source when querying an attribute.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DevSource {
    /// Do not use the polling cache.
    Dev,
    /// Only use the polling cache.
    Cache,
    /// First use the polling cache, and fallback without it.
    CacheDev,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DisplayLevel {
    Operator,
    Expert,
}

/// Kind of event for a device attribute.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum AttrValueEvent {
    Change,
    Periodic,
    Archive,
    User,
    Alarm,
}

/// An attribute subscribe channel to receive events asynchronously.
pub struct AttrChannel<T> {
    backend: Box<dyn AttrValueChannelBackend>,
    name: String,
    _phantom: PhantomData<fn() -> T>,
}

impl<T> AttrChannel<T> {

    /// Wait for the latest unread data on this channel.
    pub async fn wait(&mut self) -> Result<AttrRead<'_, T>>
    where
        T: for<'d> FromData<'d>,
    {

        let data = self.backend.wait().await?;
        let attr_read = data.get()?;

        if attr_read.name != self.name {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description(format_args!("Incoherent name read ({:?}) from what was subscribed to ({:?})", 
                    attr_read.name, self.name)));
        }

        let value = match attr_read.value {
            Some(val) => match T::from_data(val) {
                Ok(val) => Some(val),
                Err(err) => return Err(Error::new(ErrorKind::InvalidData)
                    .with_append_description("Failed to parse read value")
                    .with_append_description(err)),
            }
            None => None,
        };

        let w_value = match attr_read.w_value {
            Some(val) => match T::from_data(val) {
                Ok(val) => Some(val),
                Err(err) => return Err(Error::new(ErrorKind::InvalidData)
                    .with_append_description("Failed to parse write value")
                    .with_append_description(err)),
            }
            None => None,
        };

        Ok(AttrRead {
            name: Cow::Borrowed(&self.name[..]),
            value,
            w_value,
            quality: attr_read.quality,
            time: attr_read.time,
        })

    }

}

/// An attribute subscribe channel to receive events asynchronously.
pub struct AttrReadyChannel {
    backend: Box<dyn AttrReadyChannelBackend>,
    name: String,
}

impl AttrReadyChannel {

    pub async fn wait(&mut self) -> Result<&'_ str> {
        self.backend.wait().await?;
        Ok(&self.name)
    }

}

// Try to parse the given cpp tango data type into a standard [`DataKind`].
fn data_kind_from_cpp_tango(cpp_type: &str) -> Option<DataKind> {
    // FIXME: Missing encoded for now! Waiting on Data module to support it.
    Some(match cpp_type {
        "DevState" => DataKind::State(DataLayoutKind::Scalar),
        "DevBoolean" => DataKind::Bool(DataLayoutKind::Scalar),
        "DevShort" => DataKind::Short(DataLayoutKind::Scalar),
        "DevLong" => DataKind::Long(DataLayoutKind::Scalar),
        "DevLong64" => DataKind::Long64(DataLayoutKind::Scalar),
        "DevFloat" => DataKind::Float(DataLayoutKind::Scalar),
        "DevDouble" => DataKind::Double(DataLayoutKind::Scalar),
        "DevUShort" => DataKind::UShort(DataLayoutKind::Scalar),
        "DevULong" => DataKind::ULong(DataLayoutKind::Scalar),
        "DevULong64" => DataKind::ULong64(DataLayoutKind::Scalar),
        "DevString" |
        "ConstDevString" => DataKind::String(DataLayoutKind::Scalar),
        "DevVarStateArray" => DataKind::State(DataLayoutKind::Spectrum(None)),
        "DevVarBooleanArray" => DataKind::Bool(DataLayoutKind::Spectrum(None)),
        "DevVarShortArray" => DataKind::Short(DataLayoutKind::Spectrum(None)),
        "DevVarLongArray" => DataKind::Long(DataLayoutKind::Spectrum(None)),
        "DevVarLong64Array" => DataKind::Long64(DataLayoutKind::Spectrum(None)),
        "DevVarFloatArray" => DataKind::Float(DataLayoutKind::Spectrum(None)),
        "DevVarDoubleArray" => DataKind::Double(DataLayoutKind::Spectrum(None)),
        "DevVarUShortArray" => DataKind::UShort(DataLayoutKind::Spectrum(None)),
        "DevVarULongArray" => DataKind::ULong(DataLayoutKind::Spectrum(None)),
        "DevVarULong64Array" => DataKind::ULong64(DataLayoutKind::Spectrum(None)),
        "DevVarStringArray" => DataKind::String(DataLayoutKind::Spectrum(None)),
        "DevVarLongStringArray" => DataKind::Struct(Cow::Borrowed(&[
            DataKind::Long(DataLayoutKind::Spectrum(None)),
            DataKind::String(DataLayoutKind::Spectrum(None)),
        ])),
        "DevVarDoubleStringArray" => DataKind::Struct(Cow::Borrowed(&[
            DataKind::Double(DataLayoutKind::Spectrum(None)),
            DataKind::String(DataLayoutKind::Spectrum(None)),
        ])),
        _ => return None,
    })
}

/// Get the database host/port tuples from the `TANGO_HOST` environment variable.
fn env_databases() -> &'static [(Arc<str>, u16)] {
    static CACHE: OnceLock<Box<[(Arc<str>, u16)]>> = OnceLock::new();
    &**CACHE.get_or_init(|| {

        let mut ret = Vec::new();

        if let Ok(var) = env::var("TANGO_HOST") {
            for part in var.split(',') {
                if let Some((host, port_raw)) = part.rsplit_once(':')
                && let Ok(port) = u16::from_str(port_raw) {
                    ret.push((host.into(), port));
                }
            }
        }
        
        ret.into_boxed_slice()

    })
}
