// Copyright (c) 2017 Ernest Micklei
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

package proto

import (
	"testing"
)

func TestOptionCases(t *testing.T) {
	for i, each := range []struct {
		proto     string
		name      string
		strLit    string
		nonStrLit string
	}{{
		`option (full).java_package = "com.example.foo";`,
		"(full).java_package",
		"com.example.foo",
		"",
	}, {
		`option Bool = true;`,
		"Bool",
		"",
		"true",
	}, {
		`option Float = -3.14E1;`,
		"Float",
		"",
		"-3.14E1",
	}, {
		`option (foo_options) = { opt1: 123 opt2: "baz" };`,
		"(foo_options)",
		"",
		"",
	}, {
		`option optimize_for = SPEED;`,
		"optimize_for",
		"",
		"SPEED",
	}, {
		"option (my.enum.service.is.like).rpc = 1;",
		"(my.enum.service.is.like).rpc",
		"",
		"1",
	}} {
		p := newParserOn(each.proto)
		pr, err := p.Parse()
		if err != nil {
			t.Fatal("testcase failed:", i, err)
		}
		if got, want := len(pr.Elements), 1; got != want {
			t.Fatalf("[%d] got [%v] want [%v]", i, got, want)
		}
		o := pr.Elements[0].(*Option)
		if got, want := o.Name, each.name; got != want {
			t.Errorf("[%d] got [%v] want [%v]", i, got, want)
		}
		if len(each.strLit) > 0 {
			if got, want := o.Constant.Source, each.strLit; got != want {
				t.Errorf("[%d] got [%v] want [%v]", i, got, want)
			}
		}
		if len(each.nonStrLit) > 0 {
			if got, want := o.Constant.Source, each.nonStrLit; got != want {
				t.Errorf("[%d] got [%v] want [%v]", i, got, want)
			}
		}
		if got, want := o.IsEmbedded, false; got != want {
			t.Errorf("[%d] got [%v] want [%v]", i, got, want)
		}
	}
}

func TestLiteralString(t *testing.T) {
	proto := `"string"`
	p := newParserOn(proto)
	l := new(Literal)
	if err := l.parse(p); err != nil {
		t.Fatal(err)
	}
	if got, want := l.IsString, true; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := l.Source, "string"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
}

func TestOptionComments(t *testing.T) {
	proto := `
// comment
option Help = "me"; // inline`
	p := newParserOn(proto)
	pr, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	o := pr.Elements[0].(*Option)
	if got, want := o.IsEmbedded, false; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := o.Comment != nil, true; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.Comment.Lines[0], " comment"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.InlineComment != nil, true; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.InlineComment.Lines[0], " inline"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.Position.Line, 3; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.Comment.Position.Line, 2; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.InlineComment.Position.Line, 3; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
}

func TestAggregateSyntax(t *testing.T) {
	proto := `
// usage:
message Bar {
  // alternative aggregate syntax (uses TextFormat):
  int32 b = 2 [(foo_options) = {
    opt1: 123,
    opt2: "baz"
  }];
}
	`
	p := newParserOn(proto)
	pr, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	o := pr.Elements[0].(*Message)
	f := o.Elements[0].(*NormalField)
	if got, want := len(f.Options), 1; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	ac := f.Options[0].AggregatedConstants
	if got, want := len(ac), 2; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := ac[0].Name, "opt1"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := ac[1].Name, "opt2"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := ac[0].Source, "123"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := ac[1].Source, "baz"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.Position.Line, 3; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.Comment.Position.String(), "<input>:2:1"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := f.Position.String(), "<input>:5:3"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := ac[0].Position.Line, 6; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := ac[1].Position.Line, 7; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
}

func TestNonPrimitiveOptionComment(t *testing.T) {
	proto := `
// comment
option Help = { string_field: "value" }; // inline`
	p := newParserOn(proto)
	pr, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	o := pr.Elements[0].(*Option)
	if got, want := o.Comment != nil, true; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.Comment.Lines[0], " comment"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.InlineComment != nil, true; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := o.InlineComment.Lines[0], " inline"; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
}

