//! Proxy to database devices.

use std::ops::{Deref, DerefMut};
use std::fmt::{self, Debug};
use std::borrow::Cow;
use std::{mem, vec};
use std::sync::Arc;

use crate::data::{Concat, Data, DataLayout, DataLayoutView, DataView};
use crate::dev::{AsNames, DevProxy, ProxyTarget};
use crate::error::{Error, ErrorKind, Result, reason};
use crate::prop::{FromProp, IntoProp};


/// Proxy to a database with easy abstraction for commands that request devices.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[repr(transparent)]
pub struct DatabaseProxy {
    dev: DevProxy,
}

impl DatabaseProxy {

    /// New handle to the environment database, if any.
    pub fn new_from_env() -> Self {
        Self {
            dev: DevProxy::new_with_target(ProxyTarget::DatabaseFromEnv),
        }
    }

    /// New handle to the environment database, if any.
    pub fn new(host: &str, port: u16) -> Self {
        Self {
            dev: DevProxy::new_with_target(ProxyTarget::DatabaseAt { 
                host: Arc::from(host), 
                port,
            }),
        }
    }

    /// Internal function to reinterpret a device proxy reference as a database proxy.
    pub(crate) fn from_dev(dev: &DevProxy) -> &DatabaseProxy {
        // SAFETY: DatabaseProxy is transparent over DevProxy
        unsafe { &*(dev as *const _ as *const DatabaseProxy) }
    }

    /// Internal function to manually create a database proxy by assuming that the given
    /// device proxy is such device.
    pub(crate) fn with_dev(dev: DevProxy) -> Self {
        Self {
            dev,
        }
    }

    #[doc(alias = "DbInfo")]
    pub async fn info(&self) -> Result<String> {
        self.command::<_, Concat<String>>("DbInfo", ()).await.map(|Concat(s)| s)
    }

    #[doc(alias = "DbAddDevice")]
    pub async fn add_dev(&self, name: &str, class: &str, server: &str) -> Result<()> {
        self.command("DbAddDevice", [server, name, class]).await
    }

    #[doc(alias = "DbDeleteDevice")]
    pub async fn delete_dev(&self, name: &str) -> Result<()> {
        self.command("DbDeleteDevice", name).await
    }

    // TODO: add/delete server

    #[doc(alias = "DbRenameServer")]
    pub async fn rename_server(&self, name: &str, new_name: &str) -> Result<()> {
        self.command("DbRenameServer", [name, new_name]).await
    }

    /// List all existing devices, filtering using a filter, using a pattern for 
    /// matching with domain/family/member pattern, supporting one '*' per member.
    #[doc(alias = "DbGetDeviceWideList")]
    pub async fn list_dev(&self, filter: &str) -> Result<Vec<String>> {
        self.command("DbGetDeviceWideList", filter).await
    }

    /// Same as [`Self::list_dev`] but it will only returns all the member part of the
    /// device names (in domain/family/member).
    #[doc(alias = "DbGetDeviceMemberList")]
    pub async fn list_dev_member(&self, filter: &str) -> Result<Vec<String>> {
        self.command("DbGetDeviceMemberList", filter).await
    }

    /// Same as [`Self::list_dev`] but it will only returns all the family part of the
    /// device names (in domain/family/member).
    #[doc(alias = "DbGetDeviceFamilyList")]
    pub async fn list_dev_family(&self, filter: &str) -> Result<Vec<String>> {
        self.command("DbGetDeviceFamilyList", filter).await
    }

    /// Same as [`Self::list_dev`] but it will only returns all the domain part of the
    /// device names (in domain/family/member).
    #[doc(alias = "DbGetDeviceDomainList")]
    pub async fn list_dev_domain(&self, filter: &str) -> Result<Vec<String>> {
        self.command("DbGetDeviceDomainList", filter).await
    }

    #[doc(alias = "DbExportDevice")]
    pub async fn export_dev(&self, info: ExportDevInfo<'_>) -> Result<()> {
        self.command("DbExportDevice", data_from_export_dev_info(&[info][..])).await
    }

