//! Generic Tango data for devices.

use std::convert::Infallible;
use std::fmt::{Debug, Display};
use std::borrow::Cow;
use std::{mem, slice};

use crate::array::{MdArray, MdView};
use crate::dev::DevState;


/// A temporary pointer to a data stored inside a [`DataBuf`].
#[derive(Debug, Clone, Default)]
pub enum Data<'a> {
    #[default]
    Void,
    Bool(DataLayout<'a, bool>),
    Octet(DataLayout<'a, u8>),
    UShort(DataLayout<'a, u16>),
    Short(DataLayout<'a, i16>),
    ULong(DataLayout<'a, u32>),
    Long(DataLayout<'a, i32>),
    ULong64(DataLayout<'a, u64>),
    Long64(DataLayout<'a, i64>),
    Float(DataLayout<'a, f32>),
    Double(DataLayout<'a, f64>),
    State(DataLayout<'a, DevState>),
    String(DataLayout<'a, Cow<'a, str>>),
    Struct(DataStruct<'a>),
}

#[derive(Debug, Clone)]
pub enum DataLayout<'a, T: 'a> {
    Scalar(T),
    ScalarView(&'a T),
    Spectrum(MdArray<T, 1>),
    SpectrumView(MdView<'a, T, 1>),
    Image(MdArray<T, 2>),
    ImageView(MdView<'a, T, 2>),
}

#[derive(Debug, Clone)]
pub enum DataStruct<'a> {
    Owned(Box<[Data<'a>]>),
    View(&'a [Data<'a>]),
}

impl<'a> Data<'a> {

    pub fn kind(&self) -> DataKind {
        self.as_view().kind()
    }

    /// Get a view over the data.
    pub fn as_view(&self) -> DataView<'_> {
        match self {
            Data::Void => DataView::Void,
            Data::Bool(data) => DataView::Bool(data.as_view()),
            Data::Octet(data) => DataView::Octet(data.as_view()),
            Data::UShort(data) => DataView::UShort(data.as_view()),
            Data::Short(data) => DataView::Short(data.as_view()),
            Data::ULong(data) => DataView::ULong(data.as_view()),
            Data::Long(data) => DataView::Long(data.as_view()),
            Data::ULong64(data) => DataView::ULong64(data.as_view()),
            Data::Long64(data) => DataView::Long64(data.as_view()),
            Data::Float(data) => DataView::Float(data.as_view()),
            Data::Double(data) => DataView::Double(data.as_view()),
            Data::State(data) => DataView::State(data.as_view()),
            Data::String(data) => DataView::String(data.as_view()),
            Data::Struct(data) => DataView::Struct(data.as_view()),
        }
    }

    /// Internal function that determines if a data is owned, this is used to transmute
    /// the data to Data<'static>, **this function is critical to the soundness of this
    /// transmutation!**
    fn is_owned(&self) -> bool {
        match self {
            Data::Void => true,
            Data::Bool(data) => data.is_owned(),
            Data::Octet(data) => data.is_owned(),
            Data::UShort(data) => data.is_owned(),
            Data::Short(data) => data.is_owned(),
            Data::ULong(data) => data.is_owned(),
            Data::Long(data) => data.is_owned(),
            Data::ULong64(data) => data.is_owned(),
            Data::Long64(data) => data.is_owned(),
            Data::Float(data) => data.is_owned(),
            Data::Double(data) => data.is_owned(),
            Data::State(data) => data.is_owned(),
            Data::String(DataLayout::Scalar(Cow::Owned(_))) => true,
            Data::String(DataLayout::Spectrum(arr)) => arr.as_raw_slice().iter().all(|s| matches!(s, Cow::Owned(_))),
            Data::String(DataLayout::Image(arr)) => arr.as_raw_slice().iter().all(|s| matches!(s, Cow::Owned(_))),
            Data::String(_) => false,
            Data::Struct(DataStruct::View(_)) => false,
            Data::Struct(DataStruct::Owned(data)) => data.iter().all(|d| d.is_owned()),
        }
    }

    /// Try to convert this data into a `Data<'static>`, this will only succeed if the
    /// data only contains owned data, including recursive structures such as spectrum,
    /// images and structs.
    pub fn to_owned(self) -> Result<Data<'static>, Self> {
        if self.is_owned() {
            // SAFETY: We checked that the data contains only owned data, therefore we
            // can safely transmute the lifetime to 'static, because the lifetime is not
            // actually used with owned data (either in DataLayout, or Cow<str>).
            Ok(unsafe { mem::transmute(self) })
        } else {
            Err(self)
        }
    }

    /// Make this data into an owned version of itself, thus allowing to move this data
    /// around without worrying about dependency. This effectively make every slice owned
    /// as boxes, and all string cows made into their owned version.
    pub fn into_owned(self) -> Data<'static> {
        match self {
            Data::Void => Data::Void,
            Data::Bool(data) => Data::Bool(data.into_owned()),
            Data::Octet(data) => Data::Octet(data.into_owned()),
            Data::UShort(data) => Data::UShort(data.into_owned()),
            Data::Short(data) => Data::Short(data.into_owned()),
            Data::ULong(data) => Data::ULong(data.into_owned()),
            Data::Long(data) => Data::Long(data.into_owned()),
            Data::ULong64(data) => Data::ULong64(data.into_owned()),
            Data::Long64(data) => Data::Long64(data.into_owned()),
            Data::Float(data) => Data::Float(data.into_owned()),
            Data::Double(data) => Data::Double(data.into_owned()),
            Data::State(data) => Data::State(data.into_owned()),
            Data::String(DataLayout::Scalar(v)) => 
                Data::String(DataLayout::Scalar(Cow::Owned(v.to_string()))),
            Data::String(DataLayout::ScalarView(v)) => 
                Data::String(DataLayout::Scalar(Cow::Owned(v.to_string()))),
            Data::String(DataLayout::Spectrum(v)) => 
                Data::String(DataLayout::Spectrum(v.map(|v| Cow::Owned(v.into_owned())))),
            Data::String(DataLayout::SpectrumView(v)) => 
                Data::String(DataLayout::Spectrum(v.map_to_owned(|v| Cow::Owned(v.to_string())))),
            Data::String(DataLayout::Image(v)) => 
                Data::String(DataLayout::Image(v.map(|v| Cow::Owned(v.into_owned())))),
            Data::String(DataLayout::ImageView(v)) => 
                Data::String(DataLayout::Image(v.map_to_owned(|v| Cow::Owned(v.to_string())))),
            Data::Struct(DataStruct::Owned(data)) => Data::Struct(DataStruct::Owned(data.into_iter()
                .map(|data| data.into_owned())
                .collect::<Box<[_]>>())),
            Data::Struct(DataStruct::View(data)) => Data::Struct(DataStruct::Owned(data.iter()
                .map(|data| data.clone().into_owned())
                .collect::<Box<[_]>>()))
        }
    }

}

impl<'a, T: 'a> DataLayout<'a, T> {

    /// Internal function to get view of a data layout.
    pub fn as_view(&self) -> DataLayoutView<'_, T> {
        match self {
            DataLayout::Scalar(v) => DataLayoutView::Scalar(v),
            &DataLayout::ScalarView(v) => DataLayoutView::Scalar(v),
            DataLayout::Spectrum(v) => DataLayoutView::Spectrum(v.as_view()),
            &DataLayout::SpectrumView(v) => DataLayoutView::Spectrum(v),
            DataLayout::Image(v) => DataLayoutView::Image(v.as_view()),
            &DataLayout::ImageView(v) => DataLayoutView::Image(v),
        }
    }

    fn is_owned(&self) -> bool {
        match self {
            DataLayout::Scalar(_) |
            DataLayout::Spectrum(_) |
            DataLayout::Image(_) => true,
            DataLayout::ScalarView(_) |
            DataLayout::SpectrumView(_) |
            DataLayout::ImageView(_) => false,
        }
    }

    pub(crate) fn map_view_to_owned<U, F: FnMut(&'a T) -> U>(self, mut func: F) -> DataLayout<'a, U> {
        match self {
            DataLayout::Scalar(_) => panic!("this function only works on views"),
            DataLayout::ScalarView(v) => DataLayout::Scalar(func(v)),
            DataLayout::Spectrum(_) => panic!("this function only works on views"),
            DataLayout::SpectrumView(v) => DataLayout::Spectrum(v.map_to_owned(func)),
            DataLayout::Image(_) => panic!("this function only works on views"),
            DataLayout::ImageView(v) => DataLayout::Image(v.map_to_owned(func)),
        }
    }

}

impl<'a, T: 'a + Clone> DataLayout<'a, T> {

    fn into_owned(self) -> DataLayout<'static, T> {
        match self {
            DataLayout::Scalar(v) => DataLayout::Scalar(v),
            DataLayout::ScalarView(v) => DataLayout::Scalar(v.clone()),
            DataLayout::Spectrum(v) => DataLayout::Spectrum(v),
            DataLayout::SpectrumView(v) => DataLayout::Spectrum(v.to_owned()),
            DataLayout::Image(v) => DataLayout::Image(v),
            DataLayout::ImageView(v) => DataLayout::Image(v.to_owned()),
        }
    }

}

impl DataStruct<'_> {

    #[inline]
    pub fn as_view(&self) -> DataStructView<'_> {
        match self {
            DataStruct::Owned(fields) => DataStructView { raw: &fields[..] },
            DataStruct::View(fields) => DataStructView { raw: *fields },
        }
    }

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

}

// =========================== //
//          Data view          //
// =========================== //

/// A view over a data, which only borrows from the underlying data.
#[derive(Debug, Clone)]
pub enum DataView<'a> {
    Void,
    Bool(DataLayoutView<'a, bool>),
    Octet(DataLayoutView<'a, u8>),
    UShort(DataLayoutView<'a, u16>),
    Short(DataLayoutView<'a, i16>),
    ULong(DataLayoutView<'a, u32>),
    Long(DataLayoutView<'a, i32>),
    ULong64(DataLayoutView<'a, u64>),
    Long64(DataLayoutView<'a, i64>),
    Float(DataLayoutView<'a, f32>),
    Double(DataLayoutView<'a, f64>),
    State(DataLayoutView<'a, DevState>),
    String(DataLayoutView<'a, Cow<'a, str>>),
    Struct(DataStructView<'a>),
}

/// Layout for a data view.
#[derive(Debug, Clone, Copy)]
pub enum DataLayoutView<'a, T: 'a> {
    Scalar(&'a T),
    Spectrum(MdView<'a, T, 1>),
    Image(MdView<'a, T, 2>),
}

/// A view over a data structure.
#[derive(Debug, Clone)]
pub struct DataStructView<'a> {
    raw: &'a [Data<'a>],
}

impl<'a> DataView<'a> {

    pub fn kind(&self) -> DataKind {
        match self {
            DataView::Void => DataKind::Void,
            DataView::Bool(layout) => DataKind::Bool(layout.kind()),
            DataView::Octet(layout) => DataKind::Octet(layout.kind()),
            DataView::UShort(layout) => DataKind::UShort(layout.kind()),
            DataView::Short(layout) => DataKind::Short(layout.kind()),
            DataView::ULong(layout) => DataKind::ULong(layout.kind()),
            DataView::Long(layout) => DataKind::Long(layout.kind()),
            DataView::ULong64(layout) => DataKind::ULong64(layout.kind()),
            DataView::Long64(layout) => DataKind::Long64(layout.kind()),
            DataView::Float(layout) => DataKind::Float(layout.kind()),
            DataView::Double(layout) => DataKind::Double(layout.kind()),
            DataView::State(layout) => DataKind::State(layout.kind()),
            DataView::String(layout) => DataKind::String(layout.kind()),
            DataView::Struct(fields) => DataKind::Struct(Cow::Owned(fields.raw.iter()
                .map(|data| data.as_view().kind())
                .collect::<Vec<_>>())),
        }
    }

    /// Transform this view into a data, note that the produced data will only contains
    /// views over the data. The only limit is that strings are not borrowed to create a
    /// borrowed string, this would be to costly for nothing.
    pub fn into_owned(self) -> Data<'a> {
        match self {
            DataView::Void => Data::Void,
            DataView::Bool(data) => Data::Bool(data.into_owned()),
            DataView::Octet(data) => Data::Octet(data.into_owned()),
            DataView::UShort(data) => Data::UShort(data.into_owned()),
            DataView::Short(data) => Data::Short(data.into_owned()),
            DataView::ULong(data) => Data::ULong(data.into_owned()),
            DataView::Long(data) => Data::Long(data.into_owned()),
            DataView::ULong64(data) => Data::ULong64(data.into_owned()),
            DataView::Long64(data) => Data::Long64(data.into_owned()),
            DataView::Float(data) => Data::Float(data.into_owned()),
            DataView::Double(data) => Data::Double(data.into_owned()),
            DataView::State(data) => Data::State(data.into_owned()),
            DataView::String(data) => Data::String(data.into_owned()),
            DataView::Struct(data) => Data::Struct(DataStruct::View(data.raw)),
        }
    }

}

impl<'a, T: 'a> DataLayoutView<'a, T> {

    pub fn kind(&self) -> DataLayoutKind {
        match self {
            DataLayoutView::Scalar(_) => 
                DataLayoutKind::Scalar,
            DataLayoutView::Spectrum(arr) => 
                DataLayoutKind::Spectrum(Some(arr.len(0))),
            DataLayoutView::Image(arr) => 
                DataLayoutKind::Image(Some((arr.len(0), arr.len(1)))),
        }
    }
    
    fn into_owned(self) -> DataLayout<'a, T> {
        match self {
            DataLayoutView::Scalar(v) => DataLayout::ScalarView(v),
            DataLayoutView::Spectrum(v) => DataLayout::SpectrumView(v),
            DataLayoutView::Image(v) => DataLayout::ImageView(v),
        }
    }

    pub fn as_slice(&self) -> &[T] {
        match *self {
            DataLayoutView::Scalar(v) => slice::from_ref(v),
            DataLayoutView::Spectrum(v) => v.as_raw_slice(),
            DataLayoutView::Image(v) => v.as_raw_slice(),
        }
    }

    pub fn as_scalar(&self) -> Option<&T> {
        match *self {
            DataLayoutView::Scalar(v) => Some(v),
            _ => None,
        }
    }

    pub fn as_spectrum(&self) -> Option<MdView<'_, T, 1>> {
        match *self {
            DataLayoutView::Spectrum(v) => Some(v),
            _ => return None
        }
    }

    pub fn as_image(&self) -> Option<MdView<'_, T, 2>> {
        match *self {
            DataLayoutView::Image(v) => Some(v),
            _ => return None
        }
    }

}

impl<'a> DataStructView<'a> {

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

    #[inline]
    pub fn get(&self, index: usize) -> Option<DataView<'_>> {
        self.raw.get(index).map(|data| data.as_view())
    }

}

// =========================== //
//          Data type          //
// =========================== //

/// A temporary pointer to a data stored inside a [`DataBuf`].
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DataKind {
    Unspecified,
    Void,
    Bool(DataLayoutKind),
    Octet(DataLayoutKind),
    UShort(DataLayoutKind),
    Short(DataLayoutKind),
    ULong(DataLayoutKind),
    Long(DataLayoutKind),
    ULong64(DataLayoutKind),
    Long64(DataLayoutKind),
    Float(DataLayoutKind),
    Double(DataLayoutKind),
    State(DataLayoutKind),
    String(DataLayoutKind),
    Enum(Cow<'static, [String]>),
    Struct(Cow<'static, [DataKind]>),
}

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum DataLayoutKind {
    Unspecified,
    Scalar,
    Spectrum(Option<usize>),
    Image(Option<(usize, usize)>),
}

// =========================== //
//       Data conversion       //
// =========================== //

/// A trait for converting some type into a data.
pub trait IntoData<'d>: Sized {

    /// The data kind expected to be produced. It's None by default if the type is 
    /// unspecified, but if it's some, the actually returned data kind should match!
    const DATA_KIND: DataKind = DataKind::Unspecified;

    /// Perform the conversion.
    fn into_data(self) -> Data<'d>;

}

/// A trait for producing a type from a raw data.
pub trait FromData<'d>: Sized {

    /// The data kind expected to be produced. It's None by default if the type is 
    /// unspecified, but if it's some, the data actually passed is expected to correspond
    /// by the implementor. The implementor is free to handle any data that is not 
    /// corresponding and return any error it wants anyway, for example if the data 
    /// format could not be accepted.
    const DATA_KIND: DataKind = DataKind::Unspecified;

    /// The type of error.
    type Err: Display + Debug;

    /// Perform the conversion.
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err>;

}

// =========================== //
//   Default implementations   //
// =========================== //

impl<'d> IntoData<'d> for Data<'d> {

    fn into_data(self) -> Data<'d> {
        self
    }

}

impl<'d: 'dd, 'dd> FromData<'d> for Data<'dd> {

    type Err = Infallible;

    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        Ok(data)
    }

}

impl IntoData<'static> for () {

    const DATA_KIND: DataKind = DataKind::Void;

    fn into_data(self) -> Data<'static> {
        Data::Void
    }

}

impl<'d> FromData<'d> for () {

    const DATA_KIND: DataKind = DataKind::Void;

    type Err = String;

    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        match data {
            Data::Void => Ok(()),
            _ => Err(format!("Data should be a Void to make a (): {data:?}")),
        }
    }

}

macro_rules! impl_data_for_tuple {
    ( @one: $comp:ident ) => { 1 };
    ( $( $comp:ident )* ) => {

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

            const DATA_KIND: DataKind = DataKind::Struct(Cow::Borrowed(&[
                $(<$comp>::DATA_KIND,)*
            ]));

            #[inline]
            fn into_data(self) -> Data<'d> {
                let ( $($comp,)* ): ( $($comp,)* ) = self;
                Data::Struct(DataStruct::Owned(Box::new([
                    $($comp.into_data(),)*
                ])))
            }

        }
        
        #[allow(unused_assignments, non_snake_case)]
        impl<'d $(, $comp)* > FromData<'d> for ( $($comp,)* )
        where
            $(
            $comp: FromData<'d>,
            )*
        {
            const DATA_KIND: DataKind = DataKind::Struct(Cow::Borrowed(&[
                $(<$comp>::DATA_KIND,)*
            ]));

            type Err = String;

            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                
                const EXPECTED_LEN: usize = 0 $( + impl_data_for_tuple!(@one: $comp) )*;

                let mut data = match data {
                    Data::Struct(DataStruct::Owned(data)) if data.len() == EXPECTED_LEN => data,
                    Data::Struct(DataStruct::View(data)) if data.len() == EXPECTED_LEN => data.to_vec().into_boxed_slice(),
                    _ => return Err(format!("Data should be a Struct of length {EXPECTED_LEN} to make a tuple: {data:?}")),
                };

                let [$($comp,)*] = &mut data[..] else { unreachable!() };
                let mut index = 0usize;

                Ok((
                    $(
                    match $comp::from_data(mem::replace($comp, Data::Void)) {
                        Ok(val) => {
                            index += 1;
                            val
                        }
                        Err(err) => return Err(format!("Data.{index}: {err}")),
                    },
                    )*
                ))

            }

        }

    };
}

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

macro_rules! impl_data_for_copy {
    ( $ty:ty, $data_variant:ident ) => {

        impl IntoData<'static> for $ty {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Scalar);
            #[inline]
            fn into_data(self) -> Data<'static> {
                Data::$data_variant(DataLayout::Scalar(self))
            }
        }

        impl<'d> IntoData<'d> for MdView<'d, $ty, 0> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Scalar);
            #[inline]
            fn into_data(self) -> Data<'d> {
                Data::$data_variant(DataLayout::ScalarView(self.get([]).unwrap()))
            }
        }

        impl<'d> IntoData<'d> for MdView<'d, $ty, 1> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Spectrum(None));
            #[inline]
            fn into_data(self) -> Data<'d> {
                Data::$data_variant(DataLayout::SpectrumView(self))
            }
        }

        impl<'d> IntoData<'d> for MdView<'d, $ty, 2> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Image(None));
            #[inline]
            fn into_data(self) -> Data<'d> {
                Data::$data_variant(DataLayout::ImageView(self))
            }
        }

        impl<'d> IntoData<'d> for &'d [$ty] {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Spectrum(None));
            #[inline]
            fn into_data(self) -> Data<'d> {
                MdView::from(self).into_data()
            }
        }

        impl<'d> FromData<'d> for $ty {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Scalar);
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                match data {
                    Data::$data_variant(DataLayout::Scalar(val)) => Ok(val),
                    Data::$data_variant(DataLayout::ScalarView(val)) => Ok(*val),
                    _ => Err(format!(concat!("Data should be a ", stringify!($data_variant), " scalar to make a ", stringify!($ty) ,": {:?}"), data)),
                }
            }
        }

        impl<'d: 'dd, 'dd> FromData<'d> for &'dd $ty {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Scalar);
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                match data {
                    Data::$data_variant(DataLayout::ScalarView(val)) => Ok(val),
                    _ => Err(format!(concat!("Data should be a ", stringify!($data_variant), " scalar to make a ", stringify!(&$ty) ,": {:?}"), data)),
                }
            }
        }

        impl<'d: 'dd, 'dd> FromData<'d> for &'dd [$ty] {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Spectrum(None));
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                match data {
                    Data::$data_variant(DataLayout::SpectrumView(val)) => Ok(val.as_raw_slice()),
                    _ => Err(format!(concat!("Data should be a ", stringify!($data_variant), " spectrum (view) to make a ", stringify!(&[$ty]) ,": {:?}"), data)),
                }
            }
        }

        impl<'d: 'dd, 'dd> FromData<'d> for MdView<'dd, $ty, 0> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Scalar);
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                match data {
                    Data::$data_variant(DataLayout::ScalarView(val)) => Ok(MdView::from(val)),
                    _ => Err(format!(concat!("Data should be a ", stringify!($data_variant), " scalar (view) to make a ", stringify!(MdView<$ty, 0>) ,": {:?}"), data)),
                }
            }
        }

        impl<'d: 'dd, 'dd> FromData<'d> for MdView<'dd, $ty, 1> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Spectrum(None));
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                match data {
                    Data::$data_variant(DataLayout::SpectrumView(val)) => Ok(MdView::from(val)),
                    _ => Err(format!(concat!("Data should be a ", stringify!($data_variant), " spectrum (view) to make a ", stringify!(MdView<$ty, 1>) ,": {:?}"), data)),
                }
            }
        }

        impl<'d: 'dd, 'dd> FromData<'d> for MdView<'dd, $ty, 2> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Image(None));
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                match data {
                    Data::$data_variant(DataLayout::ImageView(val)) => Ok(MdView::from(val)),
                    _ => Err(format!(concat!("Data should be a ", stringify!($data_variant), " image (view) to make a ", stringify!(MdView<$ty, 2>) ,": {:?}"), data)),
                }
            }
        }

        impl<'d> FromData<'d> for MdArray<$ty, 0> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Scalar);
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                match data {
                    Data::$data_variant(DataLayout::Scalar(val)) => Ok(MdArray::from(val)),
                    Data::$data_variant(DataLayout::ScalarView(val)) => Ok(MdView::from(val).to_owned()),
                    _ => Err(format!(concat!("Data should be a ", stringify!($data_variant), " scalar to make a ", stringify!(MdArray<$ty, 0>) ,": {:?}"), data)),
                }
            }
        }

        impl<'d> FromData<'d> for MdArray<$ty, 1> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Spectrum(None));
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                match data {
                    Data::$data_variant(DataLayout::Spectrum(val)) => Ok(val),
                    Data::$data_variant(DataLayout::SpectrumView(val)) => Ok(val.to_owned()),
                    _ => Err(format!(concat!("Data should be a ", stringify!($data_variant), " spectrum to make a ", stringify!(MdArray<$ty, 1>) ,": {:?}"), data)),
                }
            }
        }

        impl<'d> FromData<'d> for MdArray<$ty, 2> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Image(None));
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                match data {
                    Data::$data_variant(DataLayout::Image(val)) => Ok(val),
                    Data::$data_variant(DataLayout::ImageView(val)) => Ok(val.to_owned()),
                    _ => Err(format!(concat!("Data should be a ", stringify!($data_variant), " image to make a ", stringify!(MdArray<$ty, 2>) ,": {:?}"), data)),
                }
            }
        }

        impl<'d> FromData<'d> for Box<[$ty]> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Spectrum(None));
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                <MdArray<$ty, 1>>::from_data(data).map(|arr| arr.into_boxed_slice())
            }
        }

        impl<'d, const N: usize> FromData<'d> for Box<[$ty; N]> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Spectrum(Some(N)));
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                <Box<[$ty]>>::from_data(data)
                    .and_then(|arr| arr.try_into()
                        .map_err(|_| format!(concat!("Data should be a ", stringify!($data_variant), " spectrum of len {} to make a ", stringify!(Box<[$ty; N]>)), N)))
            }
        }

        impl<'d> FromData<'d> for Vec<$ty> {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Spectrum(None));
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                <Box<[$ty]>>::from_data(data).map(|arr| arr.to_vec())
            }
        }

        impl<'d, const N: usize> FromData<'d> for [$ty; N] {
            const DATA_KIND: DataKind = DataKind::$data_variant(DataLayoutKind::Spectrum(Some(N)));
            type Err = String;
            fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
                <Box<[$ty; N]>>::from_data(data).map(|arr| *arr)
            }
        }

    };
}

