use alloc::ffi::CString;

use alloc::vec::Vec;
use no_std_io::io::{Read, Seek, Write};

use crate::reader::Reader;
use crate::writer::Writer;
use crate::{ctx::*, DekuReader};
use crate::{DekuError, DekuWriter};

impl<Ctx: Copy> DekuWriter<Ctx> for CString
where
    u8: DekuWriter<Ctx>,
{
    fn to_writer<W: Write + Seek>(
        &self,
        writer: &mut Writer<W>,
        ctx: Ctx,
    ) -> Result<(), DekuError> {
        let bytes = self.as_bytes_with_nul();
        bytes.to_writer(writer, ctx)
    }
}

impl DekuReader<'_> for CString {
    fn from_reader_with_ctx<R: Read + Seek>(
        reader: &mut Reader<R>,
        _: (),
    ) -> Result<Self, DekuError> {
        let bytes =
            Vec::<u8>::from_reader_with_ctx(reader, (Limit::from(|b: &u8| *b == 0x00), ()))?;

        let value = CString::from_vec_with_nul(bytes).map_err(|e| {
            crate::deku_error!(
                DekuError::Parse,
                "Failed to convert Vec to CString",
                "{}",
                e
            )
        })?;

        Ok(value)
    }
}

impl<'a> DekuReader<'a, ByteSize> for CString
where
    u8: DekuReader<'a>,
{
    fn from_reader_with_ctx<R: Read + Seek>(
        reader: &mut Reader<R>,
        byte_size: ByteSize,
    ) -> Result<Self, DekuError> {
        let bytes = Vec::from_reader_with_ctx(reader, (Limit::from(byte_size.0), ()))?;

        let value = CString::from_vec_with_nul(bytes).map_err(|e| {
            crate::deku_error!(
                DekuError::Parse,
                "Failed to convert Vec to CString",
                "{}",
                e
            )
        })?;

        Ok(value)
    }
}

#[cfg(test)]
#[cfg(all(feature = "alloc", feature = "descriptive-errors"))]
mod tests {
    use alloc::vec;
    use no_std_io::io::Cursor;
    use rstest::rstest;

    use crate::reader::Reader;

    use super::*;

    #[rstest(input, len, expected, expected_rest,
        case(
            b"test\0",
            Some(5),
            CString::new("test").unwrap(),
            &[],
        ),
        case(
            b"test\0",
            None,
            CString::new("test").unwrap(),
            &[],
        ),
        case(
            b"test\0a",
            Some(5),
            CString::new("test").unwrap(),
            b"a",
        ),
        case(
            b"test\0a",
            None,
            CString::new("test").unwrap(),
            b"a",
        ),

        #[should_panic(expected = "Failed to convert Vec to CString")]
        case(
            b"test",
            Some(4),
            CString::new("test").unwrap(),
            b"a",
        ),

        #[should_panic(expected = "Incomplete(NeedSize { bits: 8 })")]
        case(b"test", Some(5), CString::new("test").unwrap(), &[]),

        #[should_panic(expected = "Incomplete(NeedSize { bits: 8 })")]
        case(b"test", None, CString::new("test").unwrap(), &[]),
    )]
    fn test_cstring_count(
        input: &[u8],
        len: Option<usize>,
        expected: CString,
        expected_rest: &[u8],
    ) {
        let mut cursor = Cursor::new(input);
        let mut reader = Reader::new(&mut cursor);
        let res_read = if let Some(len) = len {
            CString::from_reader_with_ctx(&mut reader, ByteSize(len)).unwrap()
        } else {
            CString::from_reader_with_ctx(&mut reader, ()).unwrap()
        };
        assert_eq!(expected, res_read);
        let mut buf = vec![];
        cursor.read_to_end(&mut buf).unwrap();
        assert_eq!(expected_rest, buf);

        let mut writer = Writer::new(Cursor::new(vec![]));
        res_read.to_writer(&mut writer, ()).unwrap();
        assert_eq!(
            vec![b't', b'e', b's', b't', b'\0'],
            writer.inner.into_inner()
        );
    }
}
