//! Generic backend interfaces for devices.

use std::sync::{Arc, RwLock};
use std::fmt::Debug;

use async_trait::async_trait;

use crate::dev::{AttrInfo, AttrRead, AttrValueEvent, AttrWrite, CommandInfo, DevProxy, DevState};
use crate::error::Result;
use crate::data::Data;


// =========================== //
//       Device backend        //
// =========================== //

/// Backend for connecting to devices.
#[async_trait]
pub trait Backend: Debug + Send + Sync {

    /// Use this backend to connect to a device given its ITR (Interoperable Tango 
    /// Reference). If the backend does not support this type of ITR, then it returns
    /// [`DevError::DevNotHandled`].
    async fn connect_device(&self, itr: &str) -> Result<Arc<dyn DevBackend>>;

    /// Use this backend to connect to a device given its name, and the host/port of the
    /// service running the device server.
    async fn connect_device_at(&self, name: &str, host: &str, port: u16) -> Result<Arc<dyn DevBackend>>;

    /// Use this backend to connect to a database device. If this backend could not be 
    /// used for connection to that database, then it returns [`DevError::DevNotHandled`].
    async fn connect_database(&self, host: &str, port: u16) -> Result<Arc<dyn DevBackend>>;

    /// Bind a device to make it accessible from elsewhere via this backend.
    async fn bind_device(&self, dev: Arc<dyn DevBackend>) -> Result<()>;

}

/// The goal of a backend is to abstract away the actual technology and protocol used to
/// connect to a specific device. A running backend means that we are connected to a 
/// living device.
#[async_trait]
pub trait DevBackend: Debug + Send + Sync {

    /// Get the full domain/family/member name of this device. The string returned might
    /// be shared, because it can be more efficient to cache it because it should not be
    /// modified!
    async fn name(&self) -> Result<Arc<str>>;

    /// Get the full domain/family/member name of this device's admin device.
    /// Read [`Self::name`] for more info.
    async fn admin_dev_name(&self) -> Result<Arc<str>>;

    /// Get the description of the device, by itself.
    /// Read [`Self::name`] for more info.
    async fn description(&self) -> Result<Arc<str>>;

    /// Just ping the device.
    async fn ping(&self) -> Result<()>;

    /// Get the device's state, this should be equivalent to running `read_attr(Some(&["state"])`.
    async fn state(&self) -> Result<DevState>;
    
    /// Get the device's state, this should be equivalent to running `read_attr(Some(&["status"])`.
    async fn status(&self) -> Result<String>;

    /// Request the black box of this device, listing the actions that have been run on it.
    async fn black_box(&self, max: Option<usize>) -> Result<Vec<String>>;

