use insta::assert_debug_snapshot;

use super::*;

macro_rules! case {
    ($method:ident($source:expr)) => {{
        let mut parser = Parser::new($source);
        let result = parser.$method();
        assert_debug_snapshot!(if parser.lexer.extras.errors.is_empty() {
            Ok(result.unwrap())
        } else {
            Err(parser.lexer.extras.errors)
        });
    }};
}

#[test]
pub fn parse_option() {
    case!(parse_option("option foo = 5;"));
    case!(parse_option(
        "//detached\n\n /*leading*/\noption foo = 5;//trailing"
    ));
    case!(parse_option("option (foo.bar) = \"hello\";"));
    case!(parse_option("option (foo).bar = true;"));
    case!(parse_option("option foo.(bar.baz).qux = ident;"));
    case!(parse_option("option ;"));
    case!(parse_option("option foo ("));
    case!(parse_option("option foo.]"));
    case!(parse_option("option foo = ="));
    case!(parse_option("option foo = 1 )"));
    case!(parse_option("option foo = {};"));
    case!(parse_option("option (ext).foo = { foo: 5 };"));
    case!(parse_option("option quz.(bar) = { foo: [blah] };"));
    case!(parse_option(
        "option baz = { foo: [<x:3>, <y{};z<a:-foo>,>] };"
    ));
    case!(parse_option("option foo = 1f;"));
    case!(parse_option("option optimize_for = message;"));
    case!(parse_option("option message = 1;"));
    case!(parse_option("option ext.(service.rpc) = 1;"));
    case!(parse_option("option foo = {"));
    case!(parse_option("option foo = { x:1"));
    case!(parse_option("option optimize_for = google.protobuf.SPEED;"));
    case!(parse_option("option ext.(.foo.bar) = 42;"));
    case!(parse_option("option foo = -'a';"));
    case!(parse_option("option foo = { } }"));
    case!(parse_option("option foo = {"));
}