impl_data_for_copy!(bool, Bool);
impl_data_for_copy!(u16, UShort);
impl_data_for_copy!(i16, Short);
impl_data_for_copy!(u32, ULong);
impl_data_for_copy!(i32, Long);
impl_data_for_copy!(u64, ULong64);
impl_data_for_copy!(i64, Long64);
impl_data_for_copy!(f32, Float);
impl_data_for_copy!(f64, Double);
impl_data_for_copy!(DevState, State);

impl<'d> IntoData<'d> for Cow<'d, str> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Scalar);
    #[inline]
    fn into_data(self) -> Data<'d> {
        Data::String(DataLayout::Scalar(self))
    }
}

impl<'d> IntoData<'d> for &'d str {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Scalar);
    #[inline]
    fn into_data(self) -> Data<'d> {
        Cow::Borrowed(self).into_data()
    }
}

impl<'d> IntoData<'d> for &'d [Cow<'d, str>] {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    #[inline]
    fn into_data(self) -> Data<'d> {
        Data::String(DataLayout::SpectrumView(self.into()))
    }
}

impl<'d> IntoData<'d> for Vec<Cow<'d, str>> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    #[inline]
    fn into_data(self) -> Data<'d> {
        Data::String(DataLayout::Spectrum(self.into()))
    }
}