    /// Execute a command on the device, from its name, and returning a result.
    async fn command(&self, name: &str, data: Data<'_>) -> Result<Box<dyn DevCommandBackend>>;

    /// Read an attribute or multiple attributes. Pass None to read all attributes.
    async fn read_attr(&self, names: Option<&[&str]>) -> Result<Box<dyn DevReadAttrBackend>>;

    /// Write the given list of attributes.
    async fn write_attr(&self, attrs: &[AttrWrite<'_, Data<'_>>]) -> Result<Box<dyn DevWriteAttrBackend>>;

    /// Get the command info of the given commands.
    async fn get_command_info(&self, names: Option<&[&str]>) -> Result<Box<dyn DevGetCommandInfoBackend>>;

    /// Get the attribute info (including attr config) of the given attributes.
    async fn get_attr_info(&self, names: Option<&[&str]>) -> Result<Box<dyn DevGetAttrInfoBackend>>;

}

/// Represent an owned command output, from which a borrowing [`Data`] can be produced.
pub trait DevCommandBackend: Debug + Send {
    fn get(&self) -> Result<Data<'_>>;
}

/// A backend trait to return an abstract array of [`AttrRead`] with dyn-boxed [`Data`].
pub trait DevReadAttrBackend: Debug + Send {
    fn len(&self) -> usize;
    fn get(&self, index: usize) -> Result<AttrRead<'_, Data<'_>>>;
}

/// A backend trait to return the results for each write of data.
pub trait DevWriteAttrBackend: Debug + Send {
    fn len(&self) -> usize;
    fn check(&self, index: usize) -> Result<()>;
}

/// A backend trait to return an array of command infos.
pub trait DevGetCommandInfoBackend: Debug + Send {
    fn len(&self) -> usize;
    fn get(&self, index: usize) -> Result<CommandInfo<'_>>;
}

/// A backend trait to return an array of attribute infos.
pub trait DevGetAttrInfoBackend: Debug + Send {
    fn len(&self) -> usize;
    fn get(&self, index: usize) -> Result<AttrInfo<'_>>;
}

/// Internal registry of backends. We use an Arc in order to just clone that Arc when we
/// want to read backends, thus we have a reference to the backends that can be sent 
/// between threads, and thus kept across await point.
static BACKENDS: RwLock<Option<Arc<[Arc<dyn Backend>]>>> = RwLock::new(None);

/// Register a new backend for devices.
pub fn register_backend(backend: Arc<dyn Backend>) {
    let mut guard = BACKENDS.write().unwrap();
    let backends = guard.as_deref().unwrap_or(&[]);
    let backends = backends.iter()
        .cloned()
        .chain(Some(backend))
        .collect::<Vec<_>>();
    *guard = Some(Arc::from(backends))
}

pub fn read_backends() -> Option<Arc<[Arc<dyn Backend>]>> {
    BACKENDS.read().unwrap().clone()
}

// =========================== //
//    Device event backend     //
// =========================== //

/// An event backend is responsible for configuring event subscription.
#[async_trait]
pub trait EventBackend: Debug + Send + Sync {

    /// Subscribe to an event of some type on the device proxy, targeting a given object.
    async fn subscribe_attr_value(&self, dev: &DevProxy, name: &str, event: AttrValueEvent) -> Result<Box<dyn AttrValueChannelBackend>>;
    
    /// Subscribe to an event of some type on the device proxy, targeting a given object.
    async fn subscribe_attr_ready(&self, dev: &DevProxy, name: &str) -> Result<Box<dyn AttrReadyChannelBackend>>;
    
    /// Subscribe to an event of some type on the device proxy, targeting a given object.
    async fn subscribe_attr_conf(&self, dev: &DevProxy, name: &str) -> Result<Box<dyn AttrConfChannelBackend>>;

}

/// An abstract channel for attribute value events.
#[async_trait]
pub trait AttrValueChannelBackend: Debug + Send {
    async fn wait(&mut self) -> Result<Box<dyn AttrValueChannelGuard + '_>>;
}

/// A backend trait that contains an abstract [`AttrRead`], returned by [`AttrChannelBackend`].
pub trait AttrValueChannelGuard: Debug {
    fn get(&self) -> Result<AttrRead<'_, Data<'_>>>;
}

/// An abstract channel for attribute ready events.
#[async_trait]
pub trait AttrReadyChannelBackend: Debug + Send {
    async fn wait(&mut self) -> Result<()>;
}

/// An abstract channel for attribute configuration events.
#[async_trait]
pub trait AttrConfChannelBackend: Debug + Send {
    async fn wait(&mut self) -> Result<()>;
}

/// Same as for backends, but for event backends.
static EVENT_BACKENDS: RwLock<Option<Arc<[Arc<dyn EventBackend>]>>> = RwLock::new(None);

/// Register a new backend for devices.
pub fn register_event_backend(backend: Arc<dyn EventBackend>) {
    let mut guard = EVENT_BACKENDS.write().unwrap();
    let backends = guard.as_deref().unwrap_or(&[]);
    let backends = backends.iter()
        .cloned()
        .chain(Some(backend))
        .collect::<Vec<_>>();
    *guard = Some(Arc::from(backends))
}

pub fn read_event_backends() -> Option<Arc<[Arc<dyn EventBackend>]>> {
    EVENT_BACKENDS.read().unwrap().clone()
}
