use simplecss::*;

macro_rules! tokenize {
    ($name:ident, $text:expr, $( $token:expr ),*) => (
        #[test]
        fn $name() {
            let mut t = SelectorTokenizer::from($text);
            $(
                assert_eq!(t.next().unwrap().unwrap(), $token);
            )*

            assert!(t.next().is_none());
        }
    )
}

tokenize!(tokenize_01, "*",
    SelectorToken::UniversalSelector
);

tokenize!(tokenize_02, "div",
    SelectorToken::TypeSelector("div")
);

tokenize!(tokenize_03, "#div",
    SelectorToken::IdSelector("div")
);

tokenize!(tokenize_04, ".div",
    SelectorToken::ClassSelector("div")
);

tokenize!(tokenize_05, "[id]",
    SelectorToken::AttributeSelector("id", AttributeOperator::Exists)
);

tokenize!(tokenize_06, "[id=test]",
    SelectorToken::AttributeSelector("id", AttributeOperator::Matches("test"))
);

tokenize!(tokenize_07, "[id~=test]",
    SelectorToken::AttributeSelector("id", AttributeOperator::Contains("test"))
);

tokenize!(tokenize_08, "[id|=test]",
    SelectorToken::AttributeSelector("id", AttributeOperator::StartsWith("test"))
);

tokenize!(tokenize_09, "[id='test']",
    SelectorToken::AttributeSelector("id", AttributeOperator::Matches("test"))
);

tokenize!(tokenize_10, "[id=\"test\"]",
    SelectorToken::AttributeSelector("id", AttributeOperator::Matches("test"))
);

tokenize!(tokenize_11, "[id='te\\'st']",
    SelectorToken::AttributeSelector("id", AttributeOperator::Matches("te\\'st"))
);

tokenize!(tokenize_12, "[id=\"te\\\"st\"]",
    SelectorToken::AttributeSelector("id", AttributeOperator::Matches("te\\\"st"))
);

tokenize!(tokenize_13, "div:first-child",
    SelectorToken::TypeSelector("div"),
    SelectorToken::PseudoClass("first-child")
);

tokenize!(tokenize_14, ":first-child",
    SelectorToken::PseudoClass("first-child")
);

tokenize!(tokenize_15, "div p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("p")
);

tokenize!(tokenize_16, "div p a",
    SelectorToken::TypeSelector("div"),
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("p"),
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("a")
);

tokenize!(tokenize_17, "div>p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::ChildCombinator,
    SelectorToken::TypeSelector("p")
);

tokenize!(tokenize_18, "div >p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::ChildCombinator,
    SelectorToken::TypeSelector("p")
);

tokenize!(tokenize_19, "div> p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::ChildCombinator,
    SelectorToken::TypeSelector("p")
);

tokenize!(tokenize_20, "div > p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::ChildCombinator,
    SelectorToken::TypeSelector("p")
);

tokenize!(tokenize_21, "div .p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::DescendantCombinator,
    SelectorToken::ClassSelector("p")
);

tokenize!(tokenize_22, "div *",
    SelectorToken::TypeSelector("div"),
    SelectorToken::DescendantCombinator,
    SelectorToken::UniversalSelector
);

tokenize!(tokenize_23, "div #p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::DescendantCombinator,
    SelectorToken::IdSelector("p")
);

tokenize!(tokenize_24, "div [id]",
    SelectorToken::TypeSelector("div"),
    SelectorToken::DescendantCombinator,
    SelectorToken::AttributeSelector("id", AttributeOperator::Exists)
);

tokenize!(tokenize_25, "div :link",
    SelectorToken::TypeSelector("div"),
    SelectorToken::DescendantCombinator,
    SelectorToken::PseudoClass("link")
);

tokenize!(tokenize_26, "div+p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::AdjacentCombinator,
    SelectorToken::TypeSelector("p")
);

tokenize!(tokenize_27, "div +p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::AdjacentCombinator,
    SelectorToken::TypeSelector("p")
);

tokenize!(tokenize_28, "div+ p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::AdjacentCombinator,
    SelectorToken::TypeSelector("p")
);

tokenize!(tokenize_29, "div + p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::AdjacentCombinator,
    SelectorToken::TypeSelector("p")
);

tokenize!(tokenize_30, "div {",
    SelectorToken::TypeSelector("div")
);

tokenize!(tokenize_31, "div,",
    SelectorToken::TypeSelector("div")
);

tokenize!(tokenize_32, "div{",
    SelectorToken::TypeSelector("div")
);

tokenize!(tokenize_33, "div ,",
    SelectorToken::TypeSelector("div")
);

tokenize!(tokenize_34, "div.test",
    SelectorToken::TypeSelector("div"),
    SelectorToken::ClassSelector("test")
);

