//! Conversion of attribute value from and into Tango::AttributeValue.

use std::slice;

use tango_omniorb as orb;

use crate::backend::{DevGetAttrInfoBackend, DevReadAttrBackend, DevWriteAttrBackend};
use crate::data::{Data, DataKind, DataLayout, DataLayoutKind, DataLayoutView, DataView};
use crate::dev::{AttrArchiveEventConfig, AttrChangeEventConfig, AttrConfig, AttrDeltaConfig, AttrInfo, AttrMemorizedMode, AttrMode, AttrPeriodicEventConfig, AttrRangeConfig, AttrRead, AttrWrite, DisplayLevel};
use crate::error::{Error, ErrorKind, Result};
use crate::array::MdView;

use super::convert::{
    time_val_to_system_or_epoch, time_val_from_system_or_zero, data_kind_from_type,
    opt_str_if, opt_f32_empty, opt_duration_if,
    S_NO_DESCRIPTION, S_NOT_SPECIFIED, S_NO_DISPLAY_UNIT};


pub fn attr_write_to_value_v1(attr_data: &AttrWrite<'_, Data<'_>>) -> Result<orb::Tango_AttributeValue> {

    let name = orb::CORBA_String::try_from(&*attr_data.name)
        .map_err(|_| Error::new(ErrorKind::AttrNameInvalid))?;

    let mut ret = orb::Tango_AttributeValue {
        value: orb::CORBA_Any::new(),
        quality: attr_data.quality.into(),
        time: time_val_from_system_or_zero(attr_data.time),
        name,
        dim_x: 0,
        dim_y: 0,
    };

    if let Some(data) = attr_data.value.as_ref() {
        match data.as_view() {
            DataView::Bool(val) => 
                layout_to_any::<_, orb::Tango_DevVarBooleanArray>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y)?,
            DataView::Octet(val) =>
                layout_to_any::<_, orb::Tango_DevVarCharArray>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y)?,
            DataView::UShort(val) =>
                layout_to_any::<_, orb::Tango_DevVarUShortArray>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y)?,
            DataView::Short(val) =>
                layout_to_any::<_, orb::Tango_DevVarShortArray>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y)?,
            DataView::ULong(val) =>
                layout_to_any::<_, orb::Tango_DevVarULongArray>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y)?,
            DataView::Long(val) =>
                layout_to_any::<_, orb::Tango_DevVarLongArray>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y)?,
            DataView::ULong64(val) =>
                layout_to_any::<_, orb::Tango_DevVarULong64Array>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y)?,
            DataView::Long64(val) =>
                layout_to_any::<_, orb::Tango_DevVarLong64Array>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y)?,
            DataView::Float(val) =>
                layout_to_any::<_, orb::Tango_DevVarFloatArray>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y)?,
            DataView::Double(val) =>
                layout_to_any::<_, orb::Tango_DevVarDoubleArray>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y)?,
            DataView::State(val) =>
                layout_to_any_mapped::<_, _, orb::Tango_DevVarStateArray, _>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y, |&s| s.into())?,
            DataView::String(val) =>
                layout_to_any_mapped::<_, _, orb::Tango_DevVarStringArray, _>(val, &mut ret.value, &mut ret.dim_x, &mut ret.dim_y, |s| {
                    orb::CORBA_String::try_from(&**s).unwrap_or_else(|_| orb::CORBA_String::default())
                })?,
            _ => return Err(Error::new(ErrorKind::InvalidData)
                    .with_append_description(format_args!("Cannot be converted to Tango::AttributeValue: {data:?}"))),
        }
    }

    Ok(ret)

}

pub fn attr_write_to_value_v4(attr_data: &AttrWrite<'_, Data<'_>>) -> Result<orb::Tango_AttributeValue_4> {

    let name = orb::CORBA_String::try_from(&*attr_data.name)
        .map_err(|_| Error::new(ErrorKind::AttrNameInvalid))?;

    let (value, format, dim_x, dim_y) = 
    if let Some(data) = attr_data.value.as_ref() {
        match data.as_view() {
            DataView::Bool(val) => 
                layout_to_union::<_, orb::Tango_DevVarBooleanArray>(val, orb::Tango_AttrValUnion::with_bool_att_value)?,
            DataView::Octet(val) =>
                layout_to_union::<_, orb::Tango_DevVarCharArray>(val, orb::Tango_AttrValUnion::with_uchar_att_value)?,
            DataView::UShort(val) =>
                layout_to_union::<_, orb::Tango_DevVarUShortArray>(val, orb::Tango_AttrValUnion::with_ushort_att_value)?,
            DataView::Short(val) =>
                layout_to_union::<_, orb::Tango_DevVarShortArray>(val, orb::Tango_AttrValUnion::with_short_att_value)?,
            DataView::ULong(val) =>
                layout_to_union::<_, orb::Tango_DevVarULongArray>(val, orb::Tango_AttrValUnion::with_ulong_att_value)?,
            DataView::Long(val) =>
                layout_to_union::<_, orb::Tango_DevVarLongArray>(val, orb::Tango_AttrValUnion::with_long_att_value)?,
            DataView::ULong64(val) =>
                layout_to_union::<_, orb::Tango_DevVarULong64Array>(val, orb::Tango_AttrValUnion::with_ulong64_att_value)?,
            DataView::Long64(val) =>
                layout_to_union::<_, orb::Tango_DevVarLong64Array>(val, orb::Tango_AttrValUnion::with_long64_att_value)?,
            DataView::Float(val) =>
                layout_to_union::<_, orb::Tango_DevVarFloatArray>(val, orb::Tango_AttrValUnion::with_float_att_value)?,
            DataView::Double(val) =>
                layout_to_union::<_, orb::Tango_DevVarDoubleArray>(val, orb::Tango_AttrValUnion::with_double_att_value)?,
            DataView::State(val) =>
                layout_to_union_mapped::<_, _, orb::Tango_DevVarStateArray, _>(val, |&s| s.into(), orb::Tango_AttrValUnion::with_state_att_value)?,
            DataView::String(val) =>
                layout_to_union_mapped::<_, _, orb::Tango_DevVarStringArray, _>(val, |s| {
                    orb::CORBA_String::try_from(&**s).unwrap_or_else(|_| orb::CORBA_String::default())
                }, orb::Tango_AttrValUnion::with_string_att_value)?,
            _ => return Err(Error::new(ErrorKind::InvalidData)
                    .with_append_description(format_args!("Cannot be converted to Tango::AttributeValue: {data:?}"))),
        }
    } else {
        (orb::Tango_AttrValUnion::with_union_no_data(false), orb::Tango_AttrDataFormat::SCALAR, 0, 0)
    };

    let ret = orb::Tango_AttributeValue_4 {
        value,
        quality: attr_data.quality.into(),
        data_format: format,
        time: time_val_from_system_or_zero(attr_data.time),
        name,
        r_dim: orb::Tango_AttributeDim::default(),
        w_dim: orb::Tango_AttributeDim { dim_x, dim_y },
        err_list: orb::Tango_DevErrorList::default(),
    };

    Ok(ret)

}

