package parse

import (
	"fmt"
	"testing"

	"git.makaay.nl/mauricem/go-parsekit/parse"
)

func TestStartString(t *testing.T) {
	parser := newParser()
	wrapper := func(p *parse.API) { parser.parseString(p) }
	testParse(t, parser, wrapper, parseTest{"(not a string)", "{}", "unexpected input (expected a string value) at start of file"})
}

func TestStartBasicString(t *testing.T) {
	parser := newParser()
	wrapper := func(p *parse.API) { parser.parseBasicString("xyz", p) }
	testParse(t, parser, wrapper, parseTest{"(not a string)", "{}", "unexpected input (expected opening quotation marks) at start of file"})
}

func TestStartLiteralString(t *testing.T) {
	parser := newParser()
	wrapper := func(p *parse.API) { parser.parseLiteralString("xyz", p) }
	testParse(t, parser, wrapper, parseTest{"(not a string)", "{}", "unexpected input (expected opening single quote) at start of file"})
}

func TestStartMultiLineBasicString(t *testing.T) {
	parser := newParser()
	wrapper := func(p *parse.API) { parser.parseMultiLineBasicString(p) }
	testParse(t, parser, wrapper, parseTest{"(not a string)", "{}", "unexpected input (expected opening three quotation marks) at start of file"})
}

func TestStartMultiLineLiteralString(t *testing.T) {
	parser := newParser()
	wrapper := func(p *parse.API) { parser.parseMultiLineLiteralString(p) }
	testParse(t, parser, wrapper, parseTest{"(not a string)", "{}", "unexpected input (expected opening three single quotes) at start of file"})
}

func TestString(t *testing.T) {
	for _, test := range []parseTest{
		{`x=no start quote"`, `{}`, `unexpected input (expected a value) at line 1, column 3`},
		{`x="basic s\tring"`, `{"x": "basic s\tring"}`, ``},
		{"x=\"\"\"\n  basic multi-line\n  string value\n\"\"\"", `{"x": "  basic multi-line\n  string value\n"}`, ``},
		{`x='literal s\tring'`, `{"x": "literal s\\tring"}`, ``},
		{"x='''\n  literal multi-line\n  string value\n'''", `{"x": "  literal multi-line\n  string value\n"}`, ``},
	} {
		p := newParser()
		testParse(t, p, p.startDocument, test)
	}
}

func TestBasicString(t *testing.T) {
	for _, test := range []parseTest{
		{`x="no end quote`, `{}`, `unexpected end of file (expected closing quotation marks) at line 1, column 16`},
		{`x=""`, `{"x": ""}`, ``},
		{`x="simple string"`, `{"x": "simple string"}`, ``},
		{`x="with\tsome\r\nvalid escapes\b"`, `{"x": "with\tsome\r\nvalid escapes\b"}`, ``},
		{`x="with an \invalid escape"`, `{}`, `invalid escape sequence at line 1, column 12`},
		{`x="A cool UTF8 ƃuıɹʇs"`, `{"x": "A cool UTF8 ƃuıɹʇs"}`, ``},
		{`x="A string with UTF8 escape \u2318"`, `{"x": "A string with UTF8 escape ⌘"}`, ``},
		{"x=\"Invalid character for UTF \xcd\"", `{}`, `invalid UTF8 rune at line 1, column 30`},
		{`x="\uD801 is not a valid rune"`, `{}`, `invalid UTF8 escape '\uD801' at line 1, column 10`},
		{`x="\U00D80000 is not a valid rune"`, `{}`, `invalid UTF8 escape '\U00D80000' at line 1, column 14`},
		{"x=\"Character that mus\t be escaped\"", `{}`, `invalid character in string value: '\t' (must be escaped) at line 1, column 22`},
		{"x=\"Character that must be escaped \u0000\"", `{}`, `invalid character in string value: '\x00' (must be escaped) at line 1, column 35`},
		{"x=\"Character that must be escaped \x7f\"", `{}`, `invalid character in string value: '\u007f' (must be escaped) at line 1, column 35`},
		{`x="\b\t\n\f\r\"\\\u2318\U00002318" # all escape codes`, `{"x": "\b\t\n\f\r\"\\⌘⌘"}`, ``},
	} {
		p := newParser()
		testParse(t, p, p.startDocument, test)
	}
}

