use std::{ffi::CString, ops::Deref, str::FromStr};

use rustmex::{
    char::CharArray,
    numeric::{Numeric, NumericArray},
    MatlabClass, MxArray,
};

use crate::error::{Error, MapMexError, SResult};

/// Trait to convert a Matlab type into a Rust type
pub trait IntoRustType<T> {
    fn into_rust(self) -> SResult<T>;
}

impl IntoRustType<CString> for MxArray {
    fn into_rust(self) -> SResult<CString> {
        Ok(CharArray::from_mx_array(self)
            .mexerr(Error::InvalidMatlabType("use a string instead".into()))?
            .get_cstring())
    }
}

impl IntoRustType<String> for MxArray {
    fn into_rust(self) -> SResult<String> {
        let c: CString = self.into_rust()?;
        Ok(c.into_string()?)
    }
}

impl IntoRustType<f64> for MxArray {
    fn into_rust(self) -> SResult<f64> {
        let out = Numeric::<f64, _>::from_mx_array(self)
            .mexerr(Error::InvalidMatlabType(
                "use a numerical type instead".into(),
            ))?
            .data()
            .first()
            .mexerr(Error::Parse)?
            .to_owned();
        Ok(out)
    }
}

impl IntoRustType<f32> for MxArray {
    fn into_rust(self) -> SResult<f32> {
        let out = Numeric::<f32, _>::from_mx_array(self)
            .mexerr(Error::InvalidMatlabType("cast to a single instead".into()))?
            .data()
            .first()
            .mexerr(Error::Parse)?
            .to_owned();
        Ok(out)
    }
}

impl IntoRustType<Vec<f64>> for MxArray {
    fn into_rust(self) -> SResult<Vec<f64>> {
        let out = Numeric::<f64, _>::from_mx_array(self)
            .mexerr(Error::InvalidMatlabType(
                "use a numerical type instead".into(),
            ))?
            .data()
            .to_owned();
        Ok(out)
    }
}

/// Trait to convert a Rust type into a Matlab type
pub trait IntoMatlabType {
    fn into_matlab(self) -> SResult<MxArray>;
}

impl IntoMatlabType for Vec<f64> {
    fn into_matlab(self) -> SResult<MxArray> {
        let len = self.len();
        Ok(Numeric::<f64, _>::new(self.into_boxed_slice(), &[len])
            .unwrap()
            .deref()
            .to_owned())
    }
}

impl IntoMatlabType for Vec<f32> {
    fn into_matlab(self) -> SResult<MxArray> {
        let len = self.len();
        Ok(Numeric::<f32, _>::new(self.into_boxed_slice(), &[len])
            .unwrap()
            .deref()
            .to_owned())
    }
}

/// Functions available to the user
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Mode {
    Open,
    Close,
    Read,
    Write,
}

impl FromStr for Mode {
    type Err = crate::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s {
            "Open" => Ok(Mode::Open),
            "Close" => Ok(Mode::Close),
            "Read" => Ok(Mode::Read),
            "Write" => Ok(Mode::Write),
            _ => Err(Error::InvalidMode),
        }
    }
}