    #[doc(alias = "DbUnExportDevice")]
    pub async fn unexport_dev(&self, dev_name: &str) -> Result<()> {
        self.command("DbUnExportDevice", dev_name).await
    }

    #[doc(alias = "DbExportDevice")]
    pub async fn export_server(&self, infos: &[ExportDevInfo<'_>]) -> Result<()> {
        self.command("DbExportDevice", data_from_export_dev_info(infos)).await
    }

    #[doc(alias = "DbUnExportServer")]
    pub async fn unexport_server(&self, server: &str) -> Result<()> {
        self.command("DbUnExportServer", server).await
    }

    /// Same input as [`Self::list_dev`], but it will only returns devices that should
    /// currently be exported.
    #[doc(alias = "DbGetDeviceExportedList")]
    pub async fn list_exported_dev(&self, filter: &str) -> Result<Vec<String>> {
        self.command("DbGetDeviceExportedList", filter).await
    }

    #[doc(alias = "DbImportDevice")]
    pub async fn import_dev(&self, dev_name: &str) -> Result<ImportDevInfo> {
        
        let output = self.command_raw("DbImportDevice", dev_name).await?;
        let (
            [exported, pid],
            mut svalue,
        ) = match output.get::<([i32; 2], Vec<String>)>() {
            Ok(ret) => ret,
            Err(err) => {
                if let ErrorKind::Reason(reason::DB_DeviceNotDefined) = err.kind() {
                    return Err(Error::new(ErrorKind::DevNotFound).with_parent(err));
                } else {
                    return Err(err);
                }
            }
        };

        if svalue.len() != 5 && svalue.len() != 6 {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description("Data.1 should be a string spectrum of len 5 or 6")
                .with_append_description(format_args!("{svalue:?}")));
        }

        Ok(ImportDevInfo {
            name: mem::take(&mut svalue[0]),
            itr: mem::take(&mut svalue[1]),
            host: mem::take(&mut svalue[4]),
            version: mem::take(&mut svalue[2]),
            server: mem::take(&mut svalue[3]),
            class: svalue.get_mut(5).map(mem::take),
            exported: exported == 1,
            pid,
        })

    }

    #[doc(alias = "DbGetDeviceInfo")]
    pub async fn get_dev_info(&self, dev_name: &str) -> Result<DevInfo> {

        let output = self.command_raw("DbGetDeviceInfo", dev_name).await?;
        let (
            [exported, pid],
            mut svalue,
        ) = match output.get::<([i32; 2], Vec<String>)>() {
            Ok(ret) => ret,
            Err(err) => {
                if let ErrorKind::Reason(reason::DB_DeviceNotDefined) = err.kind() {
                    return Err(Error::new(ErrorKind::DevNotFound).with_parent(err));
                } else {
                    return Err(err);
                }
            }
        };

        if svalue.len() != 7 && svalue.len() != 8 {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description("Data.1 should be a string spectrum of len 7 or 8")
                .with_append_description(format_args!("{svalue:?}")));
        }