func TestMultiLineBasicString(t *testing.T) {
	for _, test := range []parseTest{
		{`x="""missing close quote""`, `{}`, `unexpected end of file (expected closing three quotation marks) at line 1, column 27`},
		{`x=""""""`, `{"x": ""}`, ``},
		{"x=\"\"\"\n\"\"\"", `{"x": ""}`, ``},
		{"x=\"\"\"\r\n\r\n\"\"\"", `{"x": "\n"}`, ``},
		{`x="""\"\"\"\""""`, `{"x": "\"\"\"\""}`, ``},
		{"x=\"\"\"\nThe quick brown \\\n\n\n  \t  fox jumps over \\\n\t the lazy dog.\\\n   \"\"\"", `{"x": "The quick brown fox jumps over the lazy dog."}`, ``},
		{"x=\"\"\"No control chars \f allowed\"\"\"", `{}`, `invalid character in multi-line basic string: '\f' (must be escaped) at line 1, column 23`},
		{"x=\"\"\"Escaping control chars\\nis valid\"\"\"", `{"x": "Escaping control chars\nis valid"}`, ``},
		{"x=\"\"\"Invalid escaping \\is not allowed\"\"\"", `{}`, `invalid escape sequence at line 1, column 23`},
		{"x=\"\"\"Invalid rune \xcd\"\"\"", `{}`, `invalid UTF8 rune at line 1, column 19`},
	} {
		p := newParser()
		testParse(t, p, p.startDocument, test)
	}
}

func TestLiteralString(t *testing.T) {
	for _, test := range []parseTest{
		{`x='missing close quote`, `{}`, `unexpected end of file (expected closing single quote) at line 1, column 23`},
		{`x=''`, `{"x": ""}`, ``},
		{`x='simple'`, `{"x": "simple"}`, ``},
		{`x='C:\Users\nodejs\templates'`, `{"x": "C:\\Users\\nodejs\\templates"}`, ``},
		{`x='\\ServerX\admin$\system32\'`, `{"x": "\\\\ServerX\\admin$\\system32\\"}`, ``},
		{`x='Tom "Dubs" Preston-Werner'`, `{"x": "Tom \"Dubs\" Preston-Werner"}`, ``},
		{`x='<\i\c*\s*>'`, `{"x": "<\\i\\c*\\s*>"}`, ``},
		{"x='No cont\rol chars allowed'", `{}`, `invalid character in string value: '\r' (no control chars allowed, except for tab) at line 1, column 11`},
		{"x='Except\tfor\ttabs'", `{"x": "Except\tfor\ttabs"}`, ``},
		{"x='Invalid rune \xcd'", `{}`, `invalid UTF8 rune at line 1, column 17`},
	} {
		p := newParser()
		testParse(t, p, p.startDocument, test)
	}
}

func TestMultiLineLiteralString(t *testing.T) {
	for _, test := range []parseTest{
		{`x='''missing close quote''`, `{}`, `unexpected end of file (expected closing three single quotes) at line 1, column 27`},
		{`x=''''''`, `{"x": ""}`, ``},
		{"x='''\n'''", `{"x": ""}`, ``},
		{`x='''I [dw]on't need \d{2} apples'''`, `{"x": "I [dw]on't need \\d{2} apples"}`, ``},
		{"x='''\nThere can\nbe newlines\r\nand \ttabs!\r\n'''", `{"x": "There can\nbe newlines\nand \ttabs!\n"}`, ``},
		{"x='''No other \f control characters'''", `{}`, `invalid character in literal string: '\f' (no control chars allowed, except for tab and newline) at line 1, column 15`},
		{"x='''No invalid runes allowed \xcd'''", `{}`, `invalid UTF8 rune at line 1, column 31`},
	} {
		p := newParser()
		testParse(t, p, p.startDocument, test)
	}
}

func TestBasicStringWithUnescapedControlCharacters(t *testing.T) {
	// A quick check for almost all characters that must be escaped.
	// The missing one (\x7f) is covered in the previous test.
	for i := 0x00; i <= 0x1F; i++ {
		p := newParser()
		input := fmt.Sprintf(`x="%c"`, rune(i))
		expected := fmt.Sprintf(`invalid character in string value: %q (must be escaped) at line 1, column 4`, rune(i))
		testParse(t, p, p.startDocument, parseTest{input, "{}", expected})
	}
}
