// Copyright 2023 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build goexperiment.jsonv2

package jsonopts_test

import (
	"reflect"
	"testing"

	"encoding/json/internal/jsonflags"
	. "encoding/json/internal/jsonopts"
	"encoding/json/jsontext"
	"encoding/json/v2"
)

func makeFlags(f ...jsonflags.Bools) (fs jsonflags.Flags) {
	for _, f := range f {
		fs.Set(f)
	}
	return fs
}

func TestJoin(t *testing.T) {
	tests := []struct {
		in            Options
		excludeCoders bool
		want          *Struct
	}{{
		in:   jsonflags.AllowInvalidUTF8 | 1,
		want: &Struct{Flags: makeFlags(jsonflags.AllowInvalidUTF8 | 1)},
	}, {
		in: jsonflags.Multiline | 0,
		want: &Struct{
			Flags: makeFlags(jsonflags.AllowInvalidUTF8|1, jsonflags.Multiline|0)},
	}, {
		in: Indent("\t"), // implicitly sets Multiline=true
		want: &Struct{
			Flags:       makeFlags(jsonflags.AllowInvalidUTF8 | jsonflags.Multiline | jsonflags.Indent | 1),
			CoderValues: CoderValues{Indent: "\t"},
		},
	}, {
		in: &Struct{
			Flags: makeFlags(jsonflags.Multiline|jsonflags.EscapeForJS|0, jsonflags.AllowInvalidUTF8|1),
		},
		want: &Struct{
			Flags:       makeFlags(jsonflags.AllowInvalidUTF8|jsonflags.Indent|1, jsonflags.Multiline|jsonflags.EscapeForJS|0),
			CoderValues: CoderValues{Indent: "\t"},
		},
	}, {
		in: &DefaultOptionsV1,
		want: func() *Struct {
			v1 := DefaultOptionsV1
			v1.Flags.Set(jsonflags.Indent | 1)
			v1.Flags.Set(jsonflags.Multiline | 0)
			v1.Indent = "\t"
			return &v1
		}(), // v1 fully replaces before (except for whitespace related flags)
	}, {
		in: &DefaultOptionsV2,
		want: func() *Struct {
			v2 := DefaultOptionsV2
			v2.Flags.Set(jsonflags.Indent | 1)
			v2.Flags.Set(jsonflags.Multiline | 0)
			v2.Indent = "\t"
			return &v2
		}(), // v2 fully replaces before (except for whitespace related flags)
	}, {
		in: jsonflags.Deterministic | jsonflags.AllowInvalidUTF8 | 1, excludeCoders: true,
		want: func() *Struct {
			v2 := DefaultOptionsV2
			v2.Flags.Set(jsonflags.Deterministic | 1)
			v2.Flags.Set(jsonflags.Indent | 1)
			v2.Flags.Set(jsonflags.Multiline | 0)
			v2.Indent = "\t"
			return &v2
		}(),
	}, {
		in: jsontext.WithIndentPrefix("    "), excludeCoders: true,
		want: func() *Struct {
			v2 := DefaultOptionsV2
			v2.Flags.Set(jsonflags.Deterministic | 1)
			v2.Flags.Set(jsonflags.Indent | 1)
			v2.Flags.Set(jsonflags.Multiline | 0)
			v2.Indent = "\t"
			return &v2
		}(),
	}, {
		in: jsontext.WithIndentPrefix("    "), excludeCoders: false,
		want: func() *Struct {
			v2 := DefaultOptionsV2
			v2.Flags.Set(jsonflags.Deterministic | 1)
			v2.Flags.Set(jsonflags.Indent | 1)
			v2.Flags.Set(jsonflags.IndentPrefix | 1)
			v2.Flags.Set(jsonflags.Multiline | 1)
			v2.Indent = "\t"
			v2.IndentPrefix = "    "
			return &v2
		}(),
	}, {
		in: &Struct{
			Flags: jsonflags.Flags{
				Presence: uint64(jsonflags.Deterministic | jsonflags.Indent | jsonflags.IndentPrefix),
				Values:   uint64(jsonflags.Indent | jsonflags.IndentPrefix),
			},
			CoderValues: CoderValues{Indent: "  ", IndentPrefix: "  "},
		},
		excludeCoders: true,
		want: func() *Struct {
			v2 := DefaultOptionsV2
			v2.Flags.Set(jsonflags.Indent | 1)
			v2.Flags.Set(jsonflags.IndentPrefix | 1)
			v2.Flags.Set(jsonflags.Multiline | 1)
			v2.Indent = "\t"
			v2.IndentPrefix = "    "
			return &v2
		}(),
	}, {
		in: &Struct{
			Flags: jsonflags.Flags{
				Presence: uint64(jsonflags.Deterministic | jsonflags.Indent | jsonflags.IndentPrefix),
				Values:   uint64(jsonflags.Indent | jsonflags.IndentPrefix),
			},
			CoderValues: CoderValues{Indent: "  ", IndentPrefix: "  "},
		},
		excludeCoders: false,
		want: func() *Struct {
			v2 := DefaultOptionsV2
			v2.Flags.Set(jsonflags.Indent | 1)
			v2.Flags.Set(jsonflags.IndentPrefix | 1)
			v2.Flags.Set(jsonflags.Multiline | 1)
			v2.Indent = "  "
			v2.IndentPrefix = "  "
			return &v2
		}(),
	}}
	got := new(Struct)
	for i, tt := range tests {
		if tt.excludeCoders {
			got.JoinWithoutCoderOptions(tt.in)
		} else {
			got.Join(tt.in)
		}
		if !reflect.DeepEqual(got, tt.want) {
			t.Fatalf("%d: Join:\n\tgot:  %+v\n\twant: %+v", i, got, tt.want)
		}
	}
}