        Ok(DevInfo {
            name: mem::take(&mut svalue[0]),
            itr: mem::take(&mut svalue[1]),
            host: Some(mem::take(&mut svalue[4])).filter(|s| s != "nada"),
            version: mem::take(&mut svalue[2]),
            server: mem::take(&mut svalue[3]),
            class: svalue.get_mut(7).map(mem::take),
            last_exported: Some(mem::take(&mut svalue[5])).filter(|s| s != "?"),
            last_unexported: Some(mem::take(&mut svalue[6])).filter(|s| s != "?"),
            exported: exported == 1,
            pid: Some(pid).filter(|&pid| pid != 0),
        })

    }
    
    /// Get alias for a given device.
    #[doc(alias = "DbGetDeviceAlias")]
    pub async fn get_dev_alias(&self, dev_name: &str) -> Result<Option<String>> {
        match self.command::<_, String>("DbGetDeviceAlias", dev_name).await {
            Ok(ret) => Ok(Some(ret)),
            Err(err) => {
                if let ErrorKind::Reason(reason::DB_AliasNotDefined) = err.kind() {
                    Ok(None)
                } else {
                    Err(err)
                }
            }
        }
    }

    /// Resolve a device alias, returning DevNotFound if not found.
    #[doc(alias = "DbGetAliasDevice")]
    pub async fn resolve_dev_alias(&self, alias: &str) -> Result<String> {
        match self.command("DbGetAliasDevice", alias).await {
            Ok(ret) => Ok(ret),
            Err(err) => {
                if let ErrorKind::Reason(reason::DB_AliasNotDefined) = err.kind() {
                    Err(Error::new(ErrorKind::AliasNotFound).with_parent(err))
                } else {
                    Err(err)
                }
            }
        }
    }

    /// List all device aliases matching the given filter (using at most one '*').
    #[doc(alias = "DbGetAliasDevice")]
    pub async fn list_dev_aliases(&self, filter: &str) -> Result<Vec<String>> {
        self.command("DbGetAliasDevice", filter).await
    }

    /// Get alias for a given device.
    #[doc(alias = "DbGetAttributeAlias")]
    pub async fn get_attr_alias(&self, attr_name: &str) -> Result<Option<String>> {
        match self.command::<_, String>("DbGetAttributeAlias", attr_name).await {
            Ok(ret) => Ok(Some(ret)),
            Err(err) => {
                if let ErrorKind::Reason(reason::DB_AliasNotDefined) = err.kind() {
                    Ok(None)
                } else {
                    Err(err)
                }
            }
        }
    }

    #[doc(alias = "DbGetAliasAttribute")]
    pub async fn resolve_attr_alias(&self, alias: &str) -> Result<String> {
        match self.command("DbGetAliasAttribute", alias).await {
            Ok(ret) => Ok(ret),
            Err(err) => {
                if let ErrorKind::Reason(reason::DB_AliasNotDefined) = err.kind() {
                    Err(Error::new(ErrorKind::AliasNotFound).with_parent(err))
                } else {
                    Err(err)
                }
            }
        }
    }

    /// List all device aliases matching the given filter (using at most one '*').
    #[doc(alias = "DbGetAttributeAliasList")]
    pub async fn list_attr_aliases(&self, filter: &str) -> Result<Vec<String>> {
        self.command("DbGetAttributeAliasList", filter).await
    }

    // For easier monomorphization...
    async fn get_many_prop_inner(&self, 
        cmd_name: &str, 
        obj_name: &str, 
        names: &[&str],
    ) -> Result<(Box<[Cow<'static, str>]>, Box<[(usize, usize)]>)> {

        let params = Some(obj_name).into_iter()
            .chain(names.as_ref().into_iter().copied())
            .collect::<Vec<_>>();
        
        let output = self.command_raw(cmd_name, params).await?;
        let data = output.get::<Data>()?;
        let values = match data {
            Data::String(DataLayout::Spectrum(strings)) => 
                strings.map(|s| Cow::<str>::Owned(s.into_owned())),
            Data::String(DataLayout::SpectrumView(strings)) => 
                strings.map_to_owned(|s| Cow::Owned(s.to_string())),
            _ => return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description("Data should be a spectrum of strings")
                .with_append_description(format_args!("{data:?}"))),
        }.into_boxed_slice();

        let Some(output_dev_name) = values.get(0) else {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description("Missing device name")
                .with_append_description(format_args!("{values:?}")));
        };

        if !output_dev_name.eq_ignore_ascii_case(obj_name) {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description(format_args!("Incoherent name get ({:?}) from what was requested ({:?})",
                    output_dev_name, obj_name)));
        }

        let Some(count_raw) = values.get(1) else {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description("Missing property count")
                .with_append_description(format_args!("{values:?}")));
        };

        let Ok(count) = count_raw.parse::<usize>() else {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description(format_args!("Invalid property count: {count_raw:?}")));
        };

        if count != names.as_ref().len() {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description(format_args!("Incoherent property count ({count}) from what was requested ({})",
                    names.as_ref().len())));
        }

        let mut indices = Vec::with_capacity(count);

        let mut values_idx = 2usize;
        for prop_idx in 0..count {

            if values_idx + 2 > values.len() {
                return Err(Error::new(ErrorKind::InvalidData)
                    .with_append_description("Missing property name and its count")
                    .with_append_description(format_args!("{values:?}")));
            }

            let prop_name = &*values[values_idx + 0];
            if !prop_name.eq_ignore_ascii_case(names[prop_idx]) {
                return Err(Error::new(ErrorKind::InvalidData)
                    .with_append_description(format_args!("Incoherent property name ({:?}) from what was requested ({:?})",
                        prop_name, names[indices.len()])));
            }

            let Ok(count) = values[values_idx + 1].parse::<usize>() else {
                return Err(Error::new(ErrorKind::InvalidData)
                    .with_append_description(format_args!("Invalid property values count ({count_raw:?})")));
            };

            values_idx += 2;
            let begin = values_idx;
            values_idx += count;
            
            if values_idx > values.len() {
                return Err(Error::new(ErrorKind::InvalidData)
                    .with_append_description("Missing property values")
                    .with_append_description(format_args!("{values:?}")));
            }

            indices.push((begin, values_idx));
            
        }

        Ok((values, indices.into_boxed_slice()))

    }

    #[doc(alias = "DbGetPropertyList")]
    pub async fn list_free_prop(&self, obj_name: &str, filter: &str) -> Result<Vec<String>> {
        self.command("DbGetPropertyList", [obj_name, filter]).await
    }

    #[doc(alias = "DbGetClassPropertyList")]
    #[doc(alias = "DbGetClassPropertyListWildcard")]
    pub async fn list_class_prop(&self, obj_name: &str, filter: &str) -> Result<Vec<String>> {
        if filter == "*" {
            self.command("DbGetClassPropertyList", [obj_name]).await
        } else {
            self.command("DbGetClassPropertyListWildcard", [obj_name, filter]).await
        }
    }

    #[doc(alias = "DbGetDevicePropertyList")]
    pub async fn list_dev_prop(&self, obj_name: &str, filter: &str) -> Result<Vec<String>> {
        self.command("DbGetDevicePropertyList", [obj_name, filter]).await
    }

    async fn get_all_prop(&self, list_cmd_name: &str, with_wildcard: bool, get_cmd_name: &str, obj_name: &str) -> Result<GetManyProp> {
        
        let output = if with_wildcard {
            self.command_raw(list_cmd_name, [obj_name, "*"]).await?
        } else {
            self.command_raw(list_cmd_name, [obj_name]).await?
        };

        let data = output.get::<Data>()?;
        let DataView::String(DataLayoutView::Spectrum(names)) = data.as_view() else {
            return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description("Names should be a string spectrum")
                .with_append_description(format_args!("{data:?}")));
        };

        let names = names.as_raw_slice()
            .into_iter()
            .map(|s| &**s)
            .collect::<Vec<_>>();

        // If we don't have any property to request, then we are required to return a
        // dummy GetManyProp with no value, we don't want, and don't need, to call the
        // get property function. Because in that case, it will return an error about
        // an incorrect number of arguments being given.
        if names.is_empty() {
            return Ok(GetManyProp { 
                values: Box::new([]), 
                indices: Box::new([]),
            });
        }

        let (
            values, 
            indices
        ) = self.get_many_prop_inner(get_cmd_name, obj_name, &names).await?;

        Ok(GetManyProp {
            values,
            indices,
        })

    }

    pub async fn get_all_free_prop(&self, obj_name: &str) -> Result<GetManyProp> {
        self.get_all_prop("DbGetPropertyList", true, "DbGetProperty", obj_name).await
    }

    pub async fn get_all_class_prop(&self, class_name: &str) -> Result<GetManyProp> {
        self.get_all_prop("DbGetClassPropertyList", false, "DbGetClassProperty", class_name).await
    }

    pub async fn get_all_dev_prop(&self, dev_name: &str) -> Result<GetManyProp> {
        self.get_all_prop("DbGetDevicePropertyList", true, "DbGetDeviceProperty", dev_name).await
    }

    async fn get_many_prop<Names: AsNames>(&self, cmd_name: &str, obj_name: &str, names: Names) -> Result<GetManyProp> {

        let names = names.as_names();
        let (
            values, 
            indices
        ) = self.get_many_prop_inner(cmd_name, obj_name, names.as_ref()).await?;

        Ok(GetManyProp {
            values,
            indices,
        })

    }

    #[doc(alias = "DbGetProperty")]
    pub async fn get_many_free_prop<Names: AsNames>(&self, obj_name: &str, names: Names) -> Result<GetManyProp> {
        self.get_many_prop::<Names>("DbGetProperty", obj_name, names).await
    }

    #[doc(alias = "DbGetClassProperty")]
    pub async fn get_many_class_prop<Names: AsNames>(&self, class_name: &str, names: Names) -> Result<GetManyProp> {
        self.get_many_prop::<Names>("DbGetClassProperty", class_name, names).await
    }

    #[doc(alias = "DbGetDeviceProperty")]
    pub async fn get_many_dev_prop<Names: AsNames>(&self, dev_name: &str, names: Names) -> Result<GetManyProp> {
        self.get_many_prop::<Names>("DbGetDeviceProperty", dev_name, names).await
    }

    /// Wrapper around `DbGetProperty` command on the database.
    pub async fn get_free_prop<Spec, Names: AsNames>(&self, obj_name: &str, names: Names) -> Result<Spec::Output>
    where
        Spec: GetPropSpec<Names>,
    {
        self.get_many_free_prop(obj_name, names).await.and_then(|many| Spec::output(many, get_prop_spec::Token))
    }

    /// Wrapper around `DbGetClassProperty` command on the database.
    pub async fn get_class_prop<Spec, Names: AsNames>(&self, class_name: &str, names: Names) -> Result<Spec::Output>
    where
        Spec: GetPropSpec<Names>,
    {
        self.get_many_class_prop(class_name, names).await.and_then(|many| Spec::output(many, get_prop_spec::Token))
    }

    /// Wrapper around `DbGetDeviceProperty` command on the database.
    pub async fn get_dev_prop<Spec, Names: AsNames>(&self, dev_name: &str, names: Names) -> Result<Spec::Output>
    where
        Spec: GetPropSpec<Names>,
    {
        self.get_many_dev_prop(dev_name, names).await.and_then(|many| Spec::output(many, get_prop_spec::Token))
    }

    pub fn set_free_prop_builder<'data>(&self, obj_name: &'data str) -> SetPropBuilder<'_, 'data> {
        assert!(!obj_name.is_empty(), "object name should not be empty");
        SetPropBuilder::new(self, "DbPutProperty", Cow::Borrowed(obj_name))
    }

    pub fn set_class_prop_builder<'data>(&self, class_name: &'data str) -> SetPropBuilder<'_, 'data> {
        assert!(!class_name.is_empty(), "class name should not be empty");
        SetPropBuilder::new(self, "DbPutClassProperty", Cow::Borrowed(class_name))
    }

    pub fn set_dev_prop_builder<'data>(&self, dev_name: &'data str) -> SetPropBuilder<'_, 'data> {
        assert!(!dev_name.is_empty(), "device name should not be empty");
        SetPropBuilder::new(self, "DbPutDeviceProperty", Cow::Borrowed(dev_name))
    }

    #[doc(alias = "DbPutProperty")]
    pub async fn set_free_prop<'data, Spec>(&self, obj_name: &'data str, props: Spec) -> Result<()>
    where
        Spec: SetPropSpec<'data>,
    {
        let mut builder = self.set_free_prop_builder(obj_name);
        props.push_to_builder(&mut builder);
        builder.set().await
    }

    #[doc(alias = "DbPutClassProperty")]
    pub async fn set_class_prop<'data, Spec>(&self, class_name: &'data str, props: Spec) -> Result<()>
    where
        Spec: SetPropSpec<'data>,
    {
        let mut builder = self.set_class_prop_builder(class_name);
        props.push_to_builder(&mut builder);
        builder.set().await
    }

    #[doc(alias = "DbPutDeviceProperty")]
    pub async fn set_dev_prop<'data, Spec>(&self, dev_name: &'data str, props: Spec) -> Result<()>
    where
        Spec: SetPropSpec<'data>,
    {
        let mut builder = self.set_dev_prop_builder(dev_name);
        props.push_to_builder(&mut builder);
        builder.set().await
    }

    /// Internal function common to all delete properties functions.
    async fn del_prop<Names: AsNames>(&self, cmd_name: &str, obj_name: &str, names: Names) -> Result<()> {
        
        let names = names.as_names();
        let params = Some(obj_name).iter()
            .chain(names.as_ref())
            .map(|&s| Cow::Borrowed(s))
            .collect::<Vec<_>>();

        self.command::<_, ()>(cmd_name, params).await

    }

    #[doc(alias = "DbDeleteProperty")]
    pub async fn del_obj_prop<Names: AsNames>(&self, obj_name: &str, names: Names) -> Result<()> {
        self.del_prop("DbDeleteProperty", obj_name, names).await
    }

    #[doc(alias = "DbDeleteClassProperty")]
    pub async fn del_class_prop<Names: AsNames>(&self, class_name: &str, names: Names) -> Result<()> {
        self.del_prop("DbDeleteClassProperty", class_name, names).await
    }

    #[doc(alias = "DbDeleteDeviceProperty")]
    pub async fn del_dev_prop<Names: AsNames>(&self, dev_name: &str, names: Names) -> Result<()> {
        self.del_prop("DbDeleteDeviceProperty", dev_name, names).await
    }

    // /// Internal function common too all property history functions.
    // async fn get_prop_history<T>(&mut self, cmd_name: &str, obj_name: &str, attr_name: Option<&str>, prop_name: &str) -> Result<PropHistory<'static, T>> {

    //     let output = self.command_raw("cmd_name", if let Some(attr_name) = attr_name {
    //         &[obj_name, attr_name, prop_name][..]
    //     } else {
    //         &[obj_name, prop_name][..]
    //     }).await?;

    //     let data = output.get::<Data>()?;
    //     let DataView::String(DataLayoutView::Spectrum(data)) = data.as_view() else {
    //         return Err(Error::new_data(format!("Property history should be a string spectrum: {data:?}")));
    //     };

    //     let raw = data.as_raw_slice();

    //     let mut i = 0usize;
    //     loop {

    //         let attr_name = if attr_name.is_some() {
    //             i += 1;
    //             Some(&*raw[i - 1])
    //         } else {
    //             None
    //         };

    //         let prop_name = &*raw[i + 0];
    //         let time = &*raw[i + 1];
    //         let count = raw[i + 2].parse::<()

    //     }


    // }

}

