//! Implementation of CORBA+ZMQ device backend.

mod runtime_queue;
mod runtime;
mod convert;
mod command;
pub(crate) mod attr;  // For now exported because it is used for zeromq!

use std::collections::HashSet;
use std::ffi::{CStr, CString};
use std::hash::{Hash, Hasher};
use std::fmt::{self, Debug};
use std::sync::{Arc, Weak};
use std::time::Duration;
use std::borrow::Borrow;

use tokio::sync::{Mutex, OnceCell};
use tokio::runtime as rt;

use async_trait::async_trait;
use tracing::trace;

use tango_omniorb as orb;

use crate::backend::{self, Backend, DevBackend, DevCommandBackend, DevGetAttrInfoBackend, DevGetCommandInfoBackend, DevReadAttrBackend, DevWriteAttrBackend};
use crate::omniorb::command::DevGetCommandInfoBackendExt;
use crate::dev::{AttrWrite, DevState};
use crate::error::{Error, ErrorKind, Result};
use crate::data::Data;

use attr::{DevReadAttrFromValueList, DevWriteAttrFromMultiDevFailed, DevWriteAttrOk, DevGetAttrConfigFromList,
    attr_write_to_value_v1, attr_write_to_value_v4};
use command::{DevCommandFromAny, DevGetCommandInfoMany, DevGetCommandInfoSingle,
    command_data_to_any};
use runtime::AsyncOrb;


const ALL_ATTRIBUTES: &'static str = "All attributes";
const ALL_ATTRIBUTES_3: &'static str = "All attributes_3";


/// The CORBA backend, providing fully asynchronous interaction with devices using the 
/// historical backend of cppTango and pyTango.
pub struct CorbaBackend {
    /// Weak reference back to itself.
    this: Weak<Self>,
    /// The backend ORB.
    orb: AsyncOrb,
    /// The servant for this system.
    servants: Mutex<HashSet<CorbaDevServant>>,
}

impl CorbaBackend {

    /// Initialize a new CORBA backend, to be registered later.
    pub fn init() -> Result<Arc<Self>> {

        let orb = AsyncOrb::init()?;

        Ok(Arc::new_cyclic(|this| {
            Self {
                this: this.clone(),
                orb,
                servants: Mutex::new(HashSet::new())
            }
        }))

    }

    /// Register this backend for handling real devices.
    #[inline]
    pub fn register(self: Arc<Self>) {
        backend::register_backend(self);
    }

    #[allow(unused)]
    #[inline]
    fn this(&self) -> Arc<Self> {
        // If this function is called, this means that the top-level caller have a 
        // Arc<Self> because we only ever create backends inside Arc<...>, so we can
        // safely unwrap the upgrade.
        self.this.upgrade().unwrap()
    }