impl<'d> IntoData<'d> for Box<[Cow<'d, str>]> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    #[inline]
    fn into_data(self) -> Data<'d> {
        Data::String(DataLayout::Spectrum(self.into()))
    }
}

impl<'d, const N: usize> IntoData<'d> for [Cow<'d, str>; N] {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(Some(N)));
    #[inline]
    fn into_data(self) -> Data<'d> {
        Data::String(DataLayout::Spectrum(self.into()))
    }
}

impl<'d> IntoData<'d> for &'d [&'d str] {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    #[inline]
    fn into_data(self) -> Data<'d> {
        self.into_iter()
            .copied()
            .map(Cow::Borrowed)
            .collect::<Box<[_]>>()
            .into_data()
    }
}

impl<'d> IntoData<'d> for Vec<&'d str> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    #[inline]
    fn into_data(self) -> Data<'d> {
        self.into_iter()
            .map(Cow::Borrowed)
            .collect::<Box<[_]>>()
            .into_data()
    }
}

impl<'d> IntoData<'d> for Box<[&'d str]> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    #[inline]
    fn into_data(self) -> Data<'d> {
        self.into_vec().into_data()
    }
}

impl<'d, const N: usize> IntoData<'d> for [&'d str; N] {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(Some(N)));
    #[inline]
    fn into_data(self) -> Data<'d> {
        Box::<[&'d str]>::from(self).into_data()
    }
}