tokenize!(tokenize_35, "div.test.warn",
    SelectorToken::TypeSelector("div"),
    SelectorToken::ClassSelector("test"),
    SelectorToken::ClassSelector("warn")
);

tokenize!(tokenize_36, "div#id",
    SelectorToken::TypeSelector("div"),
    SelectorToken::IdSelector("id")
);

tokenize!(tokenize_37, "*[id]",
    SelectorToken::UniversalSelector,
    SelectorToken::AttributeSelector("id", AttributeOperator::Exists)
);

tokenize!(tokenize_38, "*.test",
    SelectorToken::UniversalSelector,
    SelectorToken::ClassSelector("test")
);

tokenize!(tokenize_39, "*#id",
    SelectorToken::UniversalSelector,
    SelectorToken::IdSelector("id")
);

tokenize!(tokenize_40, "div * p",
    SelectorToken::TypeSelector("div"),
    SelectorToken::DescendantCombinator,
    SelectorToken::UniversalSelector,
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("p")
);

tokenize!(tokenize_41, "div[id=test][color=red]",
    SelectorToken::TypeSelector("div"),
    SelectorToken::AttributeSelector("id", AttributeOperator::Matches("test")),
    SelectorToken::AttributeSelector("color", AttributeOperator::Matches("red"))
);

tokenize!(tokenize_42, "a.external:visited",
    SelectorToken::TypeSelector("a"),
    SelectorToken::ClassSelector("external"),
    SelectorToken::PseudoClass("visited")
);

tokenize!(tokenize_43, ":lang(en)",
    SelectorToken::LangPseudoClass("en")
);

tokenize!(tokenize_44, "a\nb",
    SelectorToken::TypeSelector("a"),
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("b")
);

tokenize!(tokenize_45, ".warn :first-child",
    SelectorToken::ClassSelector("warn"),
    SelectorToken::DescendantCombinator,
    SelectorToken::PseudoClass("first-child")
);

macro_rules! malformed {
    ($name:ident, $text:expr, $err_str:expr) => (
        #[test]
        fn $name() {
            for token in SelectorTokenizer::from($text) {
                match token {
                    Ok(_) => {}
                    Err(e) => {
                        assert_eq!(e.to_string(), $err_str);
                        return;
                    }
                }
            }

            unreachable!()
        }
    )
}

malformed!(malformed_01, ">", "unexpected combinator");

malformed!(malformed_02, "+", "unexpected combinator");

malformed!(malformed_03, "> a", "unexpected combinator");

malformed!(malformed_04, "a >", "selector missing");

malformed!(malformed_05, "*a", "unexpected selector");

malformed!(malformed_06, "a*", "unexpected selector");

malformed!(malformed_07, "a > ,", "selector missing");

malformed!(malformed_08, "a > >", "unexpected combinator");

malformed!(malformed_09, "a > {", "selector missing");

malformed!(malformed_10, "a/**/b", "unexpected selector");

malformed!(malformed_11, "a < b", "invalid ident at 1:3");

malformed!(malformed_12, ":lang()", "invalid language pseudo-class");

malformed!(malformed_13, ":lang( )", "invalid language pseudo-class");

malformed!(malformed_14, "::first-child", "invalid ident at 1:2");

malformed!(malformed_15, "[olor:red", "invalid or unsupported attribute selector");

malformed!(malformed_16, "", "selector missing");

malformed!(malformed_17, " ", "selector missing");

malformed!(malformed_18, "/**/", "selector missing");

tokenize!(comment_01, "/**/a",
    SelectorToken::TypeSelector("a")
);

tokenize!(comment_02, "/* */a",
    SelectorToken::TypeSelector("a")
);

tokenize!(comment_03, "/* comment */a",
    SelectorToken::TypeSelector("a")
);

tokenize!(comment_04, "/**/ /**/a",
    SelectorToken::TypeSelector("a")
);

tokenize!(comment_05, "/**/ a /**/",
    SelectorToken::TypeSelector("a")
);

tokenize!(comment_06, "a /**/ b",
    SelectorToken::TypeSelector("a"),
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("b")
);

tokenize!(comment_08, "a /**/b",
    SelectorToken::TypeSelector("a"),
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("b")
);

tokenize!(comment_09, "a/**/ b",
    SelectorToken::TypeSelector("a"),
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("b")
);

tokenize!(comment_10, "a/**/ /**/b",
    SelectorToken::TypeSelector("a"),
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("b")
);

tokenize!(comment_11, "a /**/ /**/ b",
    SelectorToken::TypeSelector("a"),
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("b")
);

tokenize!(comment_12, "a /**//**/ b",
    SelectorToken::TypeSelector("a"),
    SelectorToken::DescendantCombinator,
    SelectorToken::TypeSelector("b")
);