impl Deref for DatabaseProxy {
    type Target = DevProxy;
    fn deref(&self) -> &Self::Target {
        &self.dev
    }
}

impl DerefMut for DatabaseProxy {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.dev
    }
}

// =========================== //
//    Device import/export     //
// =========================== //

/// Represent the informations required to export a device.
#[derive(Debug, Clone)]
pub struct ExportDevInfo<'a> {
    pub name: Cow<'a, str>,
    pub itr: Cow<'a, str>,
    pub host: Cow<'a, str>,
    pub version: Cow<'a, str>,
    pub pid: i32,
}

fn data_from_export_dev_info<'a>(infos: &'a [ExportDevInfo<'a>]) -> Data<'a> {

    let mut vec = Vec::with_capacity(infos.len() * 5);

    for info in infos {
        vec.push(Cow::Borrowed(&*info.name));
        vec.push(Cow::Borrowed(&*info.itr));
        vec.push(Cow::Borrowed(&*info.host));
        vec.push(Cow::Owned(format!("{}", info.pid)));
        vec.push(Cow::Borrowed(&*info.version));
    }

    Data::String(DataLayout::Spectrum(vec.into()))

}

/// Information for how to import a device from the database.
#[derive(Debug, Clone)]
pub struct ImportDevInfo {
    /// The name of the device.
    pub name: String,
    /// The Interoperable Tango Reference to join the device.
    pub itr: String,
    /// The device host.
    pub host: String,
    /// The device version.
    pub version: String,
    /// The device server.
    pub server: String,
    /// Class of this device.
    pub class: Option<String>,
    /// True if the device is currently exported by a server.
    pub exported: bool,
    /// The last known Process ID of the device's server.
    pub pid: i32,
}

#[derive(Debug, Clone)]
pub struct DevInfo {
    /// The name of the device.
    pub name: String,
    /// The Interoperable Tango Reference to join the device.
    pub itr: String,
    /// The device host.
    pub host: Option<String>,
    /// The device version.
    pub version: String,
    /// The device server.
    pub server: String,
    /// Class of this device.
    pub class: Option<String>,
    pub last_exported: Option<String>,    // TODO: Convert to chrono DateTime
    pub last_unexported: Option<String>,  // TODO: Convert to chrono DateTime
    /// True if the device is currently exported by a server.
    pub exported: bool,
    /// The last known Process ID of the device's server.
    pub pid: Option<i32>,
}

// =========================== //
//       Get properties        //
// =========================== //

/// Represent the reading of a property value.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PropValue<'name, T> {
    /// The name of the property that was read.
    pub name: Cow<'name, str>,
    /// The value of the property.
    pub value: T,
}

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

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

}

