package commontest

import (
	"fmt"
	"strconv"
)

// This file is used by test_factory.go to define a standard set of JSON scalar test values.

type encodingBehavior struct {
	encodeAsHex func(rune) bool
	forParsing  bool
}

type testValue struct {
	name     string
	encoding string
	value    AnyValue
}

type numberTestValueBase struct {
	name             string
	val              float64
	encoding         string
	simplestEncoding string
}

type stringTestValueBase struct {
	name     string
	val      string
	encoding string
}

func makeBoolTestValues() []testValue {
	return []testValue{
		{"bool true", "true", AnyValue{Kind: BoolValue, Bool: true}},
		{"bool false", "false", AnyValue{Kind: BoolValue, Bool: false}},
	}
}

func makeNumberTestValues(encodingBehavior encodingBehavior) []testValue {
	var ret []testValue
	for _, v := range []numberTestValueBase{
		{"zero", 0, "0", ""},
		{"int", 3, "3", ""},
		{"int negative", -3, "-3", ""},
		{"int large", 1603312301195, "1603312301195", ""}, // enough magnitude for a millisecond timestamp
		{"float", 3.5, "3.5", ""},
		{"float negative", -3.5, "-3.5", ""},
		{"float with exp and decimal", 3500, "3.5e3", "3500"},
		{"float with Exp and decimal", 3500, "3.5E3", "3500"},
		{"float with exp+ and decimal", 3500, "3.5e+3", "3500"},
		{"float with exp- and decimal", 0.0035, "3.5e-3", "0.0035"},
		{"float with exp but no decimal", 5000, "5e3", "5000"},
		{"float with Exp but no decimal", 5000, "5E3", "5000"},
		{"float with exp+ but no decimal", 5000, "5e+3", "5000"},
		{"float with exp- but no decimal", 0.005, "5e-3", "0.005"},
	} {
		enc := v.encoding
		if !encodingBehavior.forParsing && v.simplestEncoding != "" {
			enc = v.simplestEncoding
		}
		ret = append(ret, testValue{"number " + v.name, enc, AnyValue{Kind: NumberValue, Number: v.val}})
	}
	return ret
}

func makeStringTestValues(encodingBehavior encodingBehavior, allPermutations bool) []testValue {
	base := []stringTestValueBase{
		{name: "empty", val: "", encoding: `""`},
		{name: "simple", val: "abc", encoding: `"abc"`},
	}
	allEscapeTests := []stringTestValueBase{}
	if allPermutations {
		baseEscapeTests := []stringTestValueBase{
			{val: `"`, encoding: `\"`},
			{val: `\`, encoding: `\\`},
			{val: "\x05", encoding: `\u0005`},
			{val: "\x1c", encoding: `\u001c`},
			{val: "🦜🦄😂🧶😻 yes", encoding: "🦜🦄😂🧶😻 yes"}, // unescaped multi-byte characters are allowed
		}
		addControlChar := func(str, shortEncoding string) {
			hex := strconv.FormatInt(int64(str[0]), 16)
			if len(hex) == 1 {
				hex = "0" + hex
			}
			encodeAsHex := false
			if encodingBehavior.encodeAsHex != nil {
				encodeAsHex = encodingBehavior.encodeAsHex(rune(str[0]))
			}
			if !encodingBehavior.forParsing && !encodeAsHex {
				baseEscapeTests = append(baseEscapeTests, stringTestValueBase{val: str, encoding: shortEncoding})
			}
			if encodingBehavior.forParsing || encodeAsHex {
				baseEscapeTests = append(baseEscapeTests, stringTestValueBase{val: str, encoding: `\u00` + hex})
			}
		}
		addControlChar("\b", `\b`)
		addControlChar("\t", `\t`)
		addControlChar("\n", `\n`)
		addControlChar("\f", `\f`)
		addControlChar("\r", `\r`)
		if encodingBehavior.forParsing {
			// These escapes are not used when writing, but may be encountered when parsing
			baseEscapeTests = append(baseEscapeTests, stringTestValueBase{val: "/", encoding: `\/`})
			baseEscapeTests = append(baseEscapeTests, stringTestValueBase{val: "も", encoding: `\u3082`})
		}
		for _, et := range baseEscapeTests {
			allEscapeTests = append(allEscapeTests, et)
			addTransformed := func(valFn func(string) string, encFn func(string) string) {
				tt := stringTestValueBase{
					val:      valFn(et.val),
					encoding: encFn(et.encoding),
				}
				allEscapeTests = append(allEscapeTests, tt)
			}
			for _, f := range []string{"%sabcd", "abcd%s", "ab%scd"} {
				addTransformed(func(s string) string { return fmt.Sprintf(f, s) },
					func(s string) string { return fmt.Sprintf(f, s) })
			}
			for _, et2 := range baseEscapeTests {
				for _, f := range []string{"%s%sabcd", "ab%s%scd", "a%sbc%sd", "abcd%s%s"} {
					addTransformed(func(s string) string { return fmt.Sprintf(f, s, et2.val) },
						func(s string) string { return fmt.Sprintf(f, s, et2.encoding) })
				}
			}
		}
	} else {
		// When we're testing nested data structures, we don't need to cover all those permutations of
		// escape sequences-- we can assume that the same string encoding logic applies as it would for
		// a single value.
		allEscapeTests = []stringTestValueBase{{val: "simple\tescape", encoding: `simple\tescape`}}
	}
	for i, et := range allEscapeTests {
		st := stringTestValueBase{
			name:     fmt.Sprintf("with escapes %d", i+1),
			val:      et.val,
			encoding: `"` + et.encoding + `"`,
		}
		base = append(base, st)
	}
	ret := make([]testValue, 0, len(base))
	for _, b := range base {
		ret = append(ret, testValue{name: "string " + b.name, encoding: b.encoding,
			value: AnyValue{Kind: StringValue, String: b.val}})
	}
	return ret
}

func MakeWhitespaceOptions() map[string]string {
	return map[string]string{"spaces": "  ", "tab": "\t", "newline": "\n"}
}