    async fn connect_device_inner(&self, corba_name: String) -> Result<Arc<dyn DevBackend>> {

        // TODO: Cache the connected device by their corba name!

        trace!(?corba_name, "Connecting to device");

        // From cppTango source code, we get an good explanation of what's happening here:
        //
        //  The _narrow() call does not necessary generates a remote call. It all depends 
        //  on the object IDL type stored in the IOR. If in the IOR, the IDL is the same 
        //  than the one on which the narrow is done (Device_5 on both side for instance), 
        //  then the _narrow call will not generate any remote call and therefore, we
        //  don't know if the connection is OK or NOT. This is the reason of the 
        //  _non_existent() call. In case the IDl in the IOR and in the narrow() call are 
        //  different, then the _narrow() call try to execute a remote _is_a() call and 
        //  therefore tries to connect to the device. IN this case, the _non_existent() 
        //  call is useless. But because we don want to analyse the IOR ourself, we always
        //  call _non_existent() Reset the connection timeout only after the _non_existent 
        //  call.
        
        // The global timeouts are used only for the string_to_object method and the 
        // resolution below, once we have an object, we'll set its timeout to a great
        // number so we never really hit the timeout.
        orb::set_client_connect_timeout(Duration::from_millis(3000));
        orb::set_client_call_timeout(Duration::from_millis(3000));

        let res = self.orb.dispatch(move |orb| {
            let obj = orb.string_to_object(&corba_name)?;
            if let Some(v6) = orb::Tango_Device_6::narrow(&obj)? {
                v6.non_existent()?;
                Ok(Some(OrbDev::from_v6(v6)))
            } else if let Some(v5) = orb::Tango_Device_5::narrow(&obj)? {
                v5.non_existent()?;
                Ok(Some(OrbDev::from_v5(v5)))
            } else if let Some(v4) = orb::Tango_Device_4::narrow(&obj)? {
                v4.non_existent()?;
                Ok(Some(OrbDev::from_v4(v4)))
            } else if let Some(v3) = orb::Tango_Device_3::narrow(&obj)? {
                v3.non_existent()?;
                Ok(Some(OrbDev::from_v3(v3)))
            } else if let Some(v2) = orb::Tango_Device_2::narrow(&obj)? {
                v2.non_existent()?;
                Ok(Some(OrbDev::from_v2(v2)))
            } else if let Some(v1) = orb::Tango_Device::narrow(&obj)? {
                v1.non_existent()?;
                Ok(Some(OrbDev::from_v1(v1)))
            } else {
                Ok(None)
            }
        }).await;

        // Note that on connection to a device, we transform a timeout to a DevServer 
        // not connected, because only the omniORB instance of the device server can be
        // at fault for that!
        let obj = match res {
            Ok(Some(obj)) => obj,
            Ok(None) => return Err(Error::new(ErrorKind::DevNotConnected)),
            Err(e) if e.kind() == ErrorKind::TimedOut => return Err(Error::new(ErrorKind::ServerNotConnected)),
            Err(e) => return Err(e),
        };

        // 1 year from now, we just use this to disable timeouts on calls to devices.
        obj.v1().set_client_call_timeout(Duration::from_hours(24 * 365));

        Ok(CorbaDevBackend::new(self.orb.clone(), obj))

    }

}

#[async_trait]
impl Backend for CorbaBackend {

    async fn connect_device(&self, itr: &str) -> Result<Arc<dyn DevBackend>> {
        
        if !itr.starts_with("corbaloc:") && !itr.starts_with("corbaname:") && !itr.starts_with("IOR:") {
            return Err(Error::new(ErrorKind::NotHandled));
        }

        let itr = itr.to_string();
        self.connect_device_inner(itr).await

    }
    
    async fn connect_device_at(&self, name: &str, host: &str, port: u16) -> Result<Arc<dyn DevBackend>> {
        let itr = format!("corbaloc:iiop:{host}:{port}/{name}");
        self.connect_device_inner(itr).await
    }

    async fn connect_database(&self, host: &str, port: u16) -> Result<Arc<dyn DevBackend>> {
        self.connect_device_at("database", host, port).await
    }

    async fn bind_device(&self, backend: Arc<dyn DevBackend>) -> Result<()> {

        // We get the name of the device.
        let name = backend.name().await
            .map_err(|e| Error::new(ErrorKind::Internal)
                .with_description(format!("Could not get the name of the device to bind"))
                .with_parent(e))?;
        
        let adapter = CorbaDevAdapter {
            runtime: rt::Handle::current(),
            backend: Arc::clone(&backend),
        };

        let servant = adapter.instantiate();
        let servant = self.orb.dispatch(move |orb| {

            let poa = orb.resolve_initial_references("RootPOA")?;
            let poa = orb::PortableServer_POA::narrow(&poa)?
                .ok_or_else(|| Error::new(ErrorKind::Internal)
                    .with_description("Cannot narrow RootPOA to PortableServer::POA type"))?;
            
            let object_id = poa.activate_object(&servant)?;

            Ok(CorbaDevServant {
                name,
                backend,
                servant,
                object_id,
            })

        }).await?;

        let mut servants = self.servants.lock().await;
        servants.insert(servant);
        Ok(())

    }
    
}

impl Debug for CorbaBackend {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("CorbaBackend")
            .finish()
    }
}

