use crate::error::Error;
use std::ffi::CStr;
use std::mem::MaybeUninit;

pub fn lib_czi_error(code: std::ffi::c_int) -> Result<(), Error> {
    match code {
        0 => Ok(()),
        1 => Err(Error::LibCziApiInvalidArgument),
        2 => Err(Error::LibCziApiInvalidHandle),
        3 => Err(Error::LibCziApiOutOfMemory),
        4 => Err(Error::LibCziApiIndexOutOfRange),
        20 => Err(Error::LibCziApiLockUnlockSemanticViolated),
        50 => Err(Error::LibCziApiUnspecifiedError),
        _ => Err(Error::LibCziApiUnknownError(code as usize)),
    }
}

/// container for some types of attachment data
pub enum AttachmentData {
    Float(Vec<f64>),
    Xml(String),
    Unknown(Vec<u8>),
}

impl AttachmentData {
    pub fn attachment_type(&self) -> &str {
        match self {
            Self::Float(_) => "float",
            Self::Xml(_) => "xml",
            Self::Unknown(_) => "unknown",
        }
    }

    pub(crate) fn from_float(raw: &[u8]) -> Result<Self, Error> {
        if raw.len() < 8 {
            Err(Error::MalformedData)
        } else {
            let number = u32::from_le_bytes(raw[4..8].try_into().unwrap()) as usize;
            if raw.len() < 8 * number + 8 {
                Err(Error::MalformedData)
            } else {
                let mut data = Vec::with_capacity(number);
                for i in 0..number {
                    data.push(f64::from_le_bytes(
                        raw[8 + 8 * i..16 + 8 * i].try_into().unwrap(),
                    ));
                }
                Ok(Self::Float(data))
            }
        }
    }

    pub(crate) fn from_xml(raw: &[u8]) -> Result<Self, Error> {
        Ok(Self::Xml(
            CStr::from_bytes_until_nul(raw)?
                .to_string_lossy()
                .to_string(),
        ))
    }

    pub fn try_into_float(&self) -> Result<Vec<f64>, Error> {
        if let AttachmentData::Float(data) = self {
            Ok(data.clone())
        } else {
            Err(Error::InvalidAttachmentType(
                self.attachment_type().to_string(),
            ))
        }
    }

    pub fn try_into_xml(&self) -> Result<String, Error> {
        if let AttachmentData::Xml(data) = self {
            Ok(data.clone())
        } else {
            Err(Error::InvalidAttachmentType(
                self.attachment_type().to_string(),
            ))
        }
    }
}

#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
pub enum Dimension {
    /// The Z-dimension.
    Z = 1,
    /// The C-dimension ("channel").
    C = 2,
    /// The T-dimension ("time").
    T = 3,
    /// The R-dimension ("rotation").
    R = 4,
    /// The S-dimension ("scene").
    S = 5,
    /// The I-dimension ("illumination").
    I = 6,
    /// The H-dimension ("phase").
    H = 7,
    /// The V-dimension ("view").
    V = 8,
    /// The B-dimension ("block") - its use is deprecated.
    B = 9,
}

impl Dimension {
    pub fn vec_from_bitflags(bit_flags: u32) -> Vec<Dimension> {
        let mut bit_flags = bit_flags;
        let mut dimensions = Vec::with_capacity(9);
        for i in 1..=9 {
            if (bit_flags & 1) > 0 {
                dimensions.push(Dimension::try_from(i).expect("i must be 0 <= i <= 9"));
            }
            bit_flags >>= 1;
        }
        dimensions
    }

    pub fn is_valid(&self, v: u32) -> bool {
        v & (*self as u32).pow(2) > 0
    }
}

impl TryFrom<i32> for Dimension {
    type Error = Error;

    fn try_from(dimension: i32) -> Result<Self, Error> {
        match dimension {
            1 => Ok(Dimension::Z),
            2 => Ok(Dimension::C),
            3 => Ok(Dimension::T),
            4 => Ok(Dimension::R),
            5 => Ok(Dimension::S),
            6 => Ok(Dimension::I),
            7 => Ok(Dimension::H),
            8 => Ok(Dimension::V),
            9 => Ok(Dimension::B),
            _ => Err(Error::UnknownDimension(dimension.to_string())),
        }
    }
}

/// enum for SubBlock.get_raw_data
#[derive(Clone, Debug)]
pub enum RawDataType {
    Data = 0,
    Metadata = 1,
}

impl TryFrom<i32> for RawDataType {
    type Error = Error;

    fn try_from(raw_data_type: i32) -> Result<Self, Error> {
        match raw_data_type {
            0 => Ok(RawDataType::Data),
            1 => Ok(RawDataType::Metadata),
            _ => Err(Error::UnknownDataType(raw_data_type)),
        }
    }
}

/// pixel type
#[derive(Clone, Debug)]
pub enum PixelType {
    Gray8 = 0,
    Gray16 = 1,
    Gray32Float = 2,
    Bgr24 = 3,
    Bgr48 = 4,
    Bgr96Float = 8,
    Bgra32 = 9,
    Gray64ComplexFloat = 10,
    Bgr192ComplexFloat = 11,
    Gray32 = 12,
    Gray64Float = 13,
}

impl TryFrom<i32> for PixelType {
    type Error = Error;

    fn try_from(pixel_type: i32) -> Result<Self, Error> {
        match pixel_type {
            0 => Ok(PixelType::Gray8),
            1 => Ok(PixelType::Gray16),
            2 => Ok(PixelType::Gray32Float),
            3 => Ok(PixelType::Bgr24),
            4 => Ok(PixelType::Bgr48),
            8 => Ok(PixelType::Bgr96Float),
            9 => Ok(PixelType::Bgra32),
            10 => Ok(PixelType::Gray64ComplexFloat),
            11 => Ok(PixelType::Bgr192ComplexFloat),
            12 => Ok(PixelType::Gray32),
            13 => Ok(PixelType::Gray64Float),
            _ => Err(Error::UnknownPixelType(pixel_type)),
        }
    }
}

pub trait Ptr {
    type Pointer;

    unsafe fn assume_init(ptr: MaybeUninit<Self::Pointer>) -> Self;

    fn as_mut_ptr(&self) -> *mut Self::Pointer
    where
        Self: Sized;

    fn as_ptr(&self) -> *const Self::Pointer
    where
        Self: Sized;
}
