mod empty;
mod end;
mod include;
mod start;
pub use empty::EmptyLine;
pub use end::EndLine;
pub use include::IncludeLine;
pub use start::StartLine;
use crate::{wr, ParseContainer};
use nom::branch::alt;
use nom::bytes::complete::take_till;
use nom::character::complete::line_ending;
use nom::combinator::eof;
use nom::sequence::tuple;
use nom::IResult;
#[derive(Clone, Debug)]
pub struct PlantUmlLine {
    content: PlantUmlLineKind,
    raw: ParseContainer,
}
#[derive(Clone, Debug)]
pub enum PlantUmlLineKind {
    Start(StartLine),
    End(EndLine),
    Include(IncludeLine),
    Empty,
    Others,
}
impl PlantUmlLine {
    pub fn parse(input: ParseContainer) -> IResult<ParseContainer, Self> {
        let (raws, content) = {
            if let Ok((_, (raws, content))) = StartLine::parse(input.clone()) {
                let content = PlantUmlLineKind::Start(content);
                (raws, content)
            } else if let Ok((_, (raws, content))) = EndLine::parse(input.clone()) {
                let content = PlantUmlLineKind::End(content);
                (raws, content)
            } else if let Ok((_, (raws, content))) = IncludeLine::parse(input.clone()) {
                let content = PlantUmlLineKind::Include(content);
                (raws, content)
            } else if let Ok((_, (raws, _))) = EmptyLine::parse(input.clone()) {
                let content = PlantUmlLineKind::Empty;
                (raws, content)
            } else {
                let (_, raws) = Self::parse_others(input.clone())?;
                let content = PlantUmlLineKind::Others;
                (raws, content)
            }
        };
        let len = raws.iter().fold(0, |stack, item| stack + item.len());
        let (rest, raw) = input.split_at(len);
        let ret = (rest, Self { content, raw });
        Ok(ret)
    }
    pub fn raw_str(&self) -> &str {
        self.raw.as_str()
    }
    pub fn start(&self) -> Option<&StartLine> {
        if let PlantUmlLineKind::Start(x) = &self.content {
            Some(x)
        } else {
            None
        }
    }
    pub fn end(&self) -> Option<&EndLine> {
        if let PlantUmlLineKind::End(x) = &self.content {
            Some(x)
        } else {
            None
        }
    }
    pub fn include(&self) -> Option<&IncludeLine> {
        if let PlantUmlLineKind::Include(x) = &self.content {
            Some(x)
        } else {
            None
        }
    }
    pub fn diagram_kind(&self) -> Option<&str> {
        self.start().map(|x| x.diagram_kind())
    }
    pub fn empty(&self) -> Option<()> {
        if let PlantUmlLineKind::Empty = &self.content {
            Some(())
        } else {
            None
        }
    }
    fn parse_others(input: ParseContainer) -> IResult<ParseContainer, Vec<ParseContainer>> {
        let (rest, (not_end, end)) = tuple((
            wr!(take_till(|c| c == '\n' || c == '\r')),
            alt((wr!(eof), wr!(line_ending))),
        ))(input)?;
        Ok((rest, vec![not_end, end]))
    }
}
#[cfg(test)]
mod tests {
    use super::*;
    #[test]
    fn test_parse_start_line() -> anyhow::Result<()> {
        let testdata = "@startuml\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.start().is_some());
        let testdata = " \t@startuml\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.start().is_some());
        let testdata = "@startuml \t\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.start().is_some());
        let testdata = " \t@startuml \t\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.start().is_some());
        let testdata = " \t@startuml id_foo\t\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.start().is_some());
        let testdata = " \t@startuml(id=id_bar)\t\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.start().is_some());
        Ok(())
    }
    #[test]
    fn test_parse_end_line() -> anyhow::Result<()> {
        let testdata = "@enduml\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.end().is_some());
        let testdata = " \t@enduml\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.end().is_some());
        let testdata = "@enduml \t\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.end().is_some());
        let testdata = " \t@enduml \t\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.end().is_some());
        Ok(())
    }
    #[test]
    fn test_parse_include_line() -> anyhow::Result<()> {
        let testdata = "!include foo.puml\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.include().is_some());
        let testdata = " !include foo.puml!1\n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.include().is_some());
        let testdata = "\t!include foo.puml!bar \n";
        let (rest, parsed) = PlantUmlLine::parse(testdata.into())?;
        assert_eq!(rest, "");
        assert_eq!(testdata, parsed.raw);
        assert!(parsed.include().is_some());
        Ok(())
    }
}