/// The internal backend implementation for devices.
struct CorbaDevBackend {
    /// Weak reference back to itself.
    this: Weak<Self>,
    /// The fixed identifier for the client, we have to take a fake Java identifier.
    tango_ident: orb::Tango_ClntIdent,
    /// The backend ORB.
    orb: AsyncOrb,
    /// The internal ORB devices by version.
    obj: OrbDev,
    name: OnceCell<Arc<str>>,
    admin_name: OnceCell<Arc<str>>,
}

impl CorbaDevBackend {

    fn new(orb: AsyncOrb, obj: OrbDev) -> Arc<Self> {
        Arc::new_cyclic(|this| {
            Self {
                this: this.clone(),
                tango_ident: orb::Tango_ClntIdent::with_java_clnt(orb::CppBox::new(orb::Tango_JavaClntIdent {
                    MainClass: "RustTango".try_into().unwrap(),
                    uuid: [0; 2],
                })),
                orb,
                obj,
                name: OnceCell::new(),
                admin_name: OnceCell::new(),
            }
        })
    }

    #[inline]
    fn this(&self) -> Arc<Self> {
        // If this function is called, this means that the top-level caller have a 
        // Arc<Self> because we only ever create backends inside Arc<...>, so we can
        // safely unwrap the upgrade.
        self.this.upgrade().unwrap()
    }

}

impl Debug for CorbaDevBackend {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("CorbaDevBackend")
            .finish()
    }
}

#[async_trait]
impl DevBackend for CorbaDevBackend {

    async fn name(&self) -> Result<Arc<str>> {
        self.name.get_or_try_init(async || {
            let this = self.this();
            self.orb.dispatch(move |_| this.obj.v1()
                .name()
                .map_err(Error::from)
                .map(|s| Arc::from(s.to_string_lossy())))
                .await
        }).await.map(Arc::clone)
    }
    
    async fn admin_dev_name(&self) -> Result<Arc<str>> {
        self.admin_name.get_or_try_init(async || {
            let this = self.this();
            self.orb.dispatch(move |_| this.obj.v1()
                .adm_name()
                .map_err(Error::from)
                .map(|s| Arc::from(s.to_string_lossy())))
                .await
        }).await.map(Arc::clone)
    }

    async fn description(&self) -> Result<Arc<str>> {
        let this = self.this();
        self.orb.dispatch(move |_| this.obj.v1()
            .description()
            .map_err(Error::from)
            .map(|s| Arc::from(s.to_string_lossy())))
            .await
    }

    async fn ping(&self) -> Result<()> {
        let this = self.this();
        self.orb.dispatch(move |_| this.obj.v1()
            .ping()
            .map_err(Error::from))
            .await
    }
    
    async fn state(&self) -> Result<DevState> {
        let this = self.this();
        self.orb.dispatch(move |_| this.obj.v1()
            .state()
            .map(orb::Tango_DevState::into)
            .map_err(Error::from))
            .await
    }
    
    async fn status(&self) -> Result<String> {
        let this = self.this();
        self.orb.dispatch(move |_| this.obj.v1()
            .status()
            .map(|s| s.to_string_lossy().into_owned())
            .map_err(Error::from))
            .await
    }

    async fn black_box(&self, max: Option<usize>) -> Result<Vec<String>> {
        
        let this = self.this();
        self.orb.dispatch(move |_| {

            let res = this.obj.v1().black_box(max.and_then(|max| i32::try_from(max).ok()).unwrap_or(orb::CORBA_Long::MAX))?;
            let ret = res.into_inner()
                .into_iter()
                .map(|s| s.as_cstr().to_string_lossy().into_owned())
                .collect::<Vec<_>>();

            Ok(ret)

        }).await

    }

