// pest-typed. A statically typed version of pest.
// Copyright (c) 2023 黄博奕
//
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. All files in the project carrying such notice may not be copied,
// modified, or distributed except according to those terms.

//! Test for [`pest_typed::predefined_node`].
#![cfg(test)]

extern crate alloc;

use pest_typed::{
    atomic_rule,
    choices::Choice2,
    compound_atomic_rule,
    iterators::{Pair, PairTree, Pairs},
    non_atomic_rule, normal_rule,
    predefined_node::*,
    rule_eoi,
    sequence::{Seq2, Seq3},
    silent_rule, BoundWrapper, ParsableTypedNode, RuleStruct, RuleType, RuleWrapper, Storage,
    StringArrayWrapper, StringWrapper, TypeWrapper,
};
use std::{fmt::Write, ops::Deref, string::String};

#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
enum Rule {
    Foo,
    RepFoo,
    NotFooBar,
    Life,
    WHITESPACE,
    COMMENT,
    EOI,
    String,
    Quote,
}
impl RuleType for Rule {
    fn name(&self) -> &'static str {
        match self {
            Rule::Foo => "Foo",
            Rule::RepFoo => "RepFoo",
            Rule::NotFooBar => "NotFooBar",
            Rule::Life => "Life",
            Rule::WHITESPACE => "WHITESPACE",
            Rule::COMMENT => "COMMENT",
            Rule::EOI => "EOI",
            Rule::String => "String",
            Rule::Quote => "Quote",
        }
    }
}

#[derive(Clone, Hash, PartialEq, Eq)]
struct Foo;
impl StringWrapper for Foo {
    const CONTENT: &'static str = "foo";
}
impl RuleWrapper<Rule> for Foo {
    const RULE: Rule = Rule::Foo;
    type Rule = Rule;
}

atomic_rule!(
    WHITESPACE,
    "White space.",
    Rule,
    Rule::WHITESPACE,
    CharRange::<' ', ' '>
);
compound_atomic_rule!(
    COMMENT,
    "Comment.",
    Rule,
    Rule::COMMENT,
    CharRange::<'\t', '\t'>,
    false
);
normal_rule!(
    StrFoo,
    "String \"Foo\".",
    Rule,
    Rule::Foo,
    Str::<Foo>,
    AtomicRepeat<Choice2<WHITESPACE<'i>, COMMENT<'i>>>,
    false
);
rule_eoi!(EOI, Rule);