#[test]
fn parse_text_format_message() {
    case!(parse_text_format_message("foo: 10"));
    case!(parse_text_format_message("foo: 10f"));
    case!(parse_text_format_message("foo: 1.0f"));
    case!(parse_text_format_message("foo: 'bar' \"baz\" \n\t 'quz'"));
    case!(parse_text_format_message(
        r#"s: "first" 'second'
            "third"
        joined: "first""second"'third''fourth'"#
    ));
    case!(parse_text_format_message("message: { foo: \"bar\" }"));
    case!(parse_text_format_message("[ext.scalar]: 10"));
    case!(parse_text_format_message("[ext.message]: { foo: \"bar\" }"));
    case!(parse_text_format_message(
        "any: { [type.googleapis.com/foo.bar]: { foo: \"bar\" } }"
    ));
    case!(parse_text_format_message("foo: enum"));
    case!(parse_text_format_message("foo: [enum]"));
    case!(parse_text_format_message("foo: -enum"));
    case!(parse_text_format_message("foo: [-enum]"));
    case!(parse_text_format_message(
        "pot < kind: TULIP name: \"Bob\" legs: 0 >"
    ));
    case!(parse_text_format_message(
        r#"escapes: '\a\b\f\n\r\t\v\?\\\'\"\1\11\111\xa\xAA\u1111\U00101111'"#
    ));
    case!(parse_text_format_message(r#"value: -'string'"#));
    case!(parse_text_format_message(r#"value: -"#));
    case!(parse_text_format_message(r#"value"#));
    case!(parse_text_format_message(r#"value: {"#));
    case!(parse_text_format_message(r#"value: {} foo: 10f"#));
    case!(parse_text_format_message(r#"value: <"#));
    case!(parse_text_format_message(r#"value 'foo'"#));
    case!(parse_text_format_message(
        "value: 1 /* block */ value: 2 // line\n value: 3"
    ));
}

#[test]
fn parse_enum() {
    case!(parse_enum("enum Foo {}"));
    case!(parse_enum("enum Foo { ; ; }"));
    case!(parse_enum(
        "/*detached*//*leading*/\nenum Foo {\n//trailing\n\n; ; }"
    ));
    case!(parse_enum("enum Foo { BAR = 1; }"));
    case!(parse_enum("enum Foo { option bar = 'quz' ; VAL = -1; }"));
    case!(parse_enum("enum Foo { BAR = 0 [opt = 0.5]; }"));
    case!(parse_enum("enum Foo { BAR = 0; reserved -1 to max; }"));
    case!(parse_enum("enum 3"));
    case!(parse_enum("enum Foo 0.1"));
    case!(parse_enum("enum Foo {]"));
    case!(parse_enum("enum Foo { BAR ."));
    case!(parse_enum("enum Foo { BAR = foo"));
    case!(parse_enum("enum Foo { message = 1; }"));
    case!(parse_enum("enum \"Foo\""));
    case!(parse_enum("enum Foo { BAR = 0 [(an.ext).opt = 0.5]; }"));
    case!(parse_enum("enum Foo { BAR = 0 }"));
}

#[test]
fn parse_service() {
    case!(parse_service("service Foo {}"));
    case!(parse_service("service Foo { ; ; }"));
    case!(parse_service(
        "//detached\n\n//leading\nservice Foo {\n/* nottrailing */; ; }"
    ));
    case!(parse_service("service service { }"));
    case!(parse_service("service Foo { rpc bar(A) returns (B.C); }"));
    case!(parse_service(
        "service Foo { rpc bar(stream .A.B) returns (stream .C); }"
    ));
    case!(parse_service(
        "service Foo { rpc bar(A) returns (B.C) { } }"
    ));
    case!(parse_service(
        "service Foo { rpc bar(A) returns (B.C) { ; ; } }"
    ));
    case!(parse_service(
        "service Foo { rpc bar(A) returns (B.C) { option opt = -1; } }"
    ));
    case!(parse_service("service ;"));
    case!(parse_service("service Foo ("));
    case!(parse_service("service Foo { bar"));
    case!(parse_service("service Foo { rpc ="));
    case!(parse_service("service Foo { rpc bar{"));
    case!(parse_service("service Foo { rpc bar(+"));
    case!(parse_service("service Foo { rpc bar(A("));
    case!(parse_service("service Foo { rpc bar(A) ["));
    case!(parse_service("service Foo { rpc bar(A) returns ="));
    case!(parse_service("service Foo { rpc bar(A) returns ()"));
    case!(parse_service("service Foo { rpc bar(A) returns (stream =)"));
    case!(parse_service("service Foo { rpc bar(A) returns (stream B}"));
    case!(parse_service(
        "service Foo { rpc bar(A) returns (stream B) )"
    ));
    case!(parse_service(
        "service Foo { rpc bar(A) returns (stream B) {rpc"
    ));
    case!(parse_service(
        "service Foo { rpc bar(stream stream) returns (stream stream.stream); }"
    ));
    case!(parse_service(
        "service Foo { rpc bar(stream .stream.rpc) returns (stream .map.enum); }"
    ));
    case!(parse_service(
        "service Foo {
            option foo = bar;
        }"
    ));
}

#[test]
pub fn parse_package() {
    case!(parse_package("package foo;"));
    case!(parse_package(
        "//detached\n//detached2\n\n//detached3\n\npackage foo;\n/*trailing*/"
    ));
    case!(parse_package("package foo.bar;"));
    case!(parse_package("package ="));
    case!(parse_package("package foo)"));
}

#[test]
pub fn parse_import() {
    case!(parse_import("import 'foo';"));
    case!(parse_import("/*leading*/\nimport 'foo';/*trailing*/\n"));
    case!(parse_import("import weak \"foo\";"));
    case!(parse_import("import public 'f\\x6fo';"));
    case!(parse_import("import ;"));
    case!(parse_import("import public ;"));
    case!(parse_import("import 'foo' message"));
    case!(parse_import("import 'foo\\\\bar';"));
    case!(parse_import("import 'foo//bar';"));
    case!(parse_import("import 'foo/./bar';"));
    case!(parse_import("import 'foo/../bar';"));
    case!(parse_import("import '\\xFF';"));
    case!(parse_import("import 'foo' \"bar\";"));
}

#[test]
pub fn parse_extension() {
    case!(parse_extend("extend Foo { }"));
    case!(parse_extend("/*leading*/extend Foo {\n//trailing\n }"));
    case!(parse_extend("extend Foo { ; ; }"));
    case!(parse_extend("extend Foo.Foo { optional int32 bar = 126; }"));
    case!(parse_extend(
        "extend .Foo { optional int32 bar = 126; repeated string quz = 127; }"
    ));
    case!(parse_extend(
        "extend Foo { repeated group A = 1 { optional string name = 2; } }"
    ));
    case!(parse_extend("extend ] "));
    case!(parse_extend("extend { 'foo' }"));
}

#[test]
pub fn parse_reserved() {
    case!(parse_reserved("//detached\n\nreserved 'foo';//trailing"));
    case!(parse_reserved("reserved 0, 2 to 3, 5 to max;"));
    case!(parse_reserved("reserved -1;"));
    case!(parse_reserved("reserved 'foo', 'bar';"));
    case!(parse_reserved("reserved ;"));
    case!(parse_reserved("reserved '0foo';"));
    case!(parse_reserved("reserved '\\xFF';"));
    case!(parse_reserved("reserved -1f;"));
}

#[test]
pub fn parse_group() {
    case!(parse_field(
        "//leading\noptional group A = 1 {\n/*trailing*/ }"
    ));
    case!(parse_field("optional group A = 1 { }"));
    case!(parse_field("optional group A = 1 { ; ; }"));
    case!(parse_field("optional group A = 1 [deprecated = true] { }"));
    case!(parse_field(
        "optional group A = 1 { optional sint32 foo = 2; }"
    ));
    case!(parse_field("optional group a = 1 { }"));
    case!(parse_field("optional group , { }"));
    case!(parse_field("optional group a ["));
    case!(parse_field("optional group A = {"));
    case!(parse_field("optional group A = 1 ;"));
    case!(parse_field("optional group A = 1 {]"));
    case!(parse_field("optional group A = 1f { };"));
    case!(parse_field("optional 'group' A = 1 { };"));
}

#[test]
pub fn parse_field() {
    case!(parse_field("map<string, Project> projects = 3;"));
    case!(parse_field(
        "/*leading*/map<string, int32> projects = 3;\n/*trailing*/\n"
    ));
    case!(parse_field(
        "map<int32, bool> name = 5 [opt = true, opt2 = 4.5];"
    ));
    case!(parse_field("map<.foo.bar, bool> invalid = -0;"));
    case!(parse_field("map>"));
    case!(parse_field("map<;"));
    case!(parse_field("map<int32("));
    case!(parse_field("map<string, ="));
    case!(parse_field("map<string, .Foo,"));
    case!(parse_field("map<string, Foo> ;"));
    case!(parse_field("map<string, Foo> foo ]"));
    case!(parse_field("map<string, Foo> foo = x"));
    case!(parse_field("map<string, Foo> foo = 1 service"));
    case!(parse_field("map<foo;"));
    case!(parse_field("double double = 1 [default = -nan];"));
    case!(parse_field("optional int32 name = 5 [(ext) = \"foo\"];"));
    case!(parse_field("{"));
}

#[test]
pub fn parse_message() {
    case!(parse_message("message Foo {}"));
    case!(parse_message(
        "//detached\n/*leading*/message Foo {/*trailing*/}"
    ));
    case!(parse_message("message Foo { ; ; }"));
    case!(parse_message(
        "\
        message Foo {\
            message Bar {}\
            enum Quz {}\
            extend Bar {}\
        }"
    ));
    case!(parse_message(
        "\
        message Foo {
            fixed32 a = 1;
            optional map<int32, bool> b = 2;

            optional group C = 3 {
                required float d = 1;
            }

            oneof x {
                string y = 4;
            }
        }"
    ));
    case!(parse_message("message Foo { repeated Bar a = 1; }"));
    case!(parse_message("message Foo { repeated Bar service = 2; }"));
    case!(parse_message(
        "message Foo { extensions 5, 7 to 8, 10 to max [deprecated = false]; }"
    ));
    case!(parse_message(
        "message Foo { repeated map<sint32, fixed64> m = 1; }"
    ));
    case!(parse_message("message Foo { group Baz = 1 {} }"));
    case!(parse_message("message Foo { , }"));
    case!(parse_message(
        "message Foo {
        message Foo {
            optional int32 start = 1;  // trail1
            optional int32 end = 2;    // trail2
        }
    }"
    ));
    case!(parse_message("message Foo { service foo = 1; }"));
    case!(parse_message("message Foo { optional rpc foo = 1; }"));
    case!(parse_message(
        "message Foo { extensions 5, 7 to 8, 10 to max [(ext.ext).ext.(ext) = { a: IDENT }]; }"
    ));
    case!(parse_message("message Foo { extensions 5 }"));
    case!(parse_message("message Foo { extensions 5 to 5 }"));
    case!(parse_message("message Foo { reserved 'a' }"));
    case!(parse_message("message Foo { extensions 5 to }"));
    case!(parse_message("message Foo { optional .a.b, }"));
}

#[test]
pub fn parse_oneof() {
    case!(parse_oneof("oneof Foo {}"));
    case!(parse_oneof("oneof Foo { ; ; }"));
    case!(parse_oneof(
        "/*detached1*///detached2\n\n//leading\noneof Foo {/*trailing*/ ; ; }"
    ));
    case!(parse_oneof("oneof Foo { int32 bar = 1; }"));
    case!(parse_oneof("oneof Foo { optional group Bar = 1 {} }"));
    case!(parse_oneof("oneof Foo { group Baz = -1 {} }"));
    case!(parse_oneof("oneof 10.4"));
    case!(parse_oneof("oneof Foo <"));
    case!(parse_oneof("oneof Foo { ,"));
    case!(parse_oneof("oneof Foo { bytes b = 1 }"));
    case!(parse_oneof("oneof Foo { oneof Bar {} }"));
    case!(parse_oneof("oneof Foo { oneof bar = 1; }"));
    case!(parse_oneof("oneof Foo { required oneof bar = 1; }"));
}

#[test]
pub fn parse_file() {
    case!(parse_file(""));
    case!(parse_file("package protox.lib;"));
    case!(parse_file(
        "\
        package protox.lib;
        package another.one;
    "
    ));
    case!(parse_file(
        "\
        syntax = 'proto2';

        option optimize_for = SPEED;
    "
    ));
    case!(parse_file(
        "\
        syntax = \"proto3\";

        import \"foo.proto\";
    "
    ));
    case!(parse_file(
        "\
        syntax = 'unknown';
    "
    ));
    case!(parse_file(
        "\
        syntax = 'proto2';

        message Foo { , }
        enum Bar { ; }
        option quz 1;
    "
    ));
    case!(parse_file(
        "\
        syntax = 'proto3';

        message Foo {
            // trailing

            // detached

            // leading
            int32 bar = 1;
            // trailing2
        }
    "
    ));
    case!(parse_file("syntax = 'proto3'"));
    case!(parse_file(
        "/* leading detached */\n// leading\n syntax = 'proto3'; /* trailing */"
    ));
    case!(parse_file("option invalid = /"));
    case!(parse_file(
        "
        // code goes brrr
        option optimize_for = SPEED;
    "
    ));
    case!(parse_file("syntax = \"pro\" \n\n 'to3';"));
    case!(parse_file(";"));
    case!(parse_file("syntax = 1;"));
    case!(parse_file("thing"));
    case!(parse_file("message } } } message } } }"));
    case!(parse_file("thing $"));
}