/// A handle over a request to get many object/class/device properties.
pub struct GetManyProp {
    values: Box<[Cow<'static, str>]>,
    indices: Box<[(usize, usize)]>,
}

impl GetManyProp {

    /// Return the number of remaining properties.
    pub fn len(&self) -> usize {
        self.indices.len()
    }

    pub fn get_raw(&self, index: usize) -> Option<PropValue<'_, &'_ [Cow<'_, str>]>> {
        let (begin, end) = *self.indices.get(index)?;
        let values = &self.values[begin..end];
        let name = &*self.values[begin - 2];
        Some(PropValue {
            name: Cow::Borrowed(name),
            value: values,
        })
    }

    pub fn get<'d, T>(&'d self, index: usize) -> Result<PropValue<'d, T>>
    where
        T: FromProp<'d>,
    {

        let (begin, end) = *self.indices.get(index)
            .ok_or_else(|| Error::new(ErrorKind::InvalidIndex))?;

        let values = &self.values[begin..end];
        let name = &*self.values[begin - 2];

        T::from_prop(values)
            .map(|value| PropValue { name: Cow::Borrowed(name), value })
            .map_err(|err| Error::new(ErrorKind::InvalidData)
                .with_append_description("Failed to parse property")
                .with_append_description(err))

    }

}

