package json

import (
	"bytes"
	"strings"
	"testing"
)

func TestParseString(t *testing.T) {
	tests := []struct {
		in  string
		out string
		ext string
		err string
	}{
		{`""`, `""`, ``, ``},
		{`"1234567890"`, `"1234567890"`, ``, ``},
		{`"Hello World!"`, `"Hello World!"`, ``, ``},
		{`"Hello\"World!"`, `"Hello\"World!"`, ``, ``},
		{`"\\"`, `"\\"`, ``, ``},
		{`"\u0061\u0062\u0063"`, `"\u0061\u0062\u0063"`, ``, ``},
		{`"\u0"`, ``, ``, `json: unicode code point must have at least 4 characters: 0"`},
	}

	for _, test := range tests {
		t.Run(test.in, func(t *testing.T) {
			out, ext, err := parseString([]byte(test.in))

			if test.err == "" {
				if err != nil {
					t.Errorf("%s => %s", test.in, err)
					return
				}
			} else {
				if s := err.Error(); s != test.err {
					t.Error("invalid error")
					t.Logf("expected: %s", test.err)
					t.Logf("found:    %s", s)
				}
			}

			if s := string(out); s != test.out {
				t.Error("invalid output")
				t.Logf("expected: %s", test.out)
				t.Logf("found:    %s", s)
			}

			if s := string(ext); s != test.ext {
				t.Error("invalid extra bytes")
				t.Logf("expected: %s", test.ext)
				t.Logf("found:    %s", s)
			}
		})
	}
}

func TestParseStringUnquote(t *testing.T) {
	tests := []struct {
		in  string
		out string
		ext string
	}{
		{`""`, ``, ``},
		{`"1234567890"`, `1234567890`, ``},
		{`"Hello World!"`, `Hello World!`, ``},
		{`"Hello\"World!"`, `Hello"World!`, ``},
		{`"\\"`, `\`, ``},
		{`"\u0061\u0062\u0063"`, `abc`, ``},
	}

	for _, test := range tests {
		t.Run(test.in, func(t *testing.T) {
			out, ext, _, err := parseStringUnquote([]byte(test.in), nil)

			if err != nil {
				t.Errorf("%s => %s", test.in, err)
				return
			}

			if s := string(out); s != test.out {
				t.Error("invalid output")
				t.Logf("expected: %s", test.out)
				t.Logf("found:    %s", s)
			}

			if s := string(ext); s != test.ext {
				t.Error("invalid extra bytes")
				t.Logf("expected: %s", test.ext)
				t.Logf("found:    %s", s)
			}
		})
	}
}

func TestAppendToLower(t *testing.T) {
	tests := []string{
		"",
		"A",
		"a",
		"__segment_internal",
		"someFieldWithALongBName",
		"Hello World!",
		"Hello\"World!",
		"Hello\\World!",
		"Hello\nWorld!",
		"Hello\rWorld!",
		"Hello\tWorld!",
		"Hello\bWorld!",
		"Hello\fWorld!",
		"你好",
		"<",
		">",
		"&",
		"\u001944",
		"\u00c2e>",
		"\u00c2V?",
		"\u000e=8",
		"\u001944\u00c2e>\u00c2V?\u000e=8",
		"ir\u001bQJ\u007f\u0007y\u0015)",
	}

	for _, test := range tests {
		s1 := strings.ToLower(test)
		s2 := string(appendToLower(nil, []byte(test)))

		if s1 != s2 {
			t.Error("lowercase values mismatch")
			t.Log("expected:", s1)
			t.Log("found:   ", s2)
		}
	}
}

func BenchmarkParseString(b *testing.B) {
	s := []byte(`"__segment_internal"`)

	for i := 0; i != b.N; i++ {
		parseString(s)
	}
}

func BenchmarkToLower(b *testing.B) {
	s := []byte("someFieldWithALongName")

	for i := 0; i != b.N; i++ {
		bytes.ToLower(s)
	}
}

func BenchmarkAppendToLower(b *testing.B) {
	a := []byte(nil)
	s := []byte("someFieldWithALongName")

	for i := 0; i != b.N; i++ {
		a = appendToLower(a[:0], s)
	}
}

var benchmarkHasPrefixString = []byte("some random string")
var benchmarkHasPrefixResult = false

func BenchmarkHasPrefix(b *testing.B) {
	for i := 0; i < b.N; i++ {
		benchmarkHasPrefixResult = hasPrefix(benchmarkHasPrefixString, "null")
	}
}

func BenchmarkHasNullPrefix(b *testing.B) {
	for i := 0; i < b.N; i++ {
		benchmarkHasPrefixResult = hasNullPrefix(benchmarkHasPrefixString)
	}
}

func BenchmarkHasTruePrefix(b *testing.B) {
	for i := 0; i < b.N; i++ {
		benchmarkHasPrefixResult = hasTruePrefix(benchmarkHasPrefixString)
	}
}

func BenchmarkHasFalsePrefix(b *testing.B) {
	for i := 0; i < b.N; i++ {
		benchmarkHasPrefixResult = hasFalsePrefix(benchmarkHasPrefixString)
	}
}

func BenchmarkParseStringEscapeNone(b *testing.B) {
	var j = []byte(`"` + strings.Repeat(`a`, 1000) + `"`)
	var s string
	b.SetBytes(int64(len(j)))

	for i := 0; i < b.N; i++ {
		if err := Unmarshal(j, &s); err != nil {
			b.Fatal(err)
		}
		s = ""
	}
}

func BenchmarkParseStringEscapeOne(b *testing.B) {
	var j = []byte(`"` + strings.Repeat(`a`, 998) + `\n"`)
	var s string
	b.SetBytes(int64(len(j)))

	for i := 0; i < b.N; i++ {
		if err := Unmarshal(j, &s); err != nil {
			b.Fatal(err)
		}
		s = ""
	}
}

func BenchmarkParseStringEscapeAll(b *testing.B) {
	var j = []byte(`"` + strings.Repeat(`\`, 1000) + `"`)
	var s string
	b.SetBytes(int64(len(j)))

	for i := 0; i < b.N; i++ {
		if err := Unmarshal(j, &s); err != nil {
			b.Fatal(err)
		}
		s = ""
	}
}