    async fn command(&self, name: &str, data: Data<'_>) -> Result<Box<dyn DevCommandBackend>> {
        
        let Ok(name) = CString::new(name) else {
            return Err(Error::new(ErrorKind::CommandNameInvalid)
                .with_append_description(format_args!("{name:?}")));
        };

        let any_in = match command_data_to_any(data) {
            Ok(any) => any,
            Err(e) => return Err(e),
        };

        let this = self.this();
        self.orb.dispatch(move |_| {

            let any_out = match &this.obj {
                OrbDev::V1 { v1, .. } => 
                    v1.command_inout(&name, &any_in),
                OrbDev::V2 { v2, .. } |
                OrbDev::V3 { v2, .. } => 
                    v2.command_inout_2(&name, &any_in, orb::Tango_DevSource::CACHE_DEV),
                OrbDev::V4 { v4, .. } |
                OrbDev::V5 { v4, .. } |
                OrbDev::V6 { v4, .. } => 
                    v4.command_inout_4(&name, &any_in, orb::Tango_DevSource::CACHE_DEV, &this.tango_ident),
            }?.into_inner();

            let ret: Box<dyn DevCommandBackend> = Box::new(DevCommandFromAny::new(any_out));
            Ok(ret)

        }).await

    }
    
    async fn read_attr(&self, names: Option<&[&str]>) -> Result<Box<dyn DevReadAttrBackend>> {
        
        const ALL: &[&str] = &[ALL_ATTRIBUTES];

        let names = match names {
            Some(names) => {
                if names.contains(&ALL_ATTRIBUTES) || names.contains(&ALL_ATTRIBUTES_3) {
                    return Err(Error::new(ErrorKind::AttrNameInvalid)
                        .with_append_description("Given attribute names contains the 'all attributes' marker")
                        .with_append_description(format_args!("{names:?}")));
                }
                names
            }
            None => ALL,
        };

        let mut names_arr = orb::Tango_DevVarStringArray(orb::CORBA_Sequence::with_capacity(names.len()));
        for &name in names {
            let Ok(name) = orb::CORBA_String::try_from(name) else {
                return Err(Error::new(ErrorKind::AttrNameInvalid)
                        .with_append_description(format_args!("{name:?}")));
            };
            names_arr.push(name);
        }

        let this = self.this();
        self.orb.dispatch(move |_| {
            let ret: Box<dyn DevReadAttrBackend> = match &this.obj {
                OrbDev::V1 { v1, .. } |
                OrbDev::V2 { v1, .. } => 
                    Box::new(DevReadAttrFromValueList::new(v1.read_attributes(&names_arr)?)),
                OrbDev::V3 { v3, .. } => 
                    Box::new(DevReadAttrFromValueList::new(v3.read_attributes_3(&names_arr, orb::Tango_DevSource::CACHE_DEV)?)),
                OrbDev::V4 { v4, .. } => 
                    Box::new(DevReadAttrFromValueList::new(v4.read_attributes_4(&names_arr, orb::Tango_DevSource::CACHE_DEV, &this.tango_ident)?)),
                OrbDev::V5 { v5, .. } |
                OrbDev::V6 { v5, .. } => 
                    Box::new(DevReadAttrFromValueList::new(v5.read_attributes_5(&names_arr, orb::Tango_DevSource::CACHE_DEV, &this.tango_ident)?))
            };
            Ok(ret)
        }).await

    }
    