impl Debug for GetManyProp {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "GetManyProp(")?;
        f.debug_map().entries(self.indices.iter().map(|&(begin, end)| (&self.values[begin - 2], &self.values[begin..end]))).finish()?;
        write!(f, ")")
    }
}

/// Internal module for internal details of [`GetPropSpec`].
pub(crate) mod get_prop_spec {
    
    use crate::dev::AsNames;
    use super::{Result, GetManyProp};

    pub struct Token;
    pub trait Internal<Names: AsNames + ?Sized> {
        /// Kind of output type returned.
        type Output;
        /// Create the output from the given array.
        fn output(array: GetManyProp, _: Token) -> Result<Self::Output>;
    }

}

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

impl<T> get_prop_spec::Internal<&'_ str> for T
where
    T: for<'d> FromProp<'d>,
{

    type Output = PropValue<'static, T>;

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

}

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

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

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

}

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

    type Output = [Result<PropValue<'static, T>>; N];
    
    #[inline]
    fn output(array: GetManyProp, _: get_prop_spec::Token) -> Result<Self::Output> {
        Ok(std::array::from_fn::<_, N, _>(|i| {
            array.get(i).map(PropValue::into_owned)
        }))
    }

}

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

        #[allow(non_snake_case, unused_assignments)]
        impl< $($comp,)* > get_prop_spec::Internal<( $( impl_get_prop_spec_tuple!(@name: $comp), )* )> for ( $($comp,)* )
        where
            $(
            $comp: for<'d> FromProp<'d>,
            )*
        {
            
            type Output = ( $( Result<PropValue<'static, $comp>>,)* );

            fn output(array: GetManyProp, _: get_prop_spec::Token) -> Result<Self::Output> {
                let mut i = 0;
                $(
                    let $comp = array.get(i).map(PropValue::into_owned);
                    i += 1;
                )*
                Ok( ( $($comp,)* ) )
            }

        }

    };
}

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