impl<'d: 'dd, 'dd> FromData<'d> for Cow<'dd, str> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Scalar);
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        match data {
            Data::String(DataLayout::Scalar(val)) => Ok(val),
            Data::String(DataLayout::ScalarView(val)) => Ok(val.clone()),
            _ => Err(format!("Data should be a String scalar to make a Cow<'d_, str>: {data:?}")),
        }
    }
}

impl<'d: 'dd, 'dd> FromData<'d> for MdArray<Cow<'dd, str>, 0> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Scalar);
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        Cow::<'dd, str>::from_data(data).map(|data| data.into())
    }
}

impl<'d: 'dd, 'dd> FromData<'d> for MdArray<Cow<'dd, str>, 1> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        match data {
            Data::String(DataLayout::Spectrum(val)) => Ok(val),
            Data::String(DataLayout::SpectrumView(val)) => Ok(val.to_owned()),
            _ => Err(format!("Data should be a String spectrum to make a MdArray<Cow<str>, 1>: {data:?}")),
        }
    }
}

impl<'d: 'dd, 'dd> FromData<'d> for MdArray<Cow<'dd, str>, 2> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Image(None));
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        match data {
            Data::String(DataLayout::Image(val)) => Ok(val),
            Data::String(DataLayout::ImageView(val)) => Ok(val.to_owned()),
            _ => Err(format!("Data should be a String image to make a MdArray<Cow<str>, 2>: {data:?}")),
        }
    }
}