func TestGet(t *testing.T) {
	opts := &Struct{
		Flags:        makeFlags(jsonflags.Indent|jsonflags.Deterministic|jsonflags.Marshalers|1, jsonflags.Multiline|0),
		CoderValues:  CoderValues{Indent: "\t"},
		ArshalValues: ArshalValues{Marshalers: new(json.Marshalers)},
	}
	if v, ok := json.GetOption(nil, jsontext.AllowDuplicateNames); v || ok {
		t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok)
	}
	if v, ok := json.GetOption(jsonflags.AllowInvalidUTF8|0, jsontext.AllowDuplicateNames); v || ok {
		t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok)
	}
	if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|0, jsontext.AllowDuplicateNames); v || !ok {
		t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, true)", v, ok)
	}
	if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|1, jsontext.AllowDuplicateNames); !v || !ok {
		t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (true, true)", v, ok)
	}
	if v, ok := json.GetOption(Indent(""), jsontext.AllowDuplicateNames); v || ok {
		t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok)
	}
	if v, ok := json.GetOption(Indent(" "), jsontext.WithIndent); v != " " || !ok {
		t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want (" ", true)`, v, ok)
	}
	if v, ok := json.GetOption(jsonflags.AllowDuplicateNames|1, jsontext.WithIndent); v != "" || ok {
		t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want ("", false)`, v, ok)
	}
	if v, ok := json.GetOption(opts, jsontext.AllowDuplicateNames); v || ok {
		t.Errorf("GetOption(..., AllowDuplicateNames) = (%v, %v), want (false, false)", v, ok)
	}
	if v, ok := json.GetOption(opts, json.Deterministic); !v || !ok {
		t.Errorf("GetOption(..., Deterministic) = (%v, %v), want (true, true)", v, ok)
	}
	if v, ok := json.GetOption(opts, jsontext.Multiline); v || !ok {
		t.Errorf("GetOption(..., Multiline) = (%v, %v), want (false, true)", v, ok)
	}
	if v, ok := json.GetOption(opts, jsontext.AllowInvalidUTF8); v || ok {
		t.Errorf("GetOption(..., AllowInvalidUTF8) = (%v, %v), want (false, false)", v, ok)
	}
	if v, ok := json.GetOption(opts, jsontext.WithIndent); v != "\t" || !ok {
		t.Errorf(`GetOption(..., WithIndent) = (%q, %v), want ("\t", true)`, v, ok)
	}
	if v, ok := json.GetOption(opts, jsontext.WithIndentPrefix); v != "" || ok {
		t.Errorf(`GetOption(..., WithIndentPrefix) = (%q, %v), want ("", false)`, v, ok)
	}
	if v, ok := json.GetOption(opts, json.WithMarshalers); v == nil || !ok {
		t.Errorf(`GetOption(..., WithMarshalers) = (%v, %v), want (non-nil, true)`, v, ok)
	}
	if v, ok := json.GetOption(opts, json.WithUnmarshalers); v != nil || ok {
		t.Errorf(`GetOption(..., WithUnmarshalers) = (%v, %v), want (nil, false)`, v, ok)
	}
	if v, ok := json.GetOption(json.DefaultOptionsV2(), json.WithMarshalers); v != nil || ok {
		t.Errorf(`GetOption(..., WithMarshalers) = (%v, %v), want (nil, false)`, v, ok)
	}
}

var sink struct {
	Bool       bool
	String     string
	Marshalers *json.Marshalers
}

func BenchmarkGetBool(b *testing.B) {
	b.ReportAllocs()
	opts := json.DefaultOptionsV2()
	for range b.N {
		sink.Bool, sink.Bool = json.GetOption(opts, jsontext.AllowDuplicateNames)
	}
}

func BenchmarkGetIndent(b *testing.B) {
	b.ReportAllocs()
	opts := json.DefaultOptionsV2()
	for range b.N {
		sink.String, sink.Bool = json.GetOption(opts, jsontext.WithIndent)
	}
}

func BenchmarkGetMarshalers(b *testing.B) {
	b.ReportAllocs()
	opts := json.JoinOptions(json.DefaultOptionsV2(), json.WithMarshalers(nil))
	for range b.N {
		sink.Marshalers, sink.Bool = json.GetOption(opts, json.WithMarshalers)
	}
}