// =========================== //
//       Set properties        //
// =========================== //

pub struct SetPropBuilder<'dev, 'data> {
    dev: &'dev DevProxy,
    cmd: &'static str,
    array: Vec<Cow<'data, str>>,
    len: usize,
}

impl<'dev, 'data> SetPropBuilder<'dev, 'data> {

    pub(crate) fn new(dev: &'dev DevProxy, cmd: &'static str, obj_name: Cow<'data, str>) -> Self {
        Self {
            dev,
            cmd,
            array: vec![obj_name, Cow::Owned(String::new())],
            len: 0,
        }
    }

    /// Get the number of property values appended to the builder so far.
    #[inline]
    pub fn len(&self) -> usize {
        self.len
    }

    /// Push a new property value in the builder to be set.
    pub fn push<T>(&mut self, prop: PropValue<'data, T>)
    where
        T: IntoProp<'data>,
    {
        self.array.push(prop.name);
        self.array.push(Cow::Owned(String::new()));  // Will be written just after...
        let len = self.array.len();
        prop.value.into_prop(&mut self.array);
        let count = self.array.len() - len;
        self.array[len - 1] = Cow::Owned(format!("{count}"));  // Actually write count!
    }

    /// Actually set the properties on the database.
    pub async fn set(mut self) -> Result<()> {
        
        // If the name is empty, which is usually invalid, it means that we should replace
        // by the name of the device.
        if self.array[0].is_empty() {
            let dev_name = self.dev.name().await?;
            self.array[0] = Cow::Owned(dev_name);
        }

        // Write the number of properties ahead of the array.
        self.array[1] = Cow::Owned(format!("{}", self.len));
        
        self.dev.command::<_, ()>(self.cmd, self.array).await?;
        
        Ok(())

    }

}

/// Internal sealed module that defines the internals of [`AttrWriteSpec`].
mod prop_set_spec {