impl<'d: 'dd, 'dd> FromData<'d> for Box<[Cow<'dd, str>]> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        <MdArray<Cow<'dd, str>, 1>>::from_data(data).map(MdArray::into_boxed_slice)
    }
}

impl<'d: 'dd, 'dd> FromData<'d> for Vec<Cow<'dd, str>> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        <Box<[Cow<'dd, str>]>>::from_data(data).map(<[Cow<'dd, str>]>::into_vec)
    }
}

impl<'d> FromData<'d> for String {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Scalar);
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        <Cow<'_, str>>::from_data(data).map(|v| v.into_owned())
    }
}

impl<'d> FromData<'d> for MdArray<String, 0> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Scalar);
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        String::from_data(data).map(|data| data.into())
    }
}

impl<'d> FromData<'d> for MdArray<String, 1> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        match data {
            Data::String(DataLayout::Spectrum(val)) => Ok(val.map(Cow::into_owned)),
            Data::String(DataLayout::SpectrumView(val)) => Ok(val.map_to_owned(ToString::to_string)),
            _ => Err(format!("Data should be a String spectrum to make a MdArray<String, 1>: {data:?}")),
        }
    }
}

impl<'d> FromData<'d> for MdArray<String, 2> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Image(None));
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        match data {
            Data::String(DataLayout::Image(val)) => Ok(val.map(Cow::into_owned)),
            Data::String(DataLayout::ImageView(val)) => Ok(val.map_to_owned(ToString::to_string)),
            _ => Err(format!("Data should be a String image to make a MdArray<String, 2>: {data:?}")),
        }
    }
}