fn layout_to_slice_and_dim<'a, T>(val: DataLayoutView<'a, T>) -> (&'a [T], orb::Tango_AttrDataFormat, usize, usize) {
    match val {
        DataLayoutView::Scalar(val) => (slice::from_ref(val), orb::Tango_AttrDataFormat::SCALAR, 1, 0),
        DataLayoutView::Spectrum(val) => (val.as_raw_slice(), orb::Tango_AttrDataFormat::SPECTRUM, val.len(0), 0),
        DataLayoutView::Image(val) => (val.as_raw_slice(), orb::Tango_AttrDataFormat::IMAGE, val.len(0), val.len(1)),
    }
}

/// Base internal function to collect the data slice into a IDL var array.
#[inline]
fn layout_to_array_mapped<T, U, A, F>(val: DataLayoutView<'_, T>, func: F) -> Result<(A, orb::Tango_AttrDataFormat, i32, i32)>
where
    F: FnMut(&T) -> U,
    A: FromIterator<U> + orb::CORBA_AnyWideBinding,
{

    let (slice, format, dim_x, dim_y) = layout_to_slice_and_dim(val);
    let dim_x = i32::try_from(dim_x).map_err(|_| Error::new(ErrorKind::InvalidData)
        .with_append_description("Could not encode X dimension"))?;
    let dim_y = i32::try_from(dim_y).map_err(|_| Error::new(ErrorKind::InvalidData)
        .with_append_description("Could not encode Y dimension"))?;

    Ok((slice.iter().map(func).collect::<A>(), format, dim_x, dim_y))

}

#[inline]
fn layout_to_any_mapped<T, U, A, F>(val: DataLayoutView<'_, T>, any: &mut orb::CORBA_Any, dim_x: &mut i32, dim_y: &mut i32, func: F) -> Result<()>
where
    F: FnMut(&T) -> U,
    A: FromIterator<U> + orb::CORBA_AnyWideBinding,
{
    let (arr, _, dim_x_, dim_y_) = layout_to_array_mapped::<T, U, A, _>(val, func)?;
    *dim_x = dim_x_;
    *dim_y = dim_y_;
    any.insert_move(orb::CppBox::new(arr));
    Ok(())
}

#[inline]
fn layout_to_any<T: Clone, A>(val: DataLayoutView<'_, T>, any: &mut orb::CORBA_Any, dim_x: &mut i32, dim_y: &mut i32) -> Result<()>
where
    A: FromIterator<T> + orb::CORBA_AnyWideBinding,
{
    layout_to_any_mapped::<T, T, A, _>(val, any, dim_x, dim_y, |val| val.clone())
}

#[inline]
fn layout_to_union_mapped<T, U, A, F>(val: DataLayoutView<'_, T>, func: F, variant: fn(orb::CppBox<A>) -> orb::Tango_AttrValUnion) -> Result<(orb::Tango_AttrValUnion, orb::Tango_AttrDataFormat, i32, i32)>
where
    F: FnMut(&T) -> U,
    A: FromIterator<U> + orb::CORBA_AnyWideBinding,
{
    let (arr, format, dim_x, dim_y) = layout_to_array_mapped::<T, U, A, _>(val, func)?;
    Ok((variant(orb::CppBox::new(arr)), format, dim_x, dim_y))
}

#[inline]
fn layout_to_union<T: Clone, A>(val: DataLayoutView<'_, T>, variant: fn(orb::CppBox<A>) -> orb::Tango_AttrValUnion) -> Result<(orb::Tango_AttrValUnion, orb::Tango_AttrDataFormat, i32, i32)>
where
    A: FromIterator<T> + orb::CORBA_AnyWideBinding,
{
    layout_to_union_mapped::<T, T, A, _>(val, |val| val.clone(), variant)
}

/// The raw list that we are iterating over, it contains all the Any that we are 
/// extracting from, then we return either owned or borrowed data.
#[derive(Debug)]
pub struct DevReadAttrFromValueList<R> {
    raw_list: orb::CppBox<R>,
}

impl<R> DevReadAttrFromValueList<R> {

    #[inline]
    pub fn new(raw_list: orb::CppBox<R>) -> Self {
        Self {
            raw_list,
        }
    }

}

impl DevReadAttrBackend for DevReadAttrFromValueList<orb::Tango_AttributeValueList> {
    
    fn len(&self) -> usize {
        self.raw_list.len()
    }
    
    fn get(&self, index: usize) -> Result<AttrRead<'_, Data<'_>>> {
        self.raw_list.get(index)
            .ok_or_else(|| Error::new(ErrorKind::InvalidIndex))
            .and_then(attr_read_from_value_v1)
    }

}

impl DevReadAttrBackend for DevReadAttrFromValueList<orb::Tango_AttributeValueList_3> {
    
    fn len(&self) -> usize {
        self.raw_list.len()
    }

    fn get(&self, index: usize) -> Result<AttrRead<'_, Data<'_>>> {
        self.raw_list.get(index)
            .ok_or_else(|| Error::new(ErrorKind::InvalidIndex))
            .and_then(attr_read_from_value_v3)
    }

}

impl DevReadAttrBackend for DevReadAttrFromValueList<orb::Tango_AttributeValueList_4> {
    
    fn len(&self) -> usize {
        self.raw_list.len()
    }
    
    fn get(&self, index: usize) -> Result<AttrRead<'_, Data<'_>>> {
        self.raw_list.get(index)
            .ok_or_else(|| Error::new(ErrorKind::InvalidIndex))
            .and_then(attr_read_from_value_v4)
    }

}

impl DevReadAttrBackend for DevReadAttrFromValueList<orb::Tango_AttributeValueList_5> {
    
    fn len(&self) -> usize {
        self.raw_list.len()
    }
    
    fn get(&self, index: usize) -> Result<AttrRead<'_, Data<'_>>> {
        self.raw_list.get(index)
            .ok_or_else(|| Error::new(ErrorKind::InvalidIndex))
            .and_then(attr_read_from_value_v5)
    }
    
}

/// Read/write values from a raw `Tango::AttributeValue` (v1).
pub fn attr_read_from_value_v1(raw_val: &orb::Tango_AttributeValue) -> Result<AttrRead<'_, Data<'_>>> {

    let (mut value, mut w_value) = (None, None);
    if raw_val.quality != orb::Tango_AttrQuality::ATTR_INVALID {
        (value, w_value) = attr_data_from_any(&raw_val.value, &orb::Tango_AttributeDim { dim_x: raw_val.dim_x, dim_y: raw_val.dim_y }, None)?;
    }
    
    Ok(AttrRead {
        name: raw_val.name.as_cstr().to_string_lossy(),
        value,
        w_value,
        quality: raw_val.quality.into(),
        time: time_val_to_system_or_epoch(&raw_val.time),
    })

}

// Compared to v1, we now have two dimensions for the read and write values, this allows
// us to determine if the attribute is read-only, write-only or read-write. But we still
// don't know the format.
pub fn attr_read_from_value_v3(raw_val: &orb::Tango_AttributeValue_3) -> Result<AttrRead<'_, Data<'_>>> {

    if !raw_val.err_list.is_empty() {
        return Err(Error::from(&raw_val.err_list));
    }

    let (mut value, mut w_value) = (None, None);
    if raw_val.quality != orb::Tango_AttrQuality::ATTR_INVALID {
        (value, w_value) = attr_data_from_any(&raw_val.value, &raw_val.r_dim, Some(&raw_val.w_dim))?;
    }

    Ok(AttrRead {
        name: raw_val.name.as_cstr().to_string_lossy(),
        value,
        w_value,
        quality: raw_val.quality.into(),
        time: time_val_to_system_or_epoch(&raw_val.time),
    })

}

// Compared to v3, we no longer have to deal with a CORBA::Any, we now rely on a CORBA
// union that we can "easily" reduce to a device data, we also now the data format.
pub fn attr_read_from_value_v4(raw_val: &orb::Tango_AttributeValue_4) -> Result<AttrRead<'_, Data<'_>>> {

    if !raw_val.err_list.is_empty() {
        return Err(Error::from(&raw_val.err_list));
    }

    let (mut value, mut w_value) = (None, None);
    if raw_val.quality != orb::Tango_AttrQuality::ATTR_INVALID {
        (value, w_value) = attr_data_from_attr_union(&raw_val.value, raw_val.data_format, &raw_val.r_dim, &raw_val.w_dim)?;
    }
    
    Ok(AttrRead {
        name: raw_val.name.as_cstr().to_string_lossy(),
        value,
        w_value,
        quality: raw_val.quality.into(),
        time: time_val_to_system_or_epoch(&raw_val.time),
    })

}

// Compared to v4, we have an data_type field which is a number that we are currently not
// checking, but that we should check at some point to ensure coherency!
pub fn attr_read_from_value_v5(raw_val: &orb::Tango_AttributeValue_5) -> Result<AttrRead<'_, Data<'_>>> {
    
    if !raw_val.err_list.is_empty() {
        return Err(Error::from(&raw_val.err_list));
    }
    
    let (mut value, mut w_value) = (None, None);
    if raw_val.quality != orb::Tango_AttrQuality::ATTR_INVALID {
        (value, w_value) = attr_data_from_attr_union(&raw_val.value, raw_val.data_format, &raw_val.r_dim, &raw_val.w_dim)?;
    }

    Ok(AttrRead {
        name: raw_val.name.as_cstr().to_string_lossy(),
        value,
        w_value,
        quality: raw_val.quality.into(),
        time: time_val_to_system_or_epoch(&raw_val.time),
    })
    
}

fn attr_data_from_any<'a>(
    any: &'a orb::CORBA_Any,
    r_dim: &orb::Tango_AttributeDim, 
    w_dim: Option<&orb::Tango_AttributeDim>,
) -> Result<(Option<Data<'a>>, Option<Data<'a>>)> {

    #[inline]
    fn seq_tc_item(any: &orb::CORBA_Any) -> Option<orb::CORBA_TypeCodeKind> {
        let tc = any.type_code()?;
        if tc.kind() != orb::CORBA_TypeCodeKind::Alias { return None }
        let tc_seq = tc.content_type()?;
        if tc_seq.kind() != orb::CORBA_TypeCodeKind::Sequence { return None }
        let tc_item = tc_seq.content_type()?;
        Some(tc_item.kind())
    }

    let Some(tc_item_kind) = seq_tc_item(any) else {
        return Err(Error::new(ErrorKind::InvalidData)
            .with_append_description("Could not find attribute type from CORBA::Any"));
    };

    let (r_dim, w_dim) = match (dim_to_tuple(r_dim), w_dim.map(dim_to_tuple)) {
        (Some(r_dim), None) => (r_dim, (0, 0)),
        (Some(r_dim), Some(Some(w_dim))) => (r_dim, w_dim),
        _ => return Err(Error::new(ErrorKind::InvalidData)
            .with_append_description("Could not parse dimensions")),
    };

    fn could_not_extract() -> Error {
        Error::new(ErrorKind::InvalidData)
            .with_append_description("Could not extract attribute type from CORBA::Any")
    }

    match tc_item_kind {
        orb::CORBA_TypeCodeKind::Boolean => 
            data_from_array(&any.extract_borrow::<orb::Tango_DevVarBooleanArray>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::Bool),
        orb::CORBA_TypeCodeKind::UShort => 
            data_from_array(&any.extract_borrow::<orb::Tango_DevVarUShortArray>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::UShort),
        orb::CORBA_TypeCodeKind::Short => 
            data_from_array(&any.extract_borrow::<orb::Tango_DevVarShortArray>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::Short),
        orb::CORBA_TypeCodeKind::ULong => 
            data_from_array(&any.extract_borrow::<orb::Tango_DevVarULongArray>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::ULong),
        orb::CORBA_TypeCodeKind::Long => 
            data_from_array(&any.extract_borrow::<orb::Tango_DevVarLongArray>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::Long),
        orb::CORBA_TypeCodeKind::ULongLong => 
            data_from_array(&any.extract_borrow::<orb::Tango_DevVarULong64Array>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::ULong64),
        orb::CORBA_TypeCodeKind::LongLong => 
            data_from_array(&any.extract_borrow::<orb::Tango_DevVarLong64Array>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::Long64),
        orb::CORBA_TypeCodeKind::Float => 
            data_from_array(&any.extract_borrow::<orb::Tango_DevVarFloatArray>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::Float),
        orb::CORBA_TypeCodeKind::Double => 
            data_from_array(&any.extract_borrow::<orb::Tango_DevVarDoubleArray>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::Double),
            orb::CORBA_TypeCodeKind::String => 
                data_from_array_mapped(any.extract_borrow::<orb::Tango_DevVarStringArray>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::String, |s| s.to_string_lossy()),
        orb::CORBA_TypeCodeKind::Enum => 
            data_from_array_mapped(any.extract_borrow::<orb::Tango_DevVarStateArray>().ok_or_else(could_not_extract)?, None, r_dim, w_dim, Data::State, |&s| s.into()),
        _ => Err(could_not_extract()),
    }

}

fn attr_data_from_attr_union<'a>(
    val_union: &'a orb::Tango_AttrValUnion,
    format: orb::Tango_AttrDataFormat,
    r_dim: &orb::Tango_AttributeDim, 
    w_dim: &orb::Tango_AttributeDim,
) -> Result<(Option<Data<'a>>, Option<Data<'a>>)> {

    let (
        Some(r_dim), 
        Some(w_dim)
    ) = (dim_to_tuple(r_dim), dim_to_tuple(w_dim)) else {
        return Err(Error::new(ErrorKind::InvalidData)
            .with_append_description("Could not parse dimensions"));
    };

    let format=  Some(format);

    if let Some(arr) = val_union.as_bool_att_value() {
        data_from_array(&arr[..], format, r_dim, w_dim, Data::Bool)
    } else if let Some(arr) = val_union.as_short_att_value() {
        data_from_array(&arr[..], format, r_dim, w_dim, Data::Short)
    } else if let Some(arr) = val_union.as_long_att_value() {
        data_from_array(&arr[..], format, r_dim, w_dim, Data::Long)
    } else if let Some(arr) = val_union.as_long64_att_value() {
        data_from_array(&arr[..], format, r_dim, w_dim, Data::Long64)
    } else if let Some(arr) = val_union.as_float_att_value() {
        data_from_array(&arr[..], format, r_dim, w_dim, Data::Float)
    } else if let Some(arr) = val_union.as_double_att_value() {
        data_from_array(&arr[..], format, r_dim, w_dim, Data::Double)
    } else if let Some(arr) = val_union.as_uchar_att_value() {
        data_from_array(&arr[..], format, r_dim, w_dim, Data::Octet)
    } else if let Some(arr) = val_union.as_ushort_att_value() {
        data_from_array(&arr[..], format, r_dim, w_dim, Data::UShort)
    } else if let Some(arr) = val_union.as_ulong_att_value() {
        data_from_array(&arr[..], format, r_dim, w_dim, Data::ULong)
    } else if let Some(arr) = val_union.as_ulong64_att_value() {
        data_from_array(&arr[..], format, r_dim, w_dim, Data::ULong64)
    } else if let Some(arr) = val_union.as_string_att_value() {
        data_from_array_mapped(&arr[..], format, r_dim, w_dim, Data::String, |s| s.to_string_lossy())
    } else if let Some(arr) = val_union.as_state_att_value() {
        data_from_array_mapped(&arr[..], format, r_dim, w_dim, Data::State, |&s| s.into())
    } else if let Some(&state) = val_union.as_dev_state_att() {
        Ok((
            Some(Data::State(DataLayout::Scalar(state.into()))),
            None,  // No write value for the device's state!
        ))
    } else {
        unreachable!()
    }

}

/// Internal function to transform a Tango::AttributeDim into a tuple, easier to match.
fn dim_to_tuple(dim: &orb::Tango_AttributeDim) -> Option<(usize, usize)> {
    Some((usize::try_from(dim.dim_x).ok()?, usize::try_from(dim.dim_y).ok()?))
}

// Note: This function is instantiated a lot, so we are currently hopping that the 
// monomorphization will produce code that could be later optimized, in order to 
// reduce the size of these functions in the executable.
// Later, if we really need to optimize this function, we can try to make some unsafe
// stuff that rely on dynamically-passed sizeof T, and splitting dynamically, yet to
// decide if need!
fn attr_data_from_array<'a, T>(
    arr: &'a [T],
    format: Option<orb::Tango_AttrDataFormat>,
    r_dim: (usize, usize), 
    w_dim: (usize, usize),
) -> (Option<DataLayout<'a, T>>, Option<DataLayout<'a, T>>) {
    match format {
        Some(orb::Tango_AttrDataFormat::SCALAR) => {
            match (r_dim, w_dim, arr.len()) {
                ((1, 0), (0, 0), 1) => ( // Read-only
                    Some(DataLayout::ScalarView(&arr[0])),
                    None
                ),
                ((1, 0), (1, 0), 1) => ( // Write-only
                    None,
                    Some(DataLayout::ScalarView(&arr[0])),
                ),
                ((1, 0), (1, 0), 2) => ( // Read-write
                    Some(DataLayout::ScalarView(&arr[0])),
                    Some(DataLayout::ScalarView(&arr[1])),
                ),
                _ => (None, None)
            }
        }
        Some(orb::Tango_AttrDataFormat::SPECTRUM) => {
            match (r_dim, w_dim, arr.len()) {
                ((r_x, 0), (0, 0), len) if r_x == len => ( // Read-only
                    Some(DataLayout::SpectrumView(arr[..].into())),
                    None,
                ),
                ((_, 0), (w_x, 0), len) if w_x == len => ( // Write-only
                    None,
                    Some(DataLayout::SpectrumView(arr[..].into())),
                ),
                ((r_x, 0), (w_x, 0), len) if r_x + w_x == len => ( // Read-write
                    Some(DataLayout::SpectrumView(arr[..r_x].into())),
                    Some(DataLayout::SpectrumView(arr[r_x..].into())),
                ),
                _ => (None, None)
            }
        }
        Some(orb::Tango_AttrDataFormat::IMAGE) => {
            match (r_dim, w_dim, arr.len()) {
                ((r_x, r_y), (0, 0), len) if r_x * r_y == len => ( // Read-only
                    Some(DataLayout::ImageView(MdView::try_from_slice(&arr[..], [r_x, r_y]).unwrap())),
                    None,
                ),
                ((_, _), (w_x, w_y), len) if w_x * w_y == len => ( // Write-only
                    None,
                    Some(DataLayout::ImageView(MdView::try_from_slice(&arr[..], [w_x, w_y]).unwrap())),
                ),
                ((r_x, r_y), (w_x, w_y), len) if r_x * r_y + w_x * w_y == len => ( // Read-write
                    Some(DataLayout::ImageView(MdView::try_from_slice(&arr[..(r_x * r_y)], [r_x, r_y]).unwrap())),
                    Some(DataLayout::ImageView(MdView::try_from_slice(&arr[(r_x * r_y)..], [w_x, w_y]).unwrap())),
                ),
                _ => (None, None)
            }
        }
        Some(orb::Tango_AttrDataFormat::FMT_UNKNOWN) => (None, None),
        None => {
            
            // We must guess the data format from the dimensions, note that this is used
            // for read_attributes v1 and v3, note that v1 don't even have different 
            // dimensions for read and write.
            //
            // What's known in v1:
            // - Not possible to distinguish read-only from write-only for scalars;
            // - Can only read read-only spectrums or images;

            match (r_dim, w_dim, arr.len()) {
                ((1, 0), (0, 0), len) if len == 1 => ( // Read-only
                    Some(DataLayout::ScalarView(&arr[0])),
                    None,
                ),
                ((1, 0), (1, 0), len) if len == 1 => (  // Write-only (v3 only)
                    None,
                    Some(DataLayout::ScalarView(&arr[0])),
                ),
                ((1, 0), (0, 0) | (1, 0), len) if len == 2 => ( // Read-write
                    Some(DataLayout::ScalarView(&arr[0])),
                    Some(DataLayout::ScalarView(&arr[1])),
                ),
                ((r_x, 0), (0, 0), len) if r_x == len => ( // Read-only
                    Some(DataLayout::SpectrumView(arr[..].into())),
                    None,
                ),
                ((_, 0), (w_x, 0), len) if w_x == len => ( // Write-only (v3 only)
                    None,
                    Some(DataLayout::SpectrumView(arr[..].into())),
                ),
                ((r_x, 0), (w_x, 0), len) if r_x + w_x == len => ( // Read-write (v3 only)
                    Some(DataLayout::SpectrumView(arr[..r_x].into())),
                    Some(DataLayout::SpectrumView(arr[r_x..].into())),
                ),
                ((r_x, r_y), (0, 0), len) if r_x * r_y == len => ( // Read-only
                    Some(DataLayout::ImageView(MdView::try_from_slice(&arr[..], [r_x, r_y]).unwrap())),
                    None,
                ),
                ((_, _), (w_x, w_y), len) if w_x * w_y == len => ( // Write-only (v3 only)
                    None,
                    Some(DataLayout::ImageView(MdView::try_from_slice(&arr[..], [w_x, w_y]).unwrap())),
                ),
                ((r_x, r_y), (w_x, w_y), len) if r_x * r_y + w_x * w_y == len => ( // Read-write (v3 only)
                    Some(DataLayout::ImageView(MdView::try_from_slice(&arr[..(r_x * r_y)], [r_x, r_y]).unwrap())),
                    Some(DataLayout::ImageView(MdView::try_from_slice(&arr[(r_x * r_y)..], [w_x, w_y]).unwrap())),
                ),
                _ => (None, None)
            }

        }
    }
}

fn data_from_array<'a, T>(
    arr: &'a [T],
    format: Option<orb::Tango_AttrDataFormat>,
    r_dim: (usize, usize), 
    w_dim: (usize, usize),
    variant: fn(DataLayout<'a, T>) -> Data<'a>,
) -> Result<(Option<Data<'a>>, Option<Data<'a>>)> {

    let (
        value, 
        w_value,
    ) = attr_data_from_array(arr, format, r_dim, w_dim);

    if value.is_none() && w_value.is_none() {
        return Err(Error::new(ErrorKind::InvalidData)
            .with_append_description("Could not parse attribute data"));
    }

    Ok((
        value.map(variant),
        w_value.map(variant),
    ))

}

fn data_from_array_mapped<'a, T, U, F: FnMut(&'a T) -> U>(
    arr: &'a [T],
    format: Option<orb::Tango_AttrDataFormat>,
    r_dim: (usize, usize), 
    w_dim: (usize, usize),
    variant: fn(DataLayout<'a, U>) -> Data<'a>,
    mut func: F,
) -> Result<(Option<Data<'a>>, Option<Data<'a>>)> {
    
    let (
        value, 
        w_value,
    ) = attr_data_from_array(arr, format, r_dim, w_dim);

    if value.is_none() && w_value.is_none() {
        return Err(Error::new(ErrorKind::InvalidData)
            .with_append_description("Could not parse attribute data"));
    }

    Ok((
        value.map(|data| data.map_view_to_owned(&mut func)).map(variant),
        w_value.map(|data| data.map_view_to_owned(&mut func)).map(variant),
    ))

}

/// A attr write array that returns Ok for every attribute index.
#[derive(Debug)]
pub struct DevWriteAttrOk {
    pub len: usize,
}

impl DevWriteAttrBackend for DevWriteAttrOk {
    
    fn len(&self) -> usize {
        self.len
    }

    fn check(&self, index: usize) -> Result<()> {
        let _ = index;
        Ok(())
    }

}

/// A attr write array that forwards the errors selectively for all attribute specified in
/// an [`orb::Tango_MultiDevFailed`].
#[derive(Debug)]
pub struct DevWriteAttrFromMultiDevFailed {
    pub len: usize,
    pub err: orb::Tango_MultiDevFailed
}

impl DevWriteAttrBackend for DevWriteAttrFromMultiDevFailed {
    
    fn len(&self) -> usize {
        self.len
    }

    fn check(&self, index: usize) -> Result<()> {
        
        for err in &self.err.data().errors {
            if usize::try_from(err.index_in_call) == Ok(index) {
                return Err(Error::from(&err.err_list));
            }
        }

        Ok(())
        
    }
    
}

/// The raw list that we are iterating over, it contains all the raw attribute configs
/// that we want to convert to the exposed Rust type.
#[derive(Debug)]
pub struct DevGetAttrConfigFromList<R> {
    raw_list: orb::CppBox<R>,
}

impl<R> DevGetAttrConfigFromList<R> {

    #[inline]
    pub fn new(raw_list: orb::CppBox<R>) -> Self {
        Self {
            raw_list,
        }
    }

}

impl DevGetAttrInfoBackend for DevGetAttrConfigFromList<orb::Tango_AttributeConfigList> {
    
    fn len(&self) -> usize {
        self.raw_list.len()
    }

    fn get(&self, index: usize) -> Result<AttrInfo<'_>> {
        self.raw_list.get(index)
            .ok_or_else(|| Error::new(ErrorKind::InvalidIndex))
            .and_then(attr_info_from_v1)
    }

}

impl DevGetAttrInfoBackend for DevGetAttrConfigFromList<orb::Tango_AttributeConfigList_2> {
    
    fn len(&self) -> usize {
        self.raw_list.len()
    }

    fn get(&self, index: usize) -> Result<AttrInfo<'_>> {
        self.raw_list.get(index)
            .ok_or_else(|| Error::new(ErrorKind::InvalidIndex))
            .and_then(attr_info_from_v2)
    }

}