    async fn write_attr(&self, attrs: &[AttrWrite<'_, Data<'_>>]) -> Result<Box<dyn DevWriteAttrBackend>> {

        #[derive(Debug)]
        enum AttributeValueList {
            V1(orb::Tango_AttributeValueList),
            V4(orb::Tango_AttributeValueList_4),
        }

        impl AttributeValueList {
            
            fn v1(&self) -> &orb::Tango_AttributeValueList {
                match self {
                    Self::V1(list) => list,
                    _ => panic!()
                }
            }

            fn v4(&self) -> &orb::Tango_AttributeValueList_4 {
                match self {
                    Self::V4(list) => list,
                    _ => panic!()
                }
            }

        }

        let len = attrs.len();
        let value_list = if self.obj.v4().is_some() {
            let mut val_list = orb::Tango_AttributeValueList_4(orb::CORBA_Sequence::with_capacity(attrs.len()));
            for attr in attrs {
                match attr_write_to_value_v4(attr) {
                    Ok(val) => val_list.push(val),
                    Err(e) => return Err(e),
                }
            }
            AttributeValueList::V4(val_list)
        } else {
            let mut val_list = orb::Tango_AttributeValueList(orb::CORBA_Sequence::with_capacity(attrs.len()));
            for attr in attrs {
                match attr_write_to_value_v1(attr) {
                    Ok(val) => val_list.push(val),
                    Err(e) => return Err(e),
                }
            }
            AttributeValueList::V1(val_list)
        };

        let this = self.this();
        self.orb.dispatch(move |_| {
            
            let res = match &this.obj {
                OrbDev::V1 { v1, .. } |
                OrbDev::V2 { v1, .. } => 
                    v1.write_attributes(value_list.v1()),
                OrbDev::V3 { v3, .. } => 
                    v3.write_attributes_3(value_list.v1()),
                OrbDev::V4 { v4, .. } |
                OrbDev::V5 { v4, .. } |
                OrbDev::V6 { v4, .. } => 
                    v4.write_attributes_4(value_list.v4(), &this.tango_ident),
            };

            let err = match res {
                Ok(()) => return Ok(Box::new(DevWriteAttrOk { len }) as Box<dyn DevWriteAttrBackend>),
                Err(e) => e,
            };

            let err = match orb::Tango_MultiDevFailed::downcast(err) {
                Ok(err) => return Ok(Box::new(DevWriteAttrFromMultiDevFailed { len, err }) as Box<dyn DevWriteAttrBackend>),
                Err(err) => err,
            };

            Err(err.into())

        }).await

    }

    async fn get_command_info(&self, names: Option<&[&str]>) -> Result<Box<dyn DevGetCommandInfoBackend>> {
        
        // The Tango IDL is not supporting multiple query for commands, therefore if we
        // have only one name, we use 'command_query', but if we have more names, we use
        // 'command_list_query' and then we filter it!
        if let Some(&[name]) = names {
            
            let Ok(name) = orb::CORBA_String::try_from(name) else {
                return Err(Error::new(ErrorKind::CommandNameInvalid)
                    .with_append_description(format_args!("{name:?}")));
            };

            let this = self.this();
            self.orb.dispatch(move |_| {
                let ret: Box<dyn DevGetCommandInfoBackend> = match &this.obj {
                    OrbDev::V1 { v1, .. } =>
                        Box::new(DevGetCommandInfoSingle::new(v1.command_query(&name)?)),
                    OrbDev::V2 { v2, .. } |
                    OrbDev::V3 { v2, .. } |
                    OrbDev::V4 { v2, .. } |
                    OrbDev::V5 { v2, .. } |
                    OrbDev::V6 { v2, .. } =>
                        Box::new(DevGetCommandInfoSingle::new(v2.command_query_2(&name)?)),
                };
                Ok(ret)
            }).await
            
        } else {

            let this = self.this();
            let mut ret = self.orb.dispatch(move |_| {
                let ret: Box<dyn DevGetCommandInfoBackendExt> = match &this.obj {
                    OrbDev::V1 { v1, .. } =>
                        Box::new(DevGetCommandInfoMany::new(v1.command_list_query()?)),
                    OrbDev::V2 { v2, .. } |
                    OrbDev::V3 { v2, .. } |
                    OrbDev::V4 { v2, .. } |
                    OrbDev::V5 { v2, .. } |
                    OrbDev::V6 { v2, .. } => 
                        Box::new(DevGetCommandInfoMany::new(v2.command_list_query_2()?)),
                };
                Ok(ret)
            }).await?;
            
            if let Some(names) = names {
                ret.init_mapping(names);
            }
            
            Ok(ret)
            
        }

    }

