use crate::v4::error::Result;
use std::{
    cmp::Ordering,
    ffi::{CStr, CString},
};

/// A null-terminated string that always maps to the 64 bytes assigned to it in [Message](crate::v4::Message).
///
/// You can get the string value back out with `to_string()`:
/// ```
/// # use toe_beans::v4::SName;
/// # use std::ffi::CString;
/// # let sname = SName::new(c"Example").unwrap();
/// let name: CString = sname.to_string();
/// ```
#[derive(Debug, PartialEq)]
pub struct SName([u8; 64]);

impl SName {
    /// Construct a valid sname field.
    /// `name` must not be more 64 bytes.
    pub fn new(name: &CStr) -> Result<Self> {
        let bytes = name.to_bytes_with_nul();
        match bytes.len().cmp(&64) {
            Ordering::Less => {
                let mut padding = [0u8; 64];
                bytes
                    .iter()
                    .enumerate()
                    .for_each(|(i, byte)| padding[i] = *byte);
                Ok(Self(padding))
            }
            Ordering::Equal => Ok(Self(bytes.try_into().unwrap())),
            Ordering::Greater => Err("SName does not fit in a 64 byte array"),
        }
    }

    /// Converts an array slice of exactly 64 byte length
    /// into a SName without performing any checks (such as length or null-termination).
    ///
    /// Panics if slice is not 64 bytes.
    ///
    /// Useful if you are handling the return of `parse_sname`.
    pub fn from_slice_unchecked(name: &[u8]) -> Self {
        Self(name.try_into().unwrap())
    }

    /// Create a SName by passing the inner type directly.
    pub fn from_array(name: [u8; 64]) -> Self {
        Self(name)
    }

    /// A SName that contains all zero bytes.
    ///
    /// This is more performant than `SName::new(c"")`
    pub const EMPTY: Self = Self([0; 64]);

    /// Convert the internal array to a null-terminated
    /// string representation with padding removed.
    pub fn to_string(&self) -> CString {
        CStr::from_bytes_until_nul(&self.0).unwrap().to_owned()
    }

    /// Get the length of SName.
    ///
    /// Note that this returns the length of the inner array
    /// which is always 64. To get the length of its string
    /// representation call `sname.to_string().count_bytes()`.
    pub fn len(&self) -> usize {
        64
    }
}

impl From<&SName> for [u8; 64] {
    fn from(sname: &SName) -> Self {
        sname.0
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_new_sname_below_length() {
        let sname = SName::new(c"12345");
        assert!(sname.is_ok());
    }

    #[test]
    fn test_new_sname_at_length() {
        // 63 bytes plus the NUL byte
        let sname = SName::new(c"123451234512345123451234512345123451234512345123451234512345123");
        assert!(sname.is_ok());
    }

    #[test]
    fn test_new_sname_above_length() {
        let sname = SName::new(c"1234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345123451234512345");
        assert!(sname.is_err());
    }

    #[test]
    fn test_empty() {
        let sname = SName::EMPTY;
        assert_eq!(sname.0, [0; 64]);
    }
}