impl DevGetAttrInfoBackend for DevGetAttrConfigFromList<orb::Tango_AttributeConfigList_3> {
    
    fn len(&self) -> usize {
        self.raw_list.len()
    }

    fn get(&self, index: usize) -> Result<AttrInfo<'_>> {
        self.raw_list.get(index)
            .ok_or_else(|| Error::new(ErrorKind::InvalidIndex))
            .and_then(attr_info_from_v3)
    }

}

impl DevGetAttrInfoBackend for DevGetAttrConfigFromList<orb::Tango_AttributeConfigList_5> {
    
    fn len(&self) -> usize {
        self.raw_list.len()
    }

    fn get(&self, index: usize) -> Result<AttrInfo<'_>> {
        self.raw_list.get(index)
            .ok_or_else(|| Error::new(ErrorKind::InvalidIndex))
            .and_then(attr_info_from_v5)
    }

}

/// Attribute info from a raw `Tango::AttributeConfig` (v1).
pub fn attr_info_from_v1(raw_conf: &orb::Tango_AttributeConfig) -> Result<AttrInfo<'_>> {
    let mode = mode_from_attr_write_type(&raw_conf.writable, &raw_conf.writable_attr_name)?;
    let data_kind = data_kind_from_type_format(raw_conf.data_format, raw_conf.data_type, raw_conf.max_dim_x, raw_conf.max_dim_y, None)?;
    Ok(AttrInfo {
        config: AttrConfig {
            name: raw_conf.name.to_string_lossy(),
            description: opt_str_if(&raw_conf.description, S_NO_DESCRIPTION),
            label: opt_str_if(&raw_conf.label, ""),
            unit: opt_str_if(&raw_conf.unit, ""),
            standard_unit: opt_f32_empty(&raw_conf.standard_unit),
            display_unit: opt_str_if(&raw_conf.display_unit, S_NO_DISPLAY_UNIT),
            display_level: DisplayLevel::Operator,
            display_format: opt_str_if(&raw_conf.format, S_NOT_SPECIFIED),
            range: AttrRangeConfig {
                min: opt_str_if(&raw_conf.min_value, S_NOT_SPECIFIED),
                max: opt_str_if(&raw_conf.max_value, S_NOT_SPECIFIED),
            },
            alarm: AttrRangeConfig {
                min: opt_str_if(&raw_conf.min_alarm, S_NOT_SPECIFIED),
                max: opt_str_if(&raw_conf.max_alarm, S_NOT_SPECIFIED),
            },
            warning: AttrRangeConfig::default(),
            delta: None,
            change_event: AttrChangeEventConfig::default(),
            periodic_event: AttrPeriodicEventConfig::default(),
            archive_event: AttrArchiveEventConfig::default(),
            memorized: None,
            extensions: raw_conf.extensions.iter()
                .map(|s| s.to_string_lossy().into_owned())
                .collect::<Vec<_>>(),
        },
        mode,
        data_kind,
    })
}