    async fn get_attr_info(&self, names: Option<&[&str]>) -> Result<Box<dyn DevGetAttrInfoBackend>> {
        
        const ALL: &[&str] = &[ALL_ATTRIBUTES];
        const ALL_3: &[&str] = &[ALL_ATTRIBUTES_3];

        let names = match names {
            Some(names) => {
                if names.contains(&ALL_ATTRIBUTES) || names.contains(&ALL_ATTRIBUTES_3) {
                    return Err(Error::new(ErrorKind::AttrNameInvalid)
                        .with_append_description("Given attribute names contains the 'all attributes' marker")
                        .with_append_description(format_args!("{names:?}")));
                }
                names
            }
            None => {
                if self.obj.v3().is_some() {
                    ALL_3
                } else {
                    ALL
                }
            }
        };

        let mut names_arr = orb::Tango_DevVarStringArray(orb::CORBA_Sequence::with_capacity(names.len()));
        for &name in names {
            let Ok(name) = orb::CORBA_String::try_from(name) else {
                return Err(Error::new(ErrorKind::AttrNameInvalid)
                        .with_append_description(format_args!("{name:?}")));
            };
            names_arr.push(name);
        }

        let this = self.this();
        self.orb.dispatch(move |_| {
            let ret: Box<dyn DevGetAttrInfoBackend> = match &this.obj {
                OrbDev::V1 { v1, .. } => 
                    Box::new(DevGetAttrConfigFromList::new(v1.get_attribute_config(&names_arr)?)),
                OrbDev::V2 { v2, .. } => 
                    Box::new(DevGetAttrConfigFromList::new(v2.get_attribute_config_2(&names_arr)?)),
                OrbDev::V3 { v3, .. } |
                OrbDev::V4 { v3, .. } =>
                    Box::new(DevGetAttrConfigFromList::new(v3.get_attribute_config_3(&names_arr)?)),
                OrbDev::V5 { v5, .. } |
                OrbDev::V6 { v5, .. } => 
                    Box::new(DevGetAttrConfigFromList::new(v5.get_attribute_config_5(&names_arr)?)),
            };
            Ok(ret)
        }).await

    }

}


/// Internal enum that contains the actual CORBA proxies for a device. Basically, this is
/// better that having multiple [`Option`] for each device, because this reflects that 
/// if we have some version, we have all versions down to 1. Most match statements will
/// be optimized anyway.
#[allow(unused)]
enum OrbDev {
    V1 {
        v1: orb::Tango_Device,
    },
    V2 {
        v1: orb::Tango_Device,
        v2: orb::Tango_Device_2,
    },
    V3 {
        v1: orb::Tango_Device,
        v2: orb::Tango_Device_2,
        v3: orb::Tango_Device_3,
    },
    V4 {
        v1: orb::Tango_Device,
        v2: orb::Tango_Device_2,
        v3: orb::Tango_Device_3,
        v4: orb::Tango_Device_4,
    },
    V5 {
        v1: orb::Tango_Device,
        v2: orb::Tango_Device_2,
        v3: orb::Tango_Device_3,
        v4: orb::Tango_Device_4,
        v5: orb::Tango_Device_5,
    },
    V6 {
        v1: orb::Tango_Device,
        v2: orb::Tango_Device_2,
        v3: orb::Tango_Device_3,
        v4: orb::Tango_Device_4,
        v5: orb::Tango_Device_5,
        v6: orb::Tango_Device_6,
    },
}

#[allow(unused)]
impl OrbDev {

    pub fn from_v1(v1: orb::Tango_Device) -> Self {
        Self::V1 { v1 }
    }

    pub fn from_v2(v2: orb::Tango_Device_2) -> Self {
        let v1 = v2.parent().duplicate();
        Self::V2 { v1, v2 }
    }

    pub fn from_v3(v3: orb::Tango_Device_3) -> Self {
        let v2 = v3.parent().duplicate();
        let v1 = v2.parent().duplicate();
        Self::V3 { v1, v2, v3 }
    }

    pub fn from_v4(v4: orb::Tango_Device_4) -> Self {
        let v3 = v4.parent().duplicate();
        let v2 = v3.parent().duplicate();
        let v1 = v2.parent().duplicate();
        Self::V4 { v1, v2, v3, v4 }
    }

    pub fn from_v5(v5: orb::Tango_Device_5) -> Self {
        let v4 = v5.parent().duplicate();
        let v3 = v4.parent().duplicate();
        let v2 = v3.parent().duplicate();
        let v1 = v2.parent().duplicate();
        Self::V5 { v1, v2, v3, v4, v5 }
    }