#[test]
fn string() {
    assert_eq!(<StrFoo<'_, 0> as TypeWrapper>::Inner::CONTENT, Foo::CONTENT);
    StrFoo::try_check("foo").unwrap();
    let s = StrFoo::try_parse("foo").unwrap();
    assert_eq!(s.content.get_content(), "foo");
    assert_eq!(
        format!("{:?}", s),
        "StrFoo { content: Str, span: Span { str: \"foo\", start: 0, end: 3 } }"
    )
}

#[test]
fn range() {
    WHITESPACE::try_check(" ").unwrap();
    let whitespace = WHITESPACE::try_parse(" ").unwrap();
    assert_eq!(
        format!("{:?}", whitespace),
        "WHITESPACE { span: Span { str: \" \", start: 0, end: 1 } }"
    );

    COMMENT::try_check("\t").unwrap();
    let comment = COMMENT::try_parse("\t").unwrap();
    assert_eq!(
            format!("{:?}", comment),
            "COMMENT { content: CharRange { content: '\\t' }, span: Span { str: \"\\t\", start: 0, end: 1 } }"
        );
}
type Ignore<'i> = AtomicRepeat<Choice2<WHITESPACE<'i>, COMMENT<'i>>>;

#[test]
fn ignore() {
    silent_rule!(
        tmp,
        "Temporary rule.",
        Rule,
        Rule::RepFoo,
        Ignore<'i>,
        Empty<'i>,
        false
    );
    tmp::try_check(" ").unwrap();
    tmp::try_parse(" \t  ").unwrap();
}

#[test]
fn repetition() {
    type REP<'i> = Rep<StrFoo<'i, 0>, Ignore<'i>, 1>;
    non_atomic_rule!(
        R,
        "Repetion of [StrFoo].",
        Rule,
        Rule::RepFoo,
        REP<'i>,
        Ignore<'i>,
        false
    );

    R::try_check("foo").unwrap();

    let inputs = [
        "foofoofoo",
        "foo foo foo",
        "foo foo\tfoo",
        "foofoofoo",
        "foo foo foo",
        "foo foo\tfoo",
    ];
    for input in inputs {
        R::try_check(input).unwrap();
    }

    let [rep1, rep2, rep3, ..] = inputs.map(|s| R::try_parse(s).unwrap());

    let _ = R::try_parse("").unwrap();
    assert_ne!(rep1, rep2);
    assert_ne!(rep1, rep3);

    let format = |rep: &R<'_, 1>| -> String {
        rep.iter_matched()
            .map(|e| e.get_content())
            .collect::<Vec<_>>()
            .concat()
    };
    assert_eq!(format(&rep1), format(&rep2));
    assert_eq!(format(&rep1), format(&rep3));

    for i in rep1.clone().children() {
        assert_eq!(i.rule, Rule::Foo);
    }

    // White spaces and comments aren't filtered out.
    assert_ne!(
        rep1.iter_matched().collect::<Vec<_>>(),
        rep2.iter_matched().collect::<Vec<_>>()
    );
    assert_ne!(
        rep1.iter_matched().collect::<Vec<_>>(),
        rep3.iter_matched().collect::<Vec<_>>()
    );

    assert_eq!(REP::MIN, 0);
    assert_eq!(rep1.deref().get_min_len(), 0);
    assert_eq!(rep1.deref().get_max_len(), usize::MAX);
    assert_eq!(<R<'_, 0> as TypeWrapper>::Inner::MIN, 0);
}

#[test]
fn repetition_at_least_once() {
    type REP<'i> = RepOnce<Insens<'i, Foo>, Ignore<'i>, 1>;
    non_atomic_rule!(
        R,
        "Repetion of [StrFoo].",
        Rule,
        Rule::RepFoo,
        REP<'i>,
        Ignore<'i>,
        false
    );

    let inputs = ["fooFoofoo", "foo Foo foo", "Foo foo\tfoo", "Foofoofoo"];
    let [rep1, rep2, rep3, rep4] = inputs.map(|s| R::try_parse(s).unwrap());
    assert_ne!(rep1, rep2);
    assert_ne!(rep1, rep3);
    assert_ne!(rep1, rep4);

    let collect = |r: &R<'_, 1>| {
        r.iter_matched()
            .map(|r| r.get_content())
            .collect::<Vec<_>>()
            .concat()
    };
    assert_eq!(collect(&rep1), collect(&rep2));
    assert_eq!(collect(&rep1), collect(&rep3));
    assert_eq!(collect(&rep1), collect(&rep4));

    assert_eq!(rep1.clone().self_or_children().len(), 1);
    assert_eq!(rep1.clone().self_or_children()[0].rule, Rule::RepFoo);

    {
        let mut buf = String::new();
        rep3.iterate_level_order(|p, _depth| writeln!(buf, "{}", p.span.as_str()))
            .unwrap();
        assert_eq!(buf, "Foo foo\tfoo\n \n\t\n");
        assert_eq!(
            rep3.format_as_tree().unwrap(),
            "RepFoo
    WHITESPACE \" \"
    COMMENT \"\\t\"
"
        );
    }

    assert_eq!(REP::MIN, 1);
    assert_eq!(rep1.deref().get_min_len(), 1);
    assert_eq!(rep1.deref().get_max_len(), usize::MAX);
    assert_eq!(<R<'_, 0> as TypeWrapper>::Inner::MIN, 1);
}

#[test]
fn skip() {
    #[derive(Clone, Debug, Hash, PartialEq, Eq)]
    struct NewLine;
    impl StringArrayWrapper for NewLine {
        const CONTENT: &'static [&'static str] = &["\r\n", "\n", "\r"];
    }
    compound_atomic_rule!(
        QuotedString,
        "Quoted string.",
        Rule,
        Rule::String,
        Seq2<Skipped<Skip<'i, NewLine>, Ignore<'i>, 0>, Skipped<NEWLINE, Ignore<'i>, 0>>,
        false
    );

    let s1 = QuotedString::<1>::try_parse("2\r\n").unwrap();
    let s2 = QuotedString::<1>::try_parse("\r\n").unwrap();
    assert_ne!(s1, s2);

    let new_line = NewLine;
    assert_eq!(new_line.get_content(), NewLine::CONTENT);
}

