// Copyright 2020 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"
	"strings"
	"testing"

	"github.com/google/go-cmp/cmp"
)

func TestQuote(t *testing.T) {
	testCases := []struct {
		form  Form
		in    string
		out   string
		lossy bool
	}{
		{form: String, in: "\x00", out: `"\u0000"`},
		{form: String, in: "abc\xffdef", out: `"abc�def"`, lossy: true},
		{form: Bytes, in: "abc\xffdef", out: `'abc\xffdef'`},
		{form: String.WithASCIIOnly(),
			in: "abc\xffdef", out: `"abc\ufffddef"`, lossy: true},
		{form: String, in: "\a\b\f\r\n\t\v", out: `"\a\b\f\r\n\t\v"`},
		{form: String, in: "\"", out: `"\""`},
		{form: String, in: "\\", out: `"\\"`},
		{form: String, in: "\u263a", out: `"☺"`},
		{form: String, in: "\U0010ffff", out: `"\U0010ffff"`},
		{form: String, in: "\x04", out: `"\u0004"`},
		{form: Bytes, in: "\x04", out: `'\x04'`},
		{form: String.WithASCIIOnly(), in: "\u263a", out: `"\u263a"`},
		{form: String.WithGraphicOnly(), in: "\u263a", out: `"☺"`},
		{
			form: String.WithASCIIOnly(),
			in:   "!\u00a0!\u2000!\u3000!",
			out:  `"!\u00a0!\u2000!\u3000!"`,
		},
		{form: String, in: "a\nb", out: `"a\nb"`},
		{form: String.WithTabIndent(3), in: "a\nb", out: `"""
			a
			b
			"""`},
		{form: String.WithTabIndent(3), in: "a", out: `"""
			a
			"""`},
		{form: String.WithTabIndent(3), in: "a\n", out: `"""
			a

			"""`},
		{form: String.WithTabIndent(3), in: "", out: `"""
			"""`},
		{form: String.WithTabIndent(3), in: "\n", out: `"""


			"""`},
		{form: String.WithTabIndent(3), in: "\n\n", out: `"""



			"""`},
		{form: String.WithOptionalTabIndent(3), in: "a", out: `"a"`},
		{form: String.WithOptionalTabIndent(3), in: "a\n", out: `"""
			a

			"""`},

		// Issue #541
		{form: String.WithTabIndent(3), in: "foo\n\"bar\"", out: `"""
			foo
			"bar"
			"""`},
		{form: String.WithTabIndent(3), in: "foo\n\"\"\"bar\"", out: `#"""
			foo
			"""bar"
			"""#`},
		{form: String.WithTabIndent(3), in: "foo\n\"\"\"\"\"###bar\"", out: `####"""
			foo
			"""""###bar"
			"""####`},
		{form: String.WithTabIndent(3), in: "foo\n\"\"\"\r\f\\", out: `#"""
			foo
			"""\#r\#f\#\
			"""#`},
		{form: Bytes.WithTabIndent(3), in: "foo'''\nhello", out: `#'''
			foo'''
			hello
			'''#`},
		{form: Bytes.WithTabIndent(3), in: "foo\n'''\r\f\\", out: `#'''
			foo
			'''\#r\#f\#\
			'''#`},
		{form: Bytes.WithTabIndent(3), in: "a\xc5a", out: `'''
			a\xc5a
			'''`},
		{form: Bytes.WithTabIndent(3), in: "a\xc5'''a", out: `#'''
			a\#xc5'''a
			'''#`},
	}
	for _, tc := range testCases {
		t.Run(fmt.Sprintf("%q", tc.in), func(t *testing.T) {
			got := tc.form.Quote(tc.in)
			if got != tc.out {
				t.Errorf("Quote: %s", cmp.Diff(tc.out, got))
			}

			got = string(tc.form.Append(nil, tc.in))
			if got != tc.out {
				t.Errorf("Append: %s", cmp.Diff(tc.out, got))
			}

			str, err := Unquote(got)
			if err != nil {
				t.Errorf("Roundtrip error: %v", err)
			}

			if !tc.lossy && str != tc.in {
				t.Errorf("Roundtrip: %s", cmp.Diff(tc.in, str))
			}
		})
	}
}

func TestAppendEscaped(t *testing.T) {
	testCases := []struct {
		form Form
		in   string
		out  string
	}{
		{String, "a", "a"},
		{String, "", ""},
		{String.WithTabIndent(2), "", ""},
		{String.WithTabIndent(2), "\n", "\n"},
		{String.WithTabIndent(2), "a\n", "a\n"},
		{String.WithTabIndent(2), "a\nb", "a\n\t\tb"},
	}
	for _, tc := range testCases {
		t.Run(tc.in, func(t *testing.T) {
			buf := tc.form.AppendEscaped(nil, tc.in)
			if got := string(buf); got != tc.out {
				t.Error(cmp.Diff(tc.out, got))
			}
		})
	}
}

func BenchmarkQuote(b *testing.B) {
	inputs := []string{
		"aaaa",
		"aaaaaaa\n\naaaaaa",
		strings.Repeat("aaaaaaaaaaa\n", 1000),
	}

	for _, f := range []Form{
		String,
		Bytes,
		String.WithTabIndent(3),
		String.WithOptionalTabIndent(3),
	} {
		b.Run("", func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				for _, s := range inputs {
					f.Quote(s)
				}
			}
		})
	}
}