/// The only difference with v1 is the presence of the display level.
pub fn attr_info_from_v2(raw_conf: &orb::Tango_AttributeConfig_2) -> Result<AttrInfo<'_>> {
    let mode = mode_from_attr_write_type(&raw_conf.writable, &raw_conf.writable_attr_name)?;
    let data_kind = data_kind_from_type_format(raw_conf.data_format, raw_conf.data_type, raw_conf.max_dim_x, raw_conf.max_dim_y, None)?;
    Ok(AttrInfo {
        config: AttrConfig {
            name: raw_conf.name.to_string_lossy(),
            description: opt_str_if(&raw_conf.description, S_NO_DESCRIPTION),
            label: opt_str_if(&raw_conf.label, ""),
            unit: opt_str_if(&raw_conf.unit, ""),
            standard_unit: opt_f32_empty(&raw_conf.standard_unit),
            display_unit: opt_str_if(&raw_conf.display_unit, S_NO_DISPLAY_UNIT),
            display_level: raw_conf.level.into(),
            display_format: opt_str_if(&raw_conf.format, S_NOT_SPECIFIED),
            range: AttrRangeConfig {
                min: opt_str_if(&raw_conf.min_value, S_NOT_SPECIFIED),
                max: opt_str_if(&raw_conf.max_value, S_NOT_SPECIFIED),
            },
            alarm: AttrRangeConfig {
                min: opt_str_if(&raw_conf.min_alarm, S_NOT_SPECIFIED),
                max: opt_str_if(&raw_conf.max_alarm, S_NOT_SPECIFIED),
            },
            warning: AttrRangeConfig::default(),
            delta: None,
            change_event: AttrChangeEventConfig::default(),
            periodic_event: AttrPeriodicEventConfig::default(),
            archive_event: AttrArchiveEventConfig::default(),
            memorized: None,
            extensions: raw_conf.extensions.iter()
                .map(|s| s.to_string_lossy().into_owned())
                .collect::<Vec<_>>(),
        },
        mode,
        data_kind,
    })
}