#[test]
fn skip_char() {
    compound_atomic_rule!(
        three,
        "Skip 3 characters.",
        Rule,
        Rule::Foo,
        SkipChar<'i, 3>,
        false
    );
    three::<1>::try_parse("foo").unwrap();
    three::<1>::try_parse("foobar").unwrap_err();
}

#[test]
fn positive_predicate() {
    atomic_rule!(Quote,"A single `'`.", Rule,Rule::Quote,CharRange<'\'', '\''>);
    compound_atomic_rule!(
        Lifetime,
        "A simplified example of rust lifetime specifier.",
        Rule,
        Rule::Life,
        Seq3<
            Skipped<Quote<'i, 0>, Ignore<'i>, 0>,
            Skipped<
                Positive<
                    Seq2<
                        Skipped<ANY, Ignore<'i>, 0>,
                        Skipped<Negative<Quote<'i, 0>>, Ignore<'i>, 0>,
                    >,
                >,
                Ignore<'i>,
                0,
            >,
            Skipped<AtomicRepeat<CharRange<'a', 'z'>>, Ignore<'i>, 0>,
        >,
        false
    );

    let l = Lifetime::<1>::try_parse("'i").unwrap();
    let (quote, peeked, name) = l.as_ref();
    assert_eq!(quote.span.as_str(), "'");
    let (any, _) = peeked.as_ref();
    assert_eq!(any.content, 'i');
    assert_eq!(name.iter().map(|c| c.content).collect::<String>(), "i");
    assert_eq!(
        name.deref()
            .into_iter()
            .map(|c| c.content)
            .collect::<String>(),
        "i"
    );

    let l = Lifetime::<1>::try_parse("'input").unwrap();
    let (_, peeked, name) = l.as_ref();
    let (any, _) = peeked.as_ref();
    assert_eq!(any.content, 'i');
    assert_eq!(name.iter().map(|c| c.content).collect::<String>(), "input");
    assert_eq!(
        name.deref()
            .clone()
            .into_iter()
            .map(|c| c.content)
            .collect::<String>(),
        "input"
    );

    Lifetime::<1>::try_parse("'i'").unwrap_err();
}