    pub fn from_v6(v6: orb::Tango_Device_6) -> Self {
        let v5 = v6.parent().duplicate();
        let v4 = v5.parent().duplicate();
        let v3 = v4.parent().duplicate();
        let v2 = v3.parent().duplicate();
        let v1 = v2.parent().duplicate();
        Self::V6 { v1, v2, v3, v4, v5, v6 }
    }

    #[inline]
    pub fn v1(&self) -> &orb::Tango_Device {
        match self {
            OrbDev::V1 { v1, .. } => v1,
            OrbDev::V2 { v1, .. } => v1,
            OrbDev::V3 { v1, .. } => v1,
            OrbDev::V4 { v1, .. } => v1,
            OrbDev::V5 { v1, .. } => v1,
            OrbDev::V6 { v1, .. } => v1,
        }
    }

    #[inline]
    pub fn v2(&self) -> Option<&orb::Tango_Device_2> {
        Some(match self {
            OrbDev::V1 { .. } => return None,
            OrbDev::V2 { v2, .. } => v2,
            OrbDev::V3 { v2, .. } => v2,
            OrbDev::V4 { v2, .. } => v2,
            OrbDev::V5 { v2, .. } => v2,
            OrbDev::V6 { v2, .. } => v2,
        })
    }

    #[inline]
    pub fn v3(&self) -> Option<&orb::Tango_Device_3> {
        Some(match self {
            OrbDev::V1 { .. } => return None,
            OrbDev::V2 { .. } => return None,
            OrbDev::V3 { v3, .. } => v3,
            OrbDev::V4 { v3, .. } => v3,
            OrbDev::V5 { v3, .. } => v3,
            OrbDev::V6 { v3, .. } => v3,
        })
    }

    #[inline]
    pub fn v4(&self) -> Option<&orb::Tango_Device_4> {
        Some(match self {
            OrbDev::V1 { .. } => return None,
            OrbDev::V2 { .. } => return None,
            OrbDev::V3 { .. } => return None,
            OrbDev::V4 { v4, .. } => v4,
            OrbDev::V5 { v4, .. } => v4,
            OrbDev::V6 { v4, .. } => v4,
        })
    }

    #[inline]
    pub fn v5(&self) -> Option<&orb::Tango_Device_5> {
        Some(match self {
            OrbDev::V1 { .. } => return None,
            OrbDev::V2 { .. } => return None,
            OrbDev::V3 { .. } => return None,
            OrbDev::V4 { .. } => return None,
            OrbDev::V5 { v5, .. } => v5,
            OrbDev::V6 { v5, .. } => v5,
        })
    }

    pub fn v6(&self) -> Option<&orb::Tango_Device_6> {
        Some(match self {
            OrbDev::V1 { .. } => return None,
            OrbDev::V2 { .. } => return None,
            OrbDev::V3 { .. } => return None,
            OrbDev::V4 { .. } => return None,
            OrbDev::V5 { .. } => return None,
            OrbDev::V6 { v6, .. } => v6,
        })
    }

}

impl fmt::Debug for OrbDev {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::V1 { v1, .. } => f.debug_tuple("V1").field(v1).finish(),
            Self::V2 { v2, .. } => f.debug_tuple("V2").field(v2).finish(),
            Self::V3 { v3, .. } => f.debug_tuple("V3").field(v3).finish(),
            Self::V4 { v4, .. } => f.debug_tuple("V4").field(v4).finish(),
            Self::V5 { v5, .. } => f.debug_tuple("V5").field(v5).finish(),
            Self::V6 { v6, .. } => f.debug_tuple("V6").field(v6).finish(),
        }
    }
}

/// Represent the runtime data of omniORB CORBA servant. This structure is used within
/// a set where is can be retrieved either from its name.
#[derive(Debug)]
struct CorbaDevServant {
    name: Arc<str>,
    backend: Arc<dyn DevBackend>,
    servant: orb::PortableServer_Servant,
    object_id: orb::PortableServer_ObjectId,
}

impl Borrow<str> for CorbaDevServant {
    fn borrow(&self) -> &str {
        &self.name
    }
}

impl Eq for CorbaDevServant {}
impl PartialEq for CorbaDevServant {
    fn eq(&self, other: &Self) -> bool {
        self.name == other.name
    }
}