    use super::SetPropBuilder;

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

    /// 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 SetPropSpec<'data>: prop_set_spec::Internal<'data> { }
impl<'data, T: prop_set_spec::Internal<'data>> SetPropSpec<'data> for T { }

/// For a single property.
impl<'data, 'data_: 'data, T> prop_set_spec::Internal<'data> for PropValue<'data_, T>
where
    T: IntoProp<'data>,
{

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

}

impl<'data, 'data_: 'data, T> prop_set_spec::Internal<'data> for (&'data_ str, T)
where
    T: IntoProp<'data>,
{

    #[inline]
    fn push_to_builder(self, builder: &mut SetPropBuilder<'_, 'data>) {
        builder.push(PropValue { 
            name: Cow::Borrowed(self.0), 
            value: self.1,
        });
    }

}

impl<'data, T, I> prop_set_spec::Internal<'data> for I
where
    T: SetPropSpec<'data>,
    I: IntoIterator<Item = T>,
    I: prop_set_spec::IntoIteratorFilter,
{

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

}

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

        #[allow(non_snake_case, unused_assignments)]
        impl<'data, $($comp,)*> prop_set_spec::Internal<'data> for ( $( $comp, )* )
        where
            $(
                $comp: SetPropSpec<'data>,
            )*
        {

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

        }

    };
}

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

// =========================== //
//      Property history       //
// =========================== //

// /// An previous value of a property.
// #[derive(Debug)]
// pub struct PropHistory<T> {
//     /// The time for this property value.
//     pub time: SystemTime,
//     /// The value, if none this means that the property has been deleted.
//     pub value: Option<T>,
// }

// #[derive(Debug)]
// pub struct PropHistory<'name, T> {
//     /// The name of the property.
//     pub name: Cow<'name, str>,
//     pub attr: Option<Cow<'name, str>>,
//     pub values: Box<[PropHistoryValue<T>]>,
// }