#[test]
fn negative_predicate() {
    #[derive(Clone, Debug, Hash, PartialEq, Eq)]
    pub(crate) struct StrFoo;
    impl StringWrapper for StrFoo {
        const CONTENT: &'static str = "foo";
    }
    #[derive(Clone, Debug, Hash, PartialEq, Eq)]
    pub(crate) struct StrBar;
    impl StringWrapper for StrBar {
        const CONTENT: &'static str = "bar";
    }
    compound_atomic_rule!(
        not_foo_bar,
        "Any character except \"foo\" or \"bar\".",
        Rule,
        Rule::NotFooBar,
        AtomicRepeat<(Negative<Choice2<Str<StrFoo>, Str<StrBar>>>, ANY)>,
        false
    );
    let _ = not_foo_bar::<1>::try_parse("").unwrap();
    let baz = not_foo_bar::<1>::try_parse("baz").unwrap();
    for i in baz.iter() {
        let (neg, any) = i;
        assert_eq!(
            <Negative<_> as Pairs<'_, Rule>>::self_or_children(neg).len(),
            0
        );
        assert_eq!(<ANY as Pairs<'_, Rule>>::self_or_children(any).len(), 0);
    }
    let _ = not_foo_bar::<1>::try_parse("Foofoo").unwrap_err();
    let _ = not_foo_bar::<1>::try_parse("bazfoo").unwrap_err();
}

#[test]
fn peek() {
    compound_atomic_rule!(
        Rep_1_3,
        "Repeat previously matched expression 1 to 3 times",
        Rule,
        Rule::RepFoo,
        Seq2<
            Skipped<Push<Insens<'i, Foo>>, Ignore<'i>, 0>,
            Skipped<RepeatMinMax<Skipped<PEEK<'i>, Ignore<'i>, 0>, 1, 3>, Ignore<'i>, 0>,
        >,
        false
    );
    let r = Rep_1_3::<1>::try_parse("foOfoO").unwrap();
    assert_eq!(
        format!("{:#?}", r),
        "Rep_1_3 {
    content: Seq2(
        Push {
            content: Insens {
                content: \"foO\",
            },
        },
        RepeatMinMax {
            content: [
                PEEK {
                    span: Span {
                        str: \"foO\",
                        start: 3,
                        end: 6,
                    },
                },
            ],
        },
    ),
    span: Span {
        str: \"foOfoO\",
        start: 0,
        end: 6,
    },
}"
    );
    let (head, following) = r.as_ref();
    assert_eq!(head.deref().content, "foO");
    assert_eq!(head.get_content(), Foo::CONTENT);
    for i in following.iter_matched() {
        assert_eq!(i.span.as_str(), head.deref().content);
    }

    Rep_1_3::try_parse("fooFoo").unwrap_err();
    Rep_1_3::try_parse("Foo").unwrap_err();
    Rep_1_3::try_parse("FooFooFooFooFoo").unwrap_err();
    let (pos, _) = Rep_1_3::try_parse_partial("FooFooFooFooFoo").unwrap();
    assert_eq!(pos.pos(), 12);

    {
        use std::collections::HashSet;
        let mut set = HashSet::new();
        for input in [
            "foofoo",
            "FooFoo",
            "fOofOo",
            "foOfoO",
            "foofoo",
            "foofoofoo",
        ] {
            set.insert(Rep_1_3::try_parse(input).unwrap());
        }
        assert_eq!(set.len(), 5);
    }
}

#[test]
fn rep() {
    compound_atomic_rule!(
        Rep_0_3,
        "Repeat previously matched expression 0 to 3 times",
        Rule,
        Rule::RepFoo,
        RepeatMin<Skipped<Str<Foo>, Ignore<'i>, 0>, 0>,
        false
    );

    let x = Rep_0_3::try_parse("").unwrap();
    assert_eq!(x.ref_inner(), &RepeatMin::default());

    use std::collections::HashSet;

    let mut set = HashSet::new();
    let foos = ["foofoo", "foo", "foofoofoo"];
    for input in foos {
        Rep_0_3::try_check(input).unwrap();
        let r = Rep_0_3::try_parse(input).unwrap();
        for s in r.iter_matched() {
            set.insert(s.clone());
        }
        for s in r.deref().clone().into_iter_matched() {
            set.insert(s);
        }
    }
    assert_eq!(set.len(), 1);

    let mut set = HashSet::new();
    for input in ["foo", "foofoofoo", "foofoo", "foofoofoo"] {
        set.insert(Rep_0_3::try_parse(input).unwrap());
    }
    assert_eq!(set.len(), 3);

    compound_atomic_rule!(
        Rep_2,
        "Repeat \"foo\" 2 times",
        Rule,
        Rule::RepFoo,
        [Insens<'i, Foo>; 2],
        false
    );
    for input in ["", "foo", "foofoofoo"] {
        Rep_2::try_check(input).unwrap_err();
        Rep_2::try_parse(input).unwrap_err();
    }
    let arr = Rep_2::try_parse("foofoo").unwrap();
    assert_eq!(
            format!("{:?}", arr),
            "Rep_2 { content: [Insens { content: \"foo\" }, Insens { content: \"foo\" }], span: Span { str: \"foofoo\", start: 0, end: 6 } }"
        );
    let mut set = HashSet::new();
    for input in ["foofoo", "fooFOO", "FooFoo", "foofoo"] {
        set.insert(Rep_2::try_parse(input).unwrap());
    }
    assert_eq!(set.len(), 3);
}