func TestFieldCustomOptions(t *testing.T) {
	proto := `foo.bar lots = 1 [foo={hello:1}, bar=2];`
	p := newParserOn(proto)
	f := newNormalField()
	err := f.parse(p)
	if err != nil {
		t.Fatal(err)
	}
	if got, want := f.Type, "foo.bar"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := f.Name, "lots"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := len(f.Options), 2; got != want {
		t.Fatalf("got [%v] want [%v]", got, want)
	}
	if got, want := f.Options[0].Name, "foo"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := f.Options[1].Name, "bar"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := f.Options[1].Constant.Source, "2"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
}

func TestIgnoreIllegalEscapeCharsInAggregatedConstants(t *testing.T) {
	src := `syntax = "proto3";
	message Person {
	  string name  = 3 [(validate.rules).string = {
						  pattern:   "^[^\d\s]+( [^\d\s]+)*$",
						  max_bytes: 256,
					   }];
	}`
	p := newParserOn(src)
	d, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	f := d.Elements[1].(*Message).Elements[0].(*NormalField)
	if got, want := f.Options[0].AggregatedConstants[0].Source, "^[^\\d\\s]+( [^\\d\\s]+)*$"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
}

func TestIgnoreIllegalEscapeCharsInConstant(t *testing.T) {
	src := `syntax = "proto2";
	message Person {
		optional string cpp_trigraph = 20 [default = "? \? ?? \?? \??? ??/ ?\?-"];
	}`
	p := newParserOn(src)
	d, err := p.Parse()
	if err != nil {
		t.Fatal(err)
	}
	f := d.Elements[1].(*Message).Elements[0].(*NormalField)
	if got, want := f.Options[0].Constant.Source, "? \\? ?? \\?? \\??? ??/ ?\\?-"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
}

func TestFieldCustomOptionExtendedIdent(t *testing.T) {
	proto := `Type field = 1 [(validate.rules).enum.defined_only = true];`
	p := newParserOn(proto)
	f := newNormalField()
	err := f.parse(p)
	if err != nil {
		t.Fatal(err)
	}
	if got, want := f.Options[0].Name, "(validate.rules).enum.defined_only"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
}

// issue #50
func TestNestedAggregateConstants(t *testing.T) {
	src := `syntax = "proto3";

	package baz;

	option (foo.bar) = {
	  woot: 100
	  foo {
		hello: 200
		hello2: 300
		bar {
			hello3: 400
		}
	  }
	};`
	p := newParserOn(src)
	proto, err := p.Parse()
	if err != nil {
		t.Error(err)
	}
	option := proto.Elements[2].(*Option)
	if got, want := option.Name, "(foo.bar)"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := len(option.AggregatedConstants), 4; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := option.AggregatedConstants[0].Name, "woot"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := option.AggregatedConstants[1].Name, "foo.hello"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := option.AggregatedConstants[2].Name, "foo.hello2"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := option.AggregatedConstants[3].Name, "foo.bar.hello3"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := option.AggregatedConstants[1].Literal.SourceRepresentation(), "200"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := option.AggregatedConstants[2].Literal.SourceRepresentation(), "300"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := option.AggregatedConstants[3].Literal.SourceRepresentation(), "400"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
}

// Issue #59
func TestMultiLineOptionAggregateValue(t *testing.T) {
	src := `rpc ListTransferLogs(ListTransferLogsRequest)
	returns (ListTransferLogsResponse) {
		option (google.api.http) = {
		get: "/v1/{parent=projects/*/locations/*/transferConfigs/*/runs/*}/"
			"transferLogs"
		};
}`
	p := newParserOn(src)
	rpc := new(RPC)
	p.next()
	err := rpc.parse(p)
	if err != nil {
		t.Error(err)
	}
	get := rpc.Options[0].AggregatedConstants[0]
	if got, want := get.Name, "get"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
	if got, want := get.Literal.Source, "/v1/{parent=projects/*/locations/*/transferConfigs/*/runs/*}/transferLogs"; got != want {
		t.Errorf("got [%v] want [%v]", got, want)
	}
}

// issue #76
func TestOptionAggregateCanUseKeyword(t *testing.T) {
	src := `message User {
		string email = 3 [(validate.field) = {required: true}];
	}`
	p := newParserOn(src)
	_, err := p.Parse()
	if err != nil {
		t.Error(err)
	}
}