/// The difference from v1/v2 is the structure has been reorganize in structures, and the
/// warning and delta alarm support is present.
pub fn attr_info_from_v3(raw_conf: &orb::Tango_AttributeConfig_3) -> Result<AttrInfo<'_>> {
    let mode = mode_from_attr_write_type(&raw_conf.writable, &raw_conf.writable_attr_name)?;
    let data_kind = data_kind_from_type_format(raw_conf.data_format, raw_conf.data_type, raw_conf.max_dim_x, raw_conf.max_dim_y, None)?;
    Ok(AttrInfo {
        config: AttrConfig {
            name: raw_conf.name.to_string_lossy(),
            description: opt_str_if(&raw_conf.description, S_NO_DESCRIPTION),
            label: opt_str_if(&raw_conf.label, ""),
            unit: opt_str_if(&raw_conf.unit, ""),
            standard_unit: opt_f32_empty(&raw_conf.standard_unit),
            display_unit: opt_str_if(&raw_conf.display_unit, S_NO_DISPLAY_UNIT),
            display_level: raw_conf.level.into(),
            display_format: opt_str_if(&raw_conf.format, S_NOT_SPECIFIED),
            range: AttrRangeConfig {
                min: opt_str_if(&raw_conf.min_value, S_NOT_SPECIFIED),
                max: opt_str_if(&raw_conf.max_value, S_NOT_SPECIFIED),
            },
            alarm: AttrRangeConfig {
                min: opt_str_if(&raw_conf.att_alarm.min_alarm, S_NOT_SPECIFIED),
                max: opt_str_if(&raw_conf.att_alarm.max_alarm, S_NOT_SPECIFIED),
            },
            warning: AttrRangeConfig {
                min: opt_str_if(&raw_conf.att_alarm.min_warning, S_NOT_SPECIFIED),
                max: opt_str_if(&raw_conf.att_alarm.max_warning, S_NOT_SPECIFIED),
            },
            delta: opt_str_if(&raw_conf.att_alarm.delta_val, S_NOT_SPECIFIED).map(|value| {
                AttrDeltaConfig {
                    value,
                    time: opt_duration_if(&raw_conf.att_alarm.delta_t, S_NOT_SPECIFIED),
                }
            }),
            change_event: AttrChangeEventConfig {
                rel_change: opt_str_if(&raw_conf.event_prop.ch_event.rel_change, S_NOT_SPECIFIED),
                abs_change: opt_str_if(&raw_conf.event_prop.ch_event.abs_change, S_NOT_SPECIFIED),
            },
            periodic_event: AttrPeriodicEventConfig {
                period: opt_duration_if(&raw_conf.event_prop.per_event.period, S_NOT_SPECIFIED),
            },
            archive_event: AttrArchiveEventConfig {
                rel_change: opt_str_if(&raw_conf.event_prop.arch_event.rel_change, S_NOT_SPECIFIED),
                abs_change: opt_str_if(&raw_conf.event_prop.arch_event.abs_change, S_NOT_SPECIFIED),
                period: opt_duration_if(&raw_conf.event_prop.arch_event.period, S_NOT_SPECIFIED),
            },
            memorized: None,
            extensions: raw_conf.extensions.iter()
                .map(|s| s.to_string_lossy().into_owned())
                .collect::<Vec<_>>(),
        },
        mode,
        data_kind,
    })
}

