use std::fmt::{Display, Formatter, Result};
use std::result;
use std::str::FromStr;
use std::string::ToString;
use nom::{
    branch::alt,
    bytes::streaming::{tag, take_until},
    combinator::{complete, map},
    multi::many0,
    sequence::{preceded, tuple},
    IResult,
};
use crate::smtp::error::Error;
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(
    feature = "serde-impls",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub enum Severity {
    
    PositiveCompletion = 2,
    
    PositiveIntermediate = 3,
    
    TransientNegativeCompletion = 4,
    
    PermanentNegativeCompletion = 5,
}
impl Display for Severity {
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(f, "{}", *self as u8)
    }
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(
    feature = "serde-impls",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub enum Category {
    
    Syntax = 0,
    
    Information = 1,
    
    Connections = 2,
    
    Unspecified3 = 3,
    
    Unspecified4 = 4,
    
    MailSystem = 5,
}
impl Display for Category {
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(f, "{}", *self as u8)
    }
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(
    feature = "serde-impls",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub enum Detail {
    #[allow(missing_docs)]
    Zero = 0,
    #[allow(missing_docs)]
    One = 1,
    #[allow(missing_docs)]
    Two = 2,
    #[allow(missing_docs)]
    Three = 3,
    #[allow(missing_docs)]
    Four = 4,
    #[allow(missing_docs)]
    Five = 5,
    #[allow(missing_docs)]
    Six = 6,
    #[allow(missing_docs)]
    Seven = 7,
    #[allow(missing_docs)]
    Eight = 8,
    #[allow(missing_docs)]
    Nine = 9,
}
impl Display for Detail {
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(f, "{}", *self as u8)
    }
}
#[derive(PartialEq, Eq, Copy, Clone, Debug)]
#[cfg_attr(
    feature = "serde-impls",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub struct Code {
    
    pub severity: Severity,
    
    pub category: Category,
    
    pub detail: Detail,
}
impl Display for Code {
    fn fmt(&self, f: &mut Formatter) -> Result {
        write!(f, "{}{}{}", self.severity, self.category, self.detail)
    }
}
impl Code {
    
    pub fn new(severity: Severity, category: Category, detail: Detail) -> Code {
        Code {
            severity,
            category,
            detail,
        }
    }
}
#[derive(PartialEq, Eq, Clone, Debug)]
#[cfg_attr(
    feature = "serde-impls",
    derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
pub struct Response {
    
    pub code: Code,
    
    
    pub message: Vec<String>,
}
impl FromStr for Response {
    type Err = Error;
    fn from_str(s: &str) -> result::Result<Response, Error> {
        parse_response(s).map(|(_, r)| r).map_err(|e| e.into())
    }
}
impl Response {
    
    pub fn new(code: Code, message: Vec<String>) -> Response {
        Response { code, message }
    }
    
    pub fn is_positive(&self) -> bool {
        matches!(
            self.code.severity,
            Severity::PositiveCompletion | Severity::PositiveIntermediate
        )
    }
    
    pub fn has_code(&self, code: u16) -> bool {
        self.code.to_string() == code.to_string()
    }
    
    pub fn first_word(&self) -> Option<&str> {
        self.message
            .first()
            .and_then(|line| line.split_whitespace().next())
    }
    
    pub fn first_line(&self) -> Option<&str> {
        self.message.first().map(String::as_str)
    }
}
fn parse_code(i: &str) -> IResult<&str, Code> {
    let (i, severity) = parse_severity(i)?;
    let (i, category) = parse_category(i)?;
    let (i, detail) = parse_detail(i)?;
    Ok((
        i,
        Code {
            severity,
            category,
            detail,
        },
    ))
}
fn parse_severity(i: &str) -> IResult<&str, Severity> {
    alt((
        map(tag("2"), |_| Severity::PositiveCompletion),
        map(tag("3"), |_| Severity::PositiveIntermediate),
        map(tag("4"), |_| Severity::TransientNegativeCompletion),
        map(tag("5"), |_| Severity::PermanentNegativeCompletion),
    ))(i)
}
fn parse_category(i: &str) -> IResult<&str, Category> {
    alt((
        map(tag("0"), |_| Category::Syntax),
        map(tag("1"), |_| Category::Information),
        map(tag("2"), |_| Category::Connections),
        map(tag("3"), |_| Category::Unspecified3),
        map(tag("4"), |_| Category::Unspecified4),
        map(tag("5"), |_| Category::MailSystem),
    ))(i)
}
fn parse_detail(i: &str) -> IResult<&str, Detail> {
    alt((
        map(tag("0"), |_| Detail::Zero),
        map(tag("1"), |_| Detail::One),
        map(tag("2"), |_| Detail::Two),
        map(tag("3"), |_| Detail::Three),
        map(tag("4"), |_| Detail::Four),
        map(tag("5"), |_| Detail::Five),
        map(tag("6"), |_| Detail::Six),
        map(tag("7"), |_| Detail::Seven),
        map(tag("8"), |_| Detail::Eight),
        map(tag("9"), |_| Detail::Nine),
    ))(i)
}
pub(crate) fn parse_response(i: &str) -> IResult<&str, Response> {
    let (i, lines) = many0(tuple((
        parse_code,
        preceded(tag("-"), take_until("\r\n")),
        tag("\r\n"),
    )))(i)?;
    let (i, (last_code, last_line)) =
        tuple((parse_code, preceded(tag(" "), take_until("\r\n"))))(i)?;
    let (i, _) = complete(tag("\r\n"))(i)?;
    
    if !lines.iter().all(|&(ref code, _, _)| *code == last_code) {
        return Err(nom::Err::Failure(("", nom::error::ErrorKind::Not)));
    }
    
    let mut lines: Vec<&str> = lines
        .into_iter()
        .map(|(_, text, _)| text)
        .collect::<Vec<_>>();
    lines.push(last_line);
    Ok((
        i,
        Response {
            code: last_code,
            message: lines
                .iter()
                .map(ToString::to_string)
                .collect::<Vec<String>>(),
        },
    ))
}
#[cfg(test)]
mod test {
    use super::{parse_response, Category, Code, Detail, Response, Severity};
    #[test]
    fn test_severity_fmt() {
        assert_eq!(format!("{}", Severity::PositiveCompletion), "2");
    }
    #[test]
    fn test_category_fmt() {
        assert_eq!(format!("{}", Category::Unspecified4), "4");
    }
    #[test]
    fn test_code_new() {
        assert_eq!(
            Code::new(
                Severity::TransientNegativeCompletion,
                Category::Connections,
                Detail::Zero,
            ),
            Code {
                severity: Severity::TransientNegativeCompletion,
                category: Category::Connections,
                detail: Detail::Zero,
            }
        );
    }
    #[test]
    fn test_code_display() {
        let code = Code {
            severity: Severity::TransientNegativeCompletion,
            category: Category::Connections,
            detail: Detail::One,
        };
        assert_eq!(code.to_string(), "421");
    }
    #[test]
    fn test_response_from_str() {
        let raw_response = "250-me\r\n250-8BITMIME\r\n250-SIZE 42\r\n250 AUTH PLAIN CRAM-MD5\r\n";
        assert_eq!(
            raw_response.parse::<Response>().unwrap(),
            Response {
                code: Code {
                    severity: Severity::PositiveCompletion,
                    category: Category::MailSystem,
                    detail: Detail::Zero,
                },
                message: vec![
                    "me".to_string(),
                    "8BITMIME".to_string(),
                    "SIZE 42".to_string(),
                    "AUTH PLAIN CRAM-MD5".to_string(),
                ],
            }
        );
        let wrong_code = "2506-me\r\n250-8BITMIME\r\n250-SIZE 42\r\n250 AUTH PLAIN CRAM-MD5\r\n";
        assert!(wrong_code.parse::<Response>().is_err());
        let wrong_end = "250-me\r\n250-8BITMIME\r\n250-SIZE 42\r\n250-AUTH PLAIN CRAM-MD5\r\n";
        assert!(wrong_end.parse::<Response>().is_err());
    }
    #[test]
    fn test_response_incomplete() {
        let raw_response = "250-smtp.example.org\r\n";
        let res = parse_response(raw_response);
        match res {
            Err(nom::Err::Incomplete(_)) => {}
            _ => panic!("Expected incomplete response, got {:?}", res),
        }
    }
    #[test]
    fn test_response_is_positive() {
        assert!(Response::new(
            Code {
                severity: Severity::PositiveCompletion,
                category: Category::MailSystem,
                detail: Detail::Zero,
            },
            vec![
                "me".to_string(),
                "8BITMIME".to_string(),
                "SIZE 42".to_string(),
            ],
        )
        .is_positive());
        assert!(!Response::new(
            Code {
                severity: Severity::TransientNegativeCompletion,
                category: Category::MailSystem,
                detail: Detail::Zero,
            },
            vec![
                "me".to_string(),
                "8BITMIME".to_string(),
                "SIZE 42".to_string(),
            ],
        )
        .is_positive());
    }
    #[test]
    fn test_response_has_code() {
        assert!(Response::new(
            Code {
                severity: Severity::TransientNegativeCompletion,
                category: Category::MailSystem,
                detail: Detail::One,
            },
            vec![
                "me".to_string(),
                "8BITMIME".to_string(),
                "SIZE 42".to_string(),
            ],
        )
        .has_code(451));
        assert!(!Response::new(
            Code {
                severity: Severity::TransientNegativeCompletion,
                category: Category::MailSystem,
                detail: Detail::One,
            },
            vec![
                "me".to_string(),
                "8BITMIME".to_string(),
                "SIZE 42".to_string(),
            ],
        )
        .has_code(251));
    }
    #[test]
    fn test_response_first_word() {
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec![
                    "me".to_string(),
                    "8BITMIME".to_string(),
                    "SIZE 42".to_string(),
                ],
            )
            .first_word(),
            Some("me")
        );
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec![
                    "me mo".to_string(),
                    "8BITMIME".to_string(),
                    "SIZE 42".to_string(),
                ],
            )
            .first_word(),
            Some("me")
        );
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec![],
            )
            .first_word(),
            None
        );
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec![" ".to_string()],
            )
            .first_word(),
            None
        );
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec!["  ".to_string()],
            )
            .first_word(),
            None
        );
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec!["".to_string()],
            )
            .first_word(),
            None
        );
    }
    #[test]
    fn test_response_first_line() {
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec![
                    "me".to_string(),
                    "8BITMIME".to_string(),
                    "SIZE 42".to_string(),
                ],
            )
            .first_line(),
            Some("me")
        );
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec![
                    "me mo".to_string(),
                    "8BITMIME".to_string(),
                    "SIZE 42".to_string(),
                ],
            )
            .first_line(),
            Some("me mo")
        );
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec![],
            )
            .first_line(),
            None
        );
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec![" ".to_string()],
            )
            .first_line(),
            Some(" ")
        );
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec!["  ".to_string()],
            )
            .first_line(),
            Some("  ")
        );
        assert_eq!(
            Response::new(
                Code {
                    severity: Severity::TransientNegativeCompletion,
                    category: Category::MailSystem,
                    detail: Detail::One,
                },
                vec!["".to_string()],
            )
            .first_line(),
            Some("")
        );
    }
}