use super::super::Error;

pub type AString<const SIZE: usize> = CDString::<AStringValidator, SIZE>;
pub type DString<const SIZE: usize> = CDString::<DStringValidator, SIZE>;

pub type DynAString = CDString<AStringValidator, 0>;
pub type DynDString = CDString<DStringValidator, 0>;

pub trait CDStringValidator {
    fn is_valid_ascii(value: &[u8]) -> Result<(), Error>;
}

pub struct AStringValidator {}
pub struct DStringValidator {}

pub struct CDString<F:CDStringValidator, const SIZE: usize> {
    value: [u8; SIZE],
    _d:    std::marker::PhantomData<F>
}

impl CDStringValidator for AStringValidator {
    fn is_valid_ascii(value: &[u8]) -> Result<(), Error> {
        for chr in value {
            let chr = *chr as char;

            if !matches!(chr, '0'..='9' | 'A'..='Z' | ' ' | '!' | '"' | '%' | '&' | '\'' | '(' | ')' | '*' | '+' | ',' | '-' | '.' | '/' | ':' | ';' | '<' | '=' | '>' | '?' | '_') {
                return Err(Error::GenericError(format!("Error parsing \"{}\": '{}' not a valid a-character", String::from_utf8_lossy(value), chr)));
            }
        }
        Ok(())
    }
}

impl CDStringValidator for DStringValidator {
    fn is_valid_ascii(value: &[u8]) -> Result<(), Error> {
        for chr in value {
            let chr = *chr as char;    

            if !matches!(chr, '\x00' | '\x01' | '0'..='9' | 'A'..='Z' | '_') {
                return Err(Error::GenericError(format!("Error parsing \"{}\": '{}' not a valid d-character", String::from_utf8_lossy(value), chr)));
            }
        }
        Ok(())
    }
}

impl<F:CDStringValidator, const SIZE: usize> CDString<F, SIZE> {
    pub fn from_str(string: &str) -> Result<CDString<F, SIZE>, Error> {
        let mut cd_str = CDString::default();

        CDString::<F, 0>::to_buffer(string, &mut cd_str.value)?;
        Ok(cd_str)
    }

    pub fn as_raw(&self) -> &[u8] {
        &self.value
    }
}

impl<F:CDStringValidator> CDString<F, 0> {
    pub fn to_buffer(string: &str, buffer: &mut [u8]) -> Result<(), Error> {
        if !string.is_ascii() {
            return Err(Error::GenericError("String has to be ASCII encoded".to_string()));
        }

        if string.len() > buffer.len() {
            return Err(Error::GenericError(format!("String has {} characters but buffer can only hold {}", string.len(), buffer.len())));
        }

        F::is_valid_ascii(string.as_bytes())?;

        let bytes = string.as_bytes();
        for i in 0..bytes.len() {
            buffer[i] = bytes[i];
        }

        Ok(())
    }
}

impl<F:CDStringValidator, const SIZE: usize> Default for CDString<F, SIZE> {
    fn default() -> Self {
        CDString{value: [' ' as u8; SIZE], _d: std::marker::PhantomData}
    }
}