impl Hash for CorbaDevServant {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.name.hash(state);
    }
}


/// This object is adapting a generic and backend-generic device backend, as a omniORB
/// adapter to be constructed as a servant.
struct CorbaDevAdapter {
    runtime: rt::Handle,
    backend: Arc<dyn DevBackend>,
}

impl CorbaDevAdapter {

    const DEF: orb::Tango_Device_AdapterDef<Self> = orb::Tango_Device_AdapterDef::<Self>::new();

    pub fn instantiate(self) -> orb::PortableServer_Servant {
        Self::DEF.instantiate(Box::new(self))
    }

}

impl orb::CORBA_Object_Adapter for CorbaDevAdapter {}

impl orb::Tango_Device_Adapter for CorbaDevAdapter {
    
    fn name(&self) -> std::result::Result<orb::CORBA_String, orb::CORBA_Exception> {
        // self.runtime.block_on(async move {
        //     let s = self.backend.name().await?;
        //     Ok(orb::CORBA_String::new(s.as_bytes()))
        // })
        todo!()
    }

    fn description(&self) -> std::result::Result<orb::CORBA_String, orb::CORBA_Exception> {
        // self.runtime.block_on(async move {
        //     let s = self.backend.description().await?;
        //     Ok(orb::CORBA_String::new(s.as_bytes()))
        // })
        todo!()
    }

    fn state(&self) -> std::result::Result<orb::Tango_DevState, orb::CORBA_Exception> {
        // self.runtime.block_on(async move {
        //     let state = self.backend.state().await?;
        //     Ok(orb::Tango_DevState::from(state))
        // })
        todo!()
    }

    fn status(&self) -> std::result::Result<orb::CORBA_String, orb::CORBA_Exception> {
        // self.runtime.block_on(async move {
        //     let s = self.backend.status().await?;
        //     Ok(orb::CORBA_String::new(s.as_bytes()))
        // })
        todo!()
    }

    fn adm_name(&self) -> std::result::Result<orb::CORBA_String, orb::CORBA_Exception> {
        // self.runtime.block_on(async move {
        //     let s = self.backend.admin_dev_name().await?;
        //     Ok(orb::CORBA_String::new(s.as_bytes()))
        // })
        todo!()
    }

    fn command_inout(&self, command: &CStr, argin: &orb::CORBA_Any) -> std::result::Result<orb::CppBox<orb::CORBA_Any>, orb::CORBA_Exception> {
        todo!()
    }

    fn get_attribute_config(&self, names: &orb::Tango_DevVarStringArray) -> std::result::Result<orb::CppBox<orb::Tango_AttributeConfigList>, orb::CORBA_Exception> {
        todo!()
    }

    fn set_attribute_config(&self, new_conf: &orb::Tango_AttributeConfigList) -> std::result::Result<(), orb::CORBA_Exception> {
        todo!()
    }

    fn read_attributes(&self, names: &orb::Tango_DevVarStringArray) -> std::result::Result<orb::CppBox<orb::Tango_AttributeValueList>, orb::CORBA_Exception> {
        todo!()
    }

    fn write_attributes(&self, values: &orb::Tango_AttributeValueList) -> std::result::Result<(), orb::CORBA_Exception> {
        todo!()
    }

    fn ping(&self) -> std::result::Result<(), orb::CORBA_Exception> {
        todo!()
    }

    fn black_box(&self, n: orb::CORBA_Long) -> std::result::Result<orb::CppBox<orb::Tango_DevVarStringArray>, orb::CORBA_Exception> {
        todo!()
    }

    fn info(&self) -> std::result::Result<orb::CppBox<orb::Tango_DevInfo>, orb::CORBA_Exception> {
        todo!()
    }

    fn command_list_query(&self) -> std::result::Result<orb::CppBox<orb::Tango_DevCmdInfoList>, orb::CORBA_Exception> {
        todo!()
    }

    fn command_query(&self, command: &CStr) -> std::result::Result<orb::CppBox<orb::Tango_DevCmdInfo>, orb::CORBA_Exception> {
        todo!()
    }

}