impl<'d> FromData<'d> for Box<[String]> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        <MdArray<String, 1>>::from_data(data).map(MdArray::into_boxed_slice)
    }
}

impl<'d> FromData<'d> for Vec<String> {
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Spectrum(None));
    type Err = String;
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        <Box<[String]>>::from_data(data).map(<[String]>::into_vec)
    }
}

/// A wrapper type for string type to concatenate string spectrum into a single multiline
/// string, 
#[derive(Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Concat<T>(pub T);

impl<'d: 'dd, 'dd> FromData<'d> for Concat<Cow<'dd, str>> {

    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Scalar);
    type Err = String;
    
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        match data {
            Data::String(DataLayout::Scalar(val)) => Ok(Concat(val)),
            Data::String(DataLayout::ScalarView(val)) => Ok(Concat(val.clone())),
            Data::String(DataLayout::Spectrum(_)) |
            Data::String(DataLayout::SpectrumView(_)) => {
                
                let val = match data {
                    Data::String(DataLayout::Spectrum(ref val)) => val.as_view(),
                    Data::String(DataLayout::SpectrumView(val)) => val,
                    _ => unreachable!()
                };

                let concat = val.as_raw_slice()
                    .into_iter()
                    .fold(String::new(), |mut acc, item| {
                        if !acc.is_empty() {
                            acc.push('\n');
                        }
                        acc.push_str(&item);
                        acc
                    });

                Ok(Concat(Cow::Owned(concat)))

            }
            _ => Err(format!("Data should be a String scalar/spectrum to be concatenated into one string: {data:?}")),
        }
    }

}

impl<'d> FromData<'d> for Concat<String> {
    
    const DATA_KIND: DataKind = DataKind::String(DataLayoutKind::Scalar);
    type Err = String;
    
    fn from_data(data: Data<'d>) -> Result<Self, Self::Err> {
        <Concat<Cow<'_, str>>>::from_data(data).map(|Concat(s)| Concat(s.into_owned()))
    }

}