/// Compared to v3 we have the memorized.
pub fn attr_info_from_v5(raw_conf: &orb::Tango_AttributeConfig_5) -> Result<AttrInfo<'_>> {
    let mode = mode_from_attr_write_type(&raw_conf.writable, &raw_conf.writable_attr_name)?;
    let data_kind = data_kind_from_type_format(raw_conf.data_format, raw_conf.data_type, raw_conf.max_dim_x, raw_conf.max_dim_y, Some(&raw_conf.enum_labels))?;
    Ok(AttrInfo {
        config: AttrConfig {
            name: raw_conf.name.to_string_lossy(),
            description: opt_str_if(&raw_conf.description, S_NO_DESCRIPTION),
            label: opt_str_if(&raw_conf.label, ""),
            unit: opt_str_if(&raw_conf.unit, ""),
            standard_unit: opt_f32_empty(&raw_conf.standard_unit),
            display_unit: opt_str_if(&raw_conf.display_unit, S_NO_DISPLAY_UNIT),
            display_level: raw_conf.level.into(),
            display_format: opt_str_if(&raw_conf.format, S_NOT_SPECIFIED),
            range: AttrRangeConfig {
                min: opt_str_if(&raw_conf.min_value, S_NOT_SPECIFIED),
                max: opt_str_if(&raw_conf.max_value, S_NOT_SPECIFIED),
            },
            alarm: AttrRangeConfig {
                min: opt_str_if(&raw_conf.att_alarm.min_alarm, S_NOT_SPECIFIED),
                max: opt_str_if(&raw_conf.att_alarm.max_alarm, S_NOT_SPECIFIED),
            },
            warning: AttrRangeConfig {
                min: opt_str_if(&raw_conf.att_alarm.min_warning, S_NOT_SPECIFIED),
                max: opt_str_if(&raw_conf.att_alarm.max_warning, S_NOT_SPECIFIED),
            },
            delta: opt_str_if(&raw_conf.att_alarm.delta_val, S_NOT_SPECIFIED).map(|value| {
                AttrDeltaConfig {
                    value,
                    time: opt_duration_if(&raw_conf.att_alarm.delta_t, S_NOT_SPECIFIED),
                }
            }),
            change_event: AttrChangeEventConfig {
                rel_change: opt_str_if(&raw_conf.event_prop.ch_event.rel_change, S_NOT_SPECIFIED),
                abs_change: opt_str_if(&raw_conf.event_prop.ch_event.abs_change, S_NOT_SPECIFIED),
            },
            periodic_event: AttrPeriodicEventConfig {
                period: opt_duration_if(&raw_conf.event_prop.per_event.period, S_NOT_SPECIFIED),
            },
            archive_event: AttrArchiveEventConfig {
                rel_change: opt_str_if(&raw_conf.event_prop.arch_event.rel_change, S_NOT_SPECIFIED),
                abs_change: opt_str_if(&raw_conf.event_prop.arch_event.abs_change, S_NOT_SPECIFIED),
                period: opt_duration_if(&raw_conf.event_prop.arch_event.period, S_NOT_SPECIFIED),
            },
            memorized: match (raw_conf.memorized, raw_conf.mem_init) {
                (false, _) => None,
                (true, false) => Some(AttrMemorizedMode::Light),
                (true, true) => Some(AttrMemorizedMode::WriteAtInit),
            },
            extensions: raw_conf.extensions.iter()
                .map(|s| s.to_string_lossy().into_owned())
                .collect::<Vec<_>>(),
        },
        mode,
        data_kind,
    })
}


