// Copyright 2019 CUE Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package literal

import (
	"fmt"
	"testing"
)

func TestUnquote(t *testing.T) {
	testCases := []struct {
		in, out string
		err     error
	}{
		{`"Hello"`, "Hello", nil},
		{`'Hello'`, "Hello", nil},
		{`'Hellø'`, "Hellø", nil},
		{`"""` + "\n\t\tHello\n\t\t" + `"""`, "Hello", nil},
		{`"""` + "\r\n\tHello\r\n\n\t" + `"""`, "Hello\n", nil},
		{"'''\n\t\tHello\n\t\t'''", "Hello", nil},
		{`"""` + "\n\tHello \\\n\tworld\n\t" + `"""`, "Hello world", nil},
		{`"""` + "\r\n\tHello \\\r\n\tworld\r\n\t" + `"""`, "Hello world", nil},
		{`"""` + "\none \\\ntwo \\\nthree\n" + `"""`, "one two three", nil},
		{`"""` + "\nHello\\\n" + `"""`, "", errEscapedLastNewline},
		{`"""` + "\n\tHello\\\n\t" + `"""`, "", errEscapedLastNewline},
		{`"""` + "\r\n\tHello\\\r\n\t" + `"""`, "", errEscapedLastNewline},
		{"'''\n\t\tHello\n\n\t\t'''", "Hello\n", nil},
		{"'''\n\n\t\tHello\n\t\t'''", "\nHello", nil},
		{"'''\n\n\n\n\t\t'''", "\n\n", nil},
		{"'''\n\t\t'''", "", nil},
		{`"""` + "\n\raaa\n\rbbb\n\r" + `"""`, "aaa\nbbb", nil},
		{`'\a\b\f\n\r\t\v\'\\\/'`, "\a\b\f\n\r\t\v'\\/", nil},
		{`"\a\b\f\n\r\t\v\"\\\/"`, "\a\b\f\n\r\t\v\"\\/", nil},
		{`#"The sequence "\U0001F604" renders as \#U0001F604."#`,
			`The sequence "\U0001F604" renders as 😄.`,
			nil},
		{`"  \U00010FfF"`, "  \U00010fff", nil},
		{`"\u0061 "`, "a ", nil},
		{`'\x61\x55'`, "\x61\x55", nil},
		{`'\061\055'`, "\061\055", nil},
		{`'\377 '`, "\377 ", nil},
		{"'e\u0300\\n'", "e\u0300\n", nil},
		{`'\06\055'`, "", errSyntax},
		{`'\0'`, "", errSyntax},
		{`"\06\055"`, "", errSyntax},    // too short
		{`'\777 '`, "", errSyntax},      // overflow
		{`'\U012301'`, "", errSyntax},   // too short
		{`'\U0123012G'`, "", errSyntax}, // invalid digit G
		{`"\x04"`, "", errSyntax},       // not allowed in strings
		{`'\U01230123'`, "", errSyntax}, // too large

		// Surrogate pairs
		{`"\uD834\uDD1E"`, "𝄞", nil},
		{`"\uDD1E\uD834"`, "", errSurrogate},
		{`"\uD834\uD834"`, "", errSurrogate},

		{`"\\"`, "\\", nil},
		{`"\'"`, "", errSyntax},
		{`"\q"`, "", errSyntax},
		{"'\n'", "", errSyntax},
		{"'---\n---'", "", errSyntax},
		{"'''\r'''", "", errMissingOpeningNewline},

		{`#"Hello"#`, "Hello", nil},
		{`#"Hello\v"#`, "Hello\\v", nil},
		{`#"Hello\#v\r"#`, "Hello\v\\r", nil},
		{`##"Hello\##v\r"##`, "Hello\v\\r", nil},
		{`##"Hello\##v"##`, "Hello\v", nil},
		{"#'''\n\t\tHello\\#v\n\t\t'''#", "Hello\v", nil},
		{"##'''\n\t\tHello\\#v\n\t\t'''##", "Hello\\#v", nil},
		{`#"""` + "\n\t\t\\#r\n\t\t" + `"""#`, "\r", nil},
		{`#""#`, "", nil},
		{`#"""#`, `"`, nil},
		{`#" ""#`, ` "`, nil},
		{`#" """#`, ` ""`, nil},
		{`##" """# "##`, ` """# `, nil},
		{`##" """# "##`, ` """# `, nil},
		{`#"This is a "dog""#`, `This is a "dog"`, nil},
		{"#\"\"\"\n\"\n\"\"\"#", `"`, nil},
		{"#\"\"\"\n\"\"\"\n\"\"\"#", `"""`, nil},
		{"#\"\"\"\n\na\n\n\"\"\"#", "\na\n", nil},
		// Gobble extra \r
		{"#\"\"\"\n\ra\n\r\"\"\"#", `a`, nil},
		{"#\"\"\"\n\r\n\ra\n\r\n\r\"\"\"#", "\na\n", nil},
		// Make sure this works for Windows.
		{"#\"\"\"\r\n\r\na\r\n\r\n\"\"\"#", "\na\n", nil},
		{"#\"\"\"\r\n \r\n a\r\n \r\n \"\"\"#", "\na\n", nil},
		{"#\"\"\"\r\na\r\n\"\"\"#", `a`, nil},
		{"#\"\"\"\r\n\ra\r\n\r\"\"\"#", `a`, nil},
		{`####"   \"####`, `   \`, nil},

		{"```", "", errSyntax},
		{"Hello", "", errSyntax},
		{`"Hello`, "", errUnmatchedQuote},
		{`"""Hello"""`, "", errMissingOpeningNewline},
		{`"""` + "Hello\n" + `"""`, "", errMissingOpeningNewline},
		{`"""` + "\nHello" + `"""`, "", errMissingClosingNewline},
		{"'''\n  Hello\n   '''", "", errInvalidWhitespace},
		{"'''\n   a\n  b\n   '''", "", errInvalidWhitespace},
		{"'''Hello\n'''", "", errMissingOpeningNewline},
		{"'''\nHello'''", "", errMissingClosingNewline},
		{`"Hello""`, "", errSyntax},
		{`#"Hello"`, "", errUnmatchedQuote},
		{`#"Hello'#`, "", errUnmatchedQuote},
		{`#""" """#`, "", errMissingOpeningNewline},
		{`"""` + "\r\n\tHello \\\r", "", errUnmatchedQuote},
	}
	for i, tc := range testCases {
		t.Run(fmt.Sprintf("%d/%s", i, tc.in), func(t *testing.T) {
			if got, err := Unquote(tc.in); err != tc.err {
				t.Errorf("error: got %#v; want %#v", err, tc.err)
			} else if got != tc.out {
				t.Errorf("value: got %q; want %q", got, tc.out)
			}
		})
	}
}

func TestInterpolation(t *testing.T) {
	testCases := []struct {
		quotes string
		in     string
		out    string
		err    error
	}{
		{`""`, `foo\(`, "foo", nil},
		{`"""` + "\n" + `"""`, `foo`, "", errUnmatchedQuote},
		{`#""#`, `foo\#(`, "foo", nil},
		{`#""#`, `foo\(`, "", errUnmatchedQuote},
		{`""`, `foo\(bar`, "", errSyntax},
		{`""`, ``, "", errUnmatchedQuote},
		{`#""#`, `"`, "", errUnmatchedQuote},
		{`#""#`, `\`, "", errUnmatchedQuote},
		{`##""##`, `\'`, "", errUnmatchedQuote},
	}
	for i, tc := range testCases {
		t.Run(fmt.Sprintf("%d/%s/%s", i, tc.quotes, tc.in), func(t *testing.T) {
			info, _, _, _ := ParseQuotes(tc.quotes, tc.quotes)
			if got, err := info.Unquote(tc.in); err != tc.err {
				t.Errorf("error: got %q; want %q", err, tc.err)
			} else if got != tc.out {
				t.Errorf("value: got %q; want %q", got, tc.out)
			}
		})
	}
}

func TestIsDouble(t *testing.T) {
	testCases := []struct {
		quotes string
		double bool
	}{
		{`""`, true},
		{`"""` + "\n" + `"""`, true},
		{`#""#`, true},
		{`''`, false},
		{`'''` + "\n" + `'''`, false},
		{`#''#`, false},
	}
	for i, tc := range testCases {
		t.Run(fmt.Sprintf("%d/%s", i, tc.quotes), func(t *testing.T) {
			info, _, _, err := ParseQuotes(tc.quotes, tc.quotes)
			if err != nil {
				t.Fatal(err)
			}
			if got := info.IsDouble(); got != tc.double {
				t.Errorf("got %v; want %v", got, tc.double)
			}
		})
	}
}
