// Copyright (C) 2022-2023  Luke Shumaker <lukeshu@lukeshu.com>
//
// SPDX-License-Identifier: GPL-2.0-or-later

package lowmemjson

import (
	"strings"
	"testing"

	"github.com/stretchr/testify/assert"

	"git.lukeshu.com/go/lowmemjson/internal/fastio"
)

func TestReEncode(t *testing.T) {
	t.Parallel()
	type testcase struct {
		enc ReEncoderConfig
		in  any
		exp string
	}
	testcases := map[string]testcase{
		"basic": {
			enc: ReEncoderConfig{
				Indent:         "\t",
				CompactIfUnder: 10,
			},
			in: map[string][]string{
				"a": {"b", "c"},
				"d": {"eeeeeeeeeeeeeee"},
			},
			exp: `{
	"a": ["b","c"],
	"d": [
		"eeeeeeeeeeeeeee"
	]
}`,
		},
		"arrays1": {
			enc: ReEncoderConfig{
				Indent:                "\t",
				CompactIfUnder:        10,
				ForceTrailingNewlines: true,
			},
			in: []any{
				map[string]any{
					"generation": 123456,
				},
				map[string]any{
					"a": 1,
				},
				map[string]any{
					"generation": 7891011213,
				},
			},
			exp: `[
	{
		"generation": 123456
	},
	{"a":1},
	{
		"generation": 7891011213
	}
]
`,
		},
		"arrays2": {
			enc: ReEncoderConfig{
				Indent:                "\t",
				CompactIfUnder:        15,
				ForceTrailingNewlines: true,
			},
			in: []any{
				map[string]any{
					"a": 1,
					"b": 2,
				},
				map[string]any{
					"generation": 123456,
				},
				map[string]any{
					"generation": 7891011213,
				},
			},
			exp: `[
	{"a":1,"b":2},
	{
		"generation": 123456
	},
	{
		"generation": 7891011213
	}
]
`,
		},
		"arrays3": {
			enc: ReEncoderConfig{
				Indent:                "\t",
				ForceTrailingNewlines: true,
			},
			in: []any{
				map[string]any{
					"a": 1,
				},
				map[string]any{
					"generation": 123456,
				},
				map[string]any{
					"generation": 7891011213,
				},
			},
			exp: `[
	{
		"a": 1
	},
	{
		"generation": 123456
	},
	{
		"generation": 7891011213
	}
]
`,
		},
		"indent-unicode": {
			enc: ReEncoderConfig{
				Prefix: "—",
				Indent: "»",
			},
			in: []int{9},
			exp: `[
—»9
—]`,
		},
		"numbers": {
			enc: ReEncoderConfig{
				Compact:       true,
				CompactFloats: true,
			},
			in: []any{
				Number("1.200e003"),
			},
			exp: `[1.2e3]`,
		},
		"numbers-zero": {
			enc: ReEncoderConfig{
				Compact:       true,
				CompactFloats: true,
			},
			in: []any{
				Number("1.000e000"),
			},
			exp: `[1.0e0]`,
		},
	}
	for tcName, tc := range testcases {
		tc := tc
		t.Run(tcName, func(t *testing.T) {
			t.Parallel()
			var out strings.Builder
			enc := NewEncoder(NewReEncoder(&out, tc.enc))
			assert.NoError(t, enc.Encode(tc.in))
			assert.Equal(t, tc.exp, out.String())
		})
	}
}

func TestReEncodeWriteSize(t *testing.T) {
	t.Parallel()

	multibyteRune := `😂`
	assert.Len(t, multibyteRune, 4)

	input := `"` + multibyteRune + `"`

	t.Run("bytes-bigwrite", func(t *testing.T) {
		t.Parallel()
		var out strings.Builder
		enc := NewReEncoder(&out, ReEncoderConfig{})

		n, err := enc.Write([]byte(input))
		assert.NoError(t, err)
		assert.Equal(t, len(input), n)

		assert.Equal(t, input, out.String())
	})
	t.Run("string-bigwrite", func(t *testing.T) {
		t.Parallel()
		var out strings.Builder
		enc := NewReEncoder(&out, ReEncoderConfig{})

		n, err := enc.WriteString(input)
		assert.NoError(t, err)
		assert.Equal(t, len(input), n)

		assert.Equal(t, input, out.String())
	})

	t.Run("bytes-smallwrites", func(t *testing.T) {
		t.Parallel()
		var out strings.Builder
		enc := NewReEncoder(&out, ReEncoderConfig{})

		var buf [1]byte
		for i := 0; i < len(input); i++ {
			buf[0] = input[i]
			n, err := enc.Write(buf[:])
			assert.NoError(t, err)
			assert.Equal(t, 1, n)
		}

		assert.Equal(t, input, out.String())
	})
	t.Run("string-smallwrites", func(t *testing.T) {
		t.Parallel()
		var out strings.Builder
		enc := NewReEncoder(&out, ReEncoderConfig{})

		for i := 0; i < len(input); i++ {
			n, err := enc.WriteString(input[i : i+1])
			assert.NoError(t, err)
			assert.Equal(t, 1, n)
		}

		assert.Equal(t, input, out.String())
	})
}

func TestReEncoderStackSize(t *testing.T) {
	t.Parallel()

	enc := NewReEncoder(fastio.Discard, ReEncoderConfig{})
	assert.Equal(t, 0, enc.stackSize())

	for i := 0; i < 5; i++ {
		assert.NoError(t, enc.WriteByte('['))
		assert.Equal(t, i+1, enc.stackSize())
		enc.pushWriteBarrier()
		assert.Equal(t, i+2, enc.stackSize())
	}
}