fn mode_from_attr_write_type<'name>(typ: &orb::Tango_AttrWriteType, write_attr_name: &'name orb::CORBA_String) -> Result<AttrMode<'name>> {
    Ok(match typ {
        orb::Tango_AttrWriteType::READ => AttrMode::Read,
        orb::Tango_AttrWriteType::READ_WITH_WRITE => AttrMode::ReadWithWrite { 
            write_attr_name: write_attr_name.to_string_lossy(),
        },
        orb::Tango_AttrWriteType::WRITE => AttrMode::Write,
        orb::Tango_AttrWriteType::READ_WRITE => AttrMode::ReadWrite,
        orb::Tango_AttrWriteType::WT_UNKNOWN => return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description("Unexpected Tango::AttrWriteType::WT_UNKNOWN")),
    })
}

fn data_kind_from_type_format(
    data_format: orb::Tango_AttrDataFormat, 
    data_type: orb::CORBA_Long, 
    max_dim_x: i32, 
    max_dim_y: i32, 
    enum_labels: Option<&orb::Tango_DevVarStringArray>,
) -> Result<DataKind> {
    
    let dim_x = u32::try_from(max_dim_x).unwrap_or(0) as usize;
    let dim_y = u32::try_from(max_dim_y).unwrap_or(0) as usize;

    let layout = match data_format {
        tango_omniorb::Tango_AttrDataFormat::SCALAR => DataLayoutKind::Scalar,
        tango_omniorb::Tango_AttrDataFormat::SPECTRUM => DataLayoutKind::Spectrum(Some(dim_x)),
        tango_omniorb::Tango_AttrDataFormat::IMAGE => DataLayoutKind::Image(Some((dim_x, dim_y))),
        tango_omniorb::Tango_AttrDataFormat::FMT_UNKNOWN => return Err(Error::new(ErrorKind::InvalidData)
                .with_append_description("Unexpected Tango::AttrWriteType::FMT_UNKNOWN")),
    };

    data_kind_from_type(data_type, Some(layout), enum_labels)
        .ok_or_else(|| Error::new(ErrorKind::InvalidData)
                .with_append_description(format_args!("Unexpected raw tango data type: {data_type}")))

}
