use std::{borrow::Cow, num::NonZeroU32, str::from_utf8};
#[cfg(not(feature = "quirk_crlf_relaxed"))]
use abnf_core::streaming::crlf;
#[cfg(feature = "quirk_crlf_relaxed")]
use abnf_core::streaming::crlf_relaxed as crlf;
use abnf_core::{is_alpha, is_digit, streaming::dquote};
use base64::{engine::general_purpose::STANDARD as _base64, Engine};
pub use imap_types::core::*;
#[cfg(feature = "ext_literal")]
use nom::character::streaming::char;
use nom::{
    branch::alt,
    bytes::streaming::{escaped, tag, tag_no_case, take, take_while, take_while1, take_while_m_n},
    character::streaming::{digit1, one_of},
    combinator::{map, map_res, opt, recognize},
    sequence::{delimited, terminated, tuple},
};
use crate::{
    codec::{IMAPErrorKind, IMAPParseError, IMAPResult},
    utils::{
        indicators::{is_astring_char, is_atom_char, is_quoted_specials, is_text_char},
        unescape_quoted,
    },
};
pub(crate) fn number(input: &[u8]) -> IMAPResult<&[u8], u32> {
    map_res(
        map(digit1, |val| from_utf8(val).unwrap()),
        str::parse::<u32>,
    )(input)
}
#[cfg(feature = "ext_quota")]
#[cfg_attr(docsrs, doc(cfg(feature = "ext_quota")))]
pub(crate) fn number64(input: &[u8]) -> IMAPResult<&[u8], u64> {
    map_res(
        map(digit1, |val| from_utf8(val).unwrap()),
        str::parse::<u64>,
    )(input)
}
pub(crate) fn nz_number(input: &[u8]) -> IMAPResult<&[u8], NonZeroU32> {
    map_res(number, NonZeroU32::try_from)(input)
}
pub(crate) fn string(input: &[u8]) -> IMAPResult<&[u8], IString> {
    alt((map(quoted, IString::Quoted), map(literal, IString::Literal)))(input)
}
pub(crate) fn quoted(input: &[u8]) -> IMAPResult<&[u8], Quoted> {
    let mut parser = tuple((
        dquote,
        map(
            escaped(
                take_while1(is_any_text_char_except_quoted_specials),
                '\\',
                one_of("\\\""),
            ),
            |val| from_utf8(val).unwrap(),
        ),
        dquote,
    ));
    let (remaining, (_, quoted, _)) = parser(input)?;
    Ok((remaining, Quoted::unvalidated(unescape_quoted(quoted))))
}
pub(crate) fn quoted_char(input: &[u8]) -> IMAPResult<&[u8], QuotedChar> {
    map(
        alt((
            map(
                take_while_m_n(1, 1, is_any_text_char_except_quoted_specials),
                |bytes: &[u8]| {
                    assert_eq!(bytes.len(), 1);
                    bytes[0] as char
                },
            ),
            map(
                tuple((tag("\\"), take_while_m_n(1, 1, is_quoted_specials))),
                |(_, bytes): (_, &[u8])| {
                    assert_eq!(bytes.len(), 1);
                    bytes[0] as char
                },
            ),
        )),
        QuotedChar::unvalidated,
    )(input)
}
pub(crate) fn is_any_text_char_except_quoted_specials(byte: u8) -> bool {
    is_text_char(byte) && !is_quoted_specials(byte)
}
pub(crate) fn literal(input: &[u8]) -> IMAPResult<&[u8], Literal> {
    #[cfg(not(feature = "ext_literal"))]
    let (remaining, length) = terminated(delimited(tag(b"{"), number, tag(b"}")), crlf)(input)?;
    #[cfg(feature = "ext_literal")]
    let (remaining, (length, mode)) = terminated(
        delimited(
            tag(b"{"),
            tuple((
                number,
                map(opt(char('+')), |i| {
                    i.map(|_| LiteralMode::NonSync).unwrap_or(LiteralMode::Sync)
                }),
            )),
            tag(b"}"),
        ),
        crlf,
    )(input)?;
    if remaining.is_empty() {
        return Err(nom::Err::Failure(IMAPParseError {
            input,
            kind: IMAPErrorKind::Literal {
                length,
                #[cfg(feature = "ext_literal")]
                mode,
            },
        }));
    }
    let (remaining, data) = take(length)(remaining)?;
    match Literal::try_from(data) {
        #[cfg(not(feature = "ext_literal"))]
        Ok(literal) => Ok((remaining, literal)),
        #[cfg(feature = "ext_literal")]
        Ok(mut literal) => {
            literal.set_mode(mode);
            Ok((remaining, literal))
        }
        Err(_) => Err(nom::Err::Failure(IMAPParseError {
            input,
            kind: IMAPErrorKind::LiteralContainsNull,
        })),
    }
}
pub(crate) fn astring(input: &[u8]) -> IMAPResult<&[u8], AString> {
    alt((
        map(take_while1(is_astring_char), |bytes: &[u8]| {
            AString::Atom(AtomExt::unvalidated(Cow::Borrowed(
                std::str::from_utf8(bytes).unwrap(),
            )))
        }),
        map(string, AString::String),
    ))(input)
}
pub(crate) fn atom(input: &[u8]) -> IMAPResult<&[u8], Atom> {
    let parser = take_while1(is_atom_char);
    let (remaining, parsed_atom) = parser(input)?;
    Ok((
        remaining,
        Atom::unvalidated(from_utf8(parsed_atom).unwrap()),
    ))
}
pub(crate) fn nstring(input: &[u8]) -> IMAPResult<&[u8], NString> {
    alt((
        map(string, |item| NString(Some(item))),
        map(nil, |_| NString(None)),
    ))(input)
}
#[inline]
pub(crate) fn nil(input: &[u8]) -> IMAPResult<&[u8], &[u8]> {
    tag_no_case(b"NIL")(input)
}
pub(crate) fn text(input: &[u8]) -> IMAPResult<&[u8], Text> {
    map(take_while1(is_text_char), |bytes|
        Text::unvalidated(from_utf8(bytes).unwrap()))(input)
}
pub(crate) fn base64(input: &[u8]) -> IMAPResult<&[u8], Vec<u8>> {
    map_res(
        recognize(tuple((
            take_while(is_base64_char),
            opt(alt((tag("=="), tag("=")))),
        ))),
        |input| _base64.decode(input),
    )(input)
}
pub(crate) fn is_base64_char(i: u8) -> bool {
    is_alpha(i) || is_digit(i) || i == b'+' || i == b'/'
}
pub(crate) fn charset(input: &[u8]) -> IMAPResult<&[u8], Charset> {
    alt((map(atom, Charset::Atom), map(quoted, Charset::Quoted)))(input)
}
pub(crate) fn tag_imap(input: &[u8]) -> IMAPResult<&[u8], Tag> {
    map(take_while1(|b| is_astring_char(b) && b != b'+'), |val| {
        Tag::unvalidated(from_utf8(val).unwrap())
    })(input)
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::codec::Encode;
    #[test]
    fn test_atom() {
        assert!(atom(b" ").is_err());
        assert!(atom(b"").is_err());
        let (rem, val) = atom(b"a(").unwrap();
        assert_eq!(val, "a".try_into().unwrap());
        assert_eq!(rem, b"(");
        let (rem, val) = atom(b"xxx yyy").unwrap();
        assert_eq!(val, "xxx".try_into().unwrap());
        assert_eq!(rem, b" yyy");
    }
    #[test]
    fn test_quoted() {
        let (rem, val) = quoted(br#""Hello"???"#).unwrap();
        assert_eq!(rem, b"???");
        assert_eq!(val, Quoted::try_from("Hello").unwrap());
        assert!(quoted(br#""Hello \" "???"#).is_ok());
        assert!(quoted(br#""Hello \\ "???"#).is_ok());
        assert!(quoted(br#""Hello \a "???"#).is_err());
        assert!(quoted(br#""Hello \z "???"#).is_err());
        assert!(quoted(br#""Hello \? "???"#).is_err());
        let (rem, val) = quoted(br#""Hello \"World\""???"#).unwrap();
        assert_eq!(rem, br#"???"#);
        assert_eq!(val, Quoted::try_from("Hello \"World\"").unwrap());
        assert!(matches!(quoted(br#""#), Err(nom::Err::Incomplete(_))));
        assert!(matches!(quoted(br#""\"#), Err(nom::Err::Incomplete(_))));
        assert!(matches!(
            quoted(br#""Hello "#),
            Err(nom::Err::Incomplete(_))
        ));
        assert!(matches!(quoted(br#"\"#), Err(nom::Err::Error(_))));
    }
    #[test]
    fn test_quoted_char() {
        let (rem, val) = quoted_char(b"\\\"xxx").unwrap();
        assert_eq!(rem, b"xxx");
        assert_eq!(val, QuotedChar::try_from('"').unwrap());
    }
    #[test]
    fn test_number() {
        assert!(number(b"").is_err());
        assert!(number(b"?").is_err());
        assert!(number(b"0?").is_ok());
        assert!(number(b"55?").is_ok());
        assert!(number(b"999?").is_ok());
    }
    #[test]
    fn test_nz_number() {
        assert!(number(b"").is_err());
        assert!(number(b"?").is_err());
        assert!(nz_number(b"0?").is_err());
        assert!(nz_number(b"55?").is_ok());
        assert!(nz_number(b"999?").is_ok());
    }
    #[test]
    fn test_literal() {
        assert!(literal(b"{3}\r\n123").is_ok());
        assert!(literal(b"{3}\r\n1\x003").is_err());
        let (rem, val) = literal(b"{3}\r\n123xxx").unwrap();
        assert_eq!(rem, b"xxx");
        assert_eq!(val, Literal::try_from(b"123".as_slice()).unwrap());
    }
    #[test]
    fn test_nil() {
        assert!(nil(b"nil").is_ok());
        assert!(nil(b"nil ").is_ok());
        assert!(nil(b" nil").is_err());
        assert!(nil(b"null").is_err());
        let (rem, _) = nil(b"nilxxx").unwrap();
        assert_eq!(rem, b"xxx");
    }
    #[test]
    fn test_encode_charset() {
        let tests = [
            ("bengali", "bengali"),
            ("\"simple\" english", r#""\"simple\" english""#),
            ("", "\"\""),
            ("\"", "\"\\\"\""),
            ("\\", "\"\\\\\""),
        ];
        for (from, expected) in tests.iter() {
            let cs = Charset::try_from(*from).unwrap();
            println!("{:?}", cs);
            let out = cs.encode().dump();
            assert_eq!(from_utf8(&out).unwrap(), *expected);
        }
        assert!(Charset::try_from("\r").is_err());
        assert!(Charset::try_from("\n").is_err());
        assert!(Charset::try_from("¹").is_err());
        assert!(Charset::try_from("²").is_err());
        assert!(Charset::try_from("\x00").is_err());
    }
}