package sjson

import (
	"bytes"
	"encoding/hex"
	gojson "encoding/json"
	"fmt"
	"math/rand"
	"strings"
	"testing"
	"time"

	"github.com/Jeffail/gabs"

	jlexer "github.com/mailru/easyjson/jlexer"
	jwriter "github.com/mailru/easyjson/jwriter"
	fflib "github.com/pquerna/ffjson/fflib/v1"
)

func TestInvalidPaths(t *testing.T) {
	var err error
	_, err = SetRaw(`{"hello":"world"}`, "", `"planet"`)
	if err == nil || err.Error() != "path cannot be empty" {
		t.Fatalf("expecting '%v', got '%v'", "path cannot be empty", err)
	}
	_, err = SetRaw("", "name.last.#", "")
	if err == nil || err.Error() != "array access character not allowed in path" {
		t.Fatalf("expecting '%v', got '%v'", "array access character not allowed in path", err)
	}
	_, err = SetRaw("", "name.last.\\1#", "")
	if err == nil || err.Error() != "array access character not allowed in path" {
		t.Fatalf("expecting '%v', got '%v'", "array access character not allowed in path", err)
	}
	_, err = SetRaw("", "name.las?t", "")
	if err == nil || err.Error() != "wildcard characters not allowed in path" {
		t.Fatalf("expecting '%v', got '%v'", "wildcard characters not allowed in path", err)
	}
	_, err = SetRaw("", "name.la\\s?t", "")
	if err == nil || err.Error() != "wildcard characters not allowed in path" {
		t.Fatalf("expecting '%v', got '%v'", "wildcard characters not allowed in path", err)
	}
	_, err = SetRaw("", "name.las*t", "")
	if err == nil || err.Error() != "wildcard characters not allowed in path" {
		t.Fatalf("expecting '%v', got '%v'", "wildcard characters not allowed in path", err)
	}
	_, err = SetRaw("", "name.las\\a*t", "")
	if err == nil || err.Error() != "wildcard characters not allowed in path" {
		t.Fatalf("expecting '%v', got '%v'", "wildcard characters not allowed in path", err)
	}
}

const (
	setRaw    = 1
	setBool   = 2
	setInt    = 3
	setFloat  = 4
	setString = 5
	setDelete = 6
)

func testRaw(t *testing.T, kind int, expect, json, path string, value interface{}) {
	var json2 string
	var err error
	switch kind {
	default:
		json2, err = Set(json, path, value)
	case setRaw:
		json2, err = SetRaw(json, path, value.(string))
	case setDelete:
		json2, err = Delete(json, path)
	}
	if err != nil {
		t.Fatal(err)
	} else if json2 != expect {
		t.Fatalf("expected '%v', got '%v'", expect, json2)
	}

	var json3 []byte
	switch kind {
	default:
		json3, err = SetBytes([]byte(json), path, value)
	case setRaw:
		json3, err = SetRawBytes([]byte(json), path, []byte(value.(string)))
	case setDelete:
		json3, err = DeleteBytes([]byte(json), path)
	}
	if err != nil {
		t.Fatal(err)
	} else if string(json3) != expect {
		t.Fatalf("expected '%v', got '%v'", expect, string(json3))
	}
}
func TestBasic(t *testing.T) {
	testRaw(t, setRaw, `[{"hiw":"planet","hi":"world"}]`, `[{"hi":"world"}]`, "0.hiw", `"planet"`)
	testRaw(t, setRaw, `[true]`, ``, "0", `true`)
	testRaw(t, setRaw, `[null,true]`, ``, "1", `true`)
	testRaw(t, setRaw, `[1,null,true]`, `[1]`, "2", `true`)
	testRaw(t, setRaw, `[1,true,false]`, `[1,null,false]`, "1", `true`)
	testRaw(t, setRaw,
		`[1,{"hello":"when","this":[0,null,2]},false]`,
		`[1,{"hello":"when","this":[0,1,2]},false]`,
		"1.this.1", `null`)
	testRaw(t, setRaw,
		`{"a":1,"b":{"hello":"when","this":[0,null,2]},"c":false}`,
		`{"a":1,"b":{"hello":"when","this":[0,1,2]},"c":false}`,
		"b.this.1", `null`)
	testRaw(t, setRaw,
		`{"a":1,"b":{"hello":"when","this":[0,null,2,null,4]},"c":false}`,
		`{"a":1,"b":{"hello":"when","this":[0,null,2]},"c":false}`,
		"b.this.4", `4`)
	testRaw(t, setRaw,
		`{"b":{"this":[null,null,null,null,4]}}`,
		``,
		"b.this.4", `4`)
	testRaw(t, setRaw,
		`[null,{"this":[null,null,null,null,4]}]`,
		``,
		"1.this.4", `4`)
	testRaw(t, setRaw,
		`{"1":{"this":[null,null,null,null,4]}}`,
		``,
		":1.this.4", `4`)
	testRaw(t, setRaw,
		`{":1":{"this":[null,null,null,null,4]}}`,
		``,
		"\\:1.this.4", `4`)
	testRaw(t, setRaw,
		`{":\1":{"this":[null,null,null,null,{".HI":4}]}}`,
		``,
		"\\:\\\\1.this.4.\\.HI", `4`)
	testRaw(t, setRaw,
		`{"b":{"this":{"😇":""}}}`,
		``,
		"b.this.😇", `""`)
	testRaw(t, setRaw,
		`[ 1,2  ,3]`,
		`  [ 1,2  ] `,
		"-1", `3`)
	testRaw(t, setInt, `[1234]`, ``, `0`, int64(1234))
	testRaw(t, setFloat, `[1234.5]`, ``, `0`, float64(1234.5))
	testRaw(t, setString, `["1234.5"]`, ``, `0`, "1234.5")
	testRaw(t, setBool, `[true]`, ``, `0`, true)
	testRaw(t, setBool, `[null]`, ``, `0`, nil)
	testRaw(t, setString, `{"arr":[1]}`, ``, `arr.-1`, 1)
}

func TestDelete(t *testing.T) {
	testRaw(t, setDelete, `[456]`, `[123,456]`, `0`, nil)
	testRaw(t, setDelete, `[123,789]`, `[123,456,789]`, `1`, nil)
	testRaw(t, setDelete, `[123,456]`, `[123,456,789]`, `-1`, nil)
	testRaw(t, setDelete, `{"a":[123,456]}`, `{"a":[123,456,789]}`, `a.-1`, nil)
	testRaw(t, setDelete, `{"and":"another"}`, `{"this":"that","and":"another"}`, `this`, nil)
	testRaw(t, setDelete, `{"this":"that"}`, `{"this":"that","and":"another"}`, `and`, nil)
	testRaw(t, setDelete, `{}`, `{"and":"another"}`, `and`, nil)
	testRaw(t, setDelete, `{"1":"2"}`, `{"1":"2"}`, `3`, nil)
}

// TestRandomData is a fuzzing test that throws random data at SetRaw
// function looking for panics.
func TestRandomData(t *testing.T) {
	var lstr string
	defer func() {
		if v := recover(); v != nil {
			println("'" + hex.EncodeToString([]byte(lstr)) + "'")
			println("'" + lstr + "'")
			panic(v)
		}
	}()
	rand.Seed(time.Now().UnixNano())
	b := make([]byte, 200)
	for i := 0; i < 2000000; i++ {
		n, err := rand.Read(b[:rand.Int()%len(b)])
		if err != nil {
			t.Fatal(err)
		}
		lstr = string(b[:n])
		SetRaw(lstr, "zzzz.zzzz.zzzz", "123")
	}
}

var exampleJSON = `
{
    "sha": "d25341478381063d1c76e81b3a52e0592a7c997f",
    "commit": {
      "author": {
        "name": "Tom Tom Anderson",
        "email": "tomtom@anderson.edu",
        "date": "2013-06-22T16:30:59Z"
      },
      "committer": {
        "name": "Tom Tom Anderson",
        "email": "jeffditto@anderson.edu",
        "date": "2013-06-22T16:30:59Z"
      },
      "message": "Merge pull request #162 from stedolan/utf8-fixes\n\nUtf8 fixes. Closes #161",
      "tree": {
        "sha": "6ab697a8dfb5a96e124666bf6d6213822599fb40",
        "url": "https://api.github.com/repos/stedolan/jq/git/trees/6ab697a8dfb5a96e124666bf6d6213822599fb40"
      },
      "url": "https://api.github.com/repos/stedolan/jq/git/commits/d25341478381063d1c76e81b3a52e0592a7c997f",
      "comment_count": 0
    }
}
`
var path = "commit.committer.email"
var value = "tomtom@anderson.com"
var rawValue = `"tomtom@anderson.com"`
var rawValueBytes = []byte(rawValue)
var expect = strings.Replace(exampleJSON, "jeffditto@anderson.edu", "tomtom@anderson.com", 1)
var jsonBytes = []byte(exampleJSON)
var jsonBytes2 = []byte(exampleJSON)
var expectBytes = []byte(expect)
var opts = &Options{Optimistic: true}
var optsInPlace = &Options{Optimistic: true, ReplaceInPlace: true}

func BenchmarkSet(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		res, err := Set(exampleJSON, path, value)
		if err != nil {
			t.Fatal(err)
		}
		if res != expect {
			t.Fatal("expected '%v', got '%v'", expect, res)
		}
	}
}

func BenchmarkSetRaw(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		res, err := SetRaw(exampleJSON, path, rawValue)
		if err != nil {
			t.Fatal(err)
		}
		if res != expect {
			t.Fatal("expected '%v', got '%v'", expect, res)
		}
	}
}

func BenchmarkSetBytes(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		res, err := SetBytes(jsonBytes, path, value)
		if err != nil {
			t.Fatal(err)
		}
		if bytes.Compare(res, expectBytes) != 0 {
			t.Fatal("expected '%v', got '%v'", expect, res)
		}
	}
}

func BenchmarkSetRawBytes(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		res, err := SetRawBytes(jsonBytes, path, rawValueBytes)
		if err != nil {
			t.Fatal(err)
		}
		if bytes.Compare(res, expectBytes) != 0 {
			t.Fatal("expected '%v', got '%v'", expect, res)
		}
	}
}

func BenchmarkSetOptimistic(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		res, err := SetOptions(exampleJSON, path, value, opts)
		if err != nil {
			t.Fatal(err)
		}
		if res != expect {
			t.Fatal("expected '%v', got '%v'", expect, res)
		}
	}
}

func BenchmarkSetInPlace(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		res, err := SetOptions(exampleJSON, path, value, optsInPlace)
		if err != nil {
			t.Fatal(err)
		}
		if res != expect {
			t.Fatal("expected '%v', got '%v'", expect, res)
		}
	}
}

func BenchmarkSetRawOptimistic(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		res, err := SetRawOptions(exampleJSON, path, rawValue, opts)
		if err != nil {
			t.Fatal(err)
		}
		if res != expect {
			t.Fatal("expected '%v', got '%v'", expect, res)
		}
	}
}

func BenchmarkSetRawInPlace(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		res, err := SetRawOptions(exampleJSON, path, rawValue, optsInPlace)
		if err != nil {
			t.Fatal(err)
		}
		if res != expect {
			t.Fatal("expected '%v', got '%v'", expect, res)
		}
	}
}

func BenchmarkSetBytesOptimistic(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		res, err := SetBytesOptions(jsonBytes, path, value, opts)
		if err != nil {
			t.Fatal(err)
		}
		if bytes.Compare(res, expectBytes) != 0 {
			t.Fatal("expected '%v', got '%v'", string(expectBytes), string(res))
		}
	}
}

func BenchmarkSetBytesInPlace(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		copy(jsonBytes2, jsonBytes)
		res, err := SetBytesOptions(jsonBytes2, path, value, optsInPlace)
		if err != nil {
			t.Fatal(err)
		}
		if bytes.Compare(res, expectBytes) != 0 {
			t.Fatal("expected '%v', got '%v'", string(expectBytes), string(res))
		}
	}
}

func BenchmarkSetRawBytesOptimistic(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		res, err := SetRawBytesOptions(jsonBytes, path, rawValueBytes, opts)
		if err != nil {
			t.Fatal(err)
		}
		if bytes.Compare(res, expectBytes) != 0 {
			t.Fatal("expected '%v', got '%v'", string(expectBytes), string(res))
		}
	}
}

func BenchmarkSetRawBytesInPlace(t *testing.B) {
	t.ReportAllocs()
	for i := 0; i < t.N; i++ {
		copy(jsonBytes2, jsonBytes)
		res, err := SetRawBytesOptions(jsonBytes2, path, rawValueBytes, optsInPlace)
		if err != nil {
			t.Fatal(err)
		}
		if bytes.Compare(res, expectBytes) != 0 {
			t.Fatal("expected '%v', got '%v'", string(expectBytes), string(res))
		}
	}
}

const benchJSON = `
{
  "widget": {
    "debug": "on",
    "window": {
      "title": "Sample Konfabulator Widget",
      "name": "main_window",
      "width": 500,
      "height": 500
    },
    "image": { 
      "src": "Images/Sun.png",
      "hOffset": 250,
      "vOffset": 250,
      "alignment": "center"
    },
    "text": {
      "data": "Click Here",
      "size": 36,
      "style": "bold",
      "vOffset": 100,
      "alignment": "center",
      "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
    }
  }
}    
`

type BenchStruct struct {
	Widget struct {
		Debug  string `json:"debug"`
		Window struct {
			Title  string `json:"title"`
			Name   string `json:"name"`
			Width  int    `json:"width"`
			Height int    `json:"height"`
		} `json:"window"`
		Image struct {
			Src       string `json:"src"`
			HOffset   int    `json:"hOffset"`
			VOffset   int    `json:"vOffset"`
			Alignment string `json:"alignment"`
		} `json:"image"`
		Text struct {
			Data      string `json:"data"`
			Size      int    `json:"size"`
			Style     string `json:"style"`
			VOffset   int    `json:"vOffset"`
			Alignment string `json:"alignment"`
			OnMouseUp string `json:"onMouseUp"`
		} `json:"text"`
	} `json:"widget"`
}

var benchPaths = []string{
	"widget.window.name",
	"widget.image.hOffset",
	"widget.text.onMouseUp",
}

func Benchmark_SJSON(t *testing.B) {
	opts := Options{Optimistic: true}
	t.ReportAllocs()
	t.ResetTimer()
	for i := 0; i < t.N; i++ {
		for _, path := range benchPaths {
			var err error
			switch path {
			case "widget.window.name":
				_, err = SetOptions(benchJSON, path, "1", &opts)
			case "widget.image.hOffset":
				_, err = SetOptions(benchJSON, path, 1, &opts)
			case "widget.text.onMouseUp":
				_, err = SetOptions(benchJSON, path, "1", &opts)
			}
			if err != nil {
				t.Fatal(err)
			}

		}
	}
	t.N *= len(benchPaths)
}

func Benchmark_SJSON_ReplaceInPlace(t *testing.B) {
	data := []byte(benchJSON)
	opts := Options{
		Optimistic:     true,
		ReplaceInPlace: true,
	}
	v1, v2 := []byte(`"1"`), []byte("1")
	t.ReportAllocs()
	t.ResetTimer()
	for i := 0; i < t.N; i++ {
		for _, path := range benchPaths {
			var err error
			switch path {
			case "widget.window.name":
				_, err = SetRawBytesOptions(data, path, v1, &opts)
			case "widget.image.hOffset":
				_, err = SetRawBytesOptions(data, path, v2, &opts)
			case "widget.text.onMouseUp":
				_, err = SetRawBytesOptions(data, path, v1, &opts)
			}
			if err != nil {
				t.Fatal(err)
			}

		}
	}
	t.N *= len(benchPaths)
}

func Benchmark_Encoding_JSON_Map(t *testing.B) {
	data := []byte(benchJSON)
	t.ReportAllocs()
	t.ResetTimer()
	for i := 0; i < t.N; i++ {
		for _, path := range benchPaths {
			var m map[string]interface{}
			if err := gojson.Unmarshal(data, &m); err != nil {
				t.Fatal(err)
			}
			switch path {
			case "widget.window.name":
				m["widget"].(map[string]interface{})["window"].(map[string]interface{})["name"] = "1"
			case "widget.image.hOffset":
				m["widget"].(map[string]interface{})["image"].(map[string]interface{})["hOffset"] = 1
			case "widget.text.onMouseUp":
				m["widget"].(map[string]interface{})["text"].(map[string]interface{})["onMouseUp"] = "1"
			}
			_, err := gojson.Marshal(&m)
			if err != nil {
				t.Fatal(err)
			}
		}
	}
	t.N *= len(benchPaths)
}

func Benchmark_Encoding_JSON_Struct(t *testing.B) {
	data := []byte(benchJSON)
	t.ReportAllocs()
	t.ResetTimer()
	for i := 0; i < t.N; i++ {
		for _, path := range benchPaths {
			var v BenchStruct
			if err := gojson.Unmarshal(data, &v); err != nil {
				t.Fatal(err)
			}
			switch path {
			case "widget.window.name":
				v.Widget.Window.Name = "1"
			case "widget.image.hOffset":
				v.Widget.Image.HOffset = 1
			case "widget.text.onMouseUp":
				v.Widget.Text.OnMouseUp = "1"
			}
			_, err := gojson.Marshal(&v)
			if err != nil {
				t.Fatal(err)
			}
		}
	}
	t.N *= len(benchPaths)
}

func Benchmark_Gabs(t *testing.B) {
	data := []byte(benchJSON)
	t.ReportAllocs()
	t.ResetTimer()
	for i := 0; i < t.N; i++ {
		for _, path := range benchPaths {
			jsonParsed, err := gabs.ParseJSON(data)
			if err != nil {
				t.Fatal(err)
			}
			switch path {
			case "widget.window.name":
				jsonParsed.SetP("1", path)
			case "widget.image.hOffset":
				jsonParsed.SetP(1, path)
			case "widget.text.onMouseUp":
				jsonParsed.SetP("1", path)
			}
			jsonParsed.String()
		}
	}
	t.N *= len(benchPaths)
}

func Benchmark_FFJSON(t *testing.B) {
	data := []byte(benchJSON)
	t.ReportAllocs()
	t.ResetTimer()
	for i := 0; i < t.N; i++ {
		for _, path := range benchPaths {
			var v BenchStruct
			if err := v.UnmarshalFFJSONFromData(data); err != nil {
				t.Fatal(err)
			}
			switch path {
			case "widget.window.name":
				v.Widget.Window.Name = "1"
			case "widget.image.hOffset":
				v.Widget.Image.HOffset = 1
			case "widget.text.onMouseUp":
				v.Widget.Text.OnMouseUp = "1"
			}
			_, err := v.MarshalFFJSONFromData()
			if err != nil {
				t.Fatal(err)
			}
		}
	}
	t.N *= len(benchPaths)
}

func Benchmark_EasyJSON(t *testing.B) {
	data := []byte(benchJSON)
	t.ReportAllocs()
	t.ResetTimer()
	for i := 0; i < t.N; i++ {
		for _, path := range benchPaths {
			var v BenchStruct
			if err := v.UnmarshalEasyJSONFromData(data); err != nil {
				t.Fatal(err)
			}
			switch path {
			case "widget.window.name":
				v.Widget.Window.Name = "1"
			case "widget.image.hOffset":
				v.Widget.Image.HOffset = 1
			case "widget.text.onMouseUp":
				v.Widget.Text.OnMouseUp = "1"
			}
			_, err := v.MarshalEasyJSONFromData()
			if err != nil {
				t.Fatal(err)
			}
		}
	}
	t.N *= len(benchPaths)
}

//////////////////////////////////////////////////////////////
// EVERYTHING BELOW IS AUTOGENERATED

// suppress unused package warning
var (
	_ = gojson.RawMessage{}
	_ = jlexer.Lexer{}
	_ = jwriter.Writer{}
)

func easyjsonDbb23193DecodeGithubComTidwallSjson(in *jlexer.Lexer, out *BenchStruct) {
	if in.IsNull() {
		in.Skip()
		return
	}
	in.Delim('{')
	for !in.IsDelim('}') {
		key := in.UnsafeString()
		in.WantColon()
		if in.IsNull() {
			in.Skip()
			in.WantComma()
			continue
		}
		switch key {
		case "widget":
			easyjsonDbb23193Decode(in, &out.Widget)
		default:
			in.SkipRecursive()
		}
		in.WantComma()
	}
	in.Delim('}')
}
func easyjsonDbb23193EncodeGithubComTidwallSjson(out *jwriter.Writer, in BenchStruct) {
	out.RawByte('{')
	first := true
	_ = first
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"widget\":")
	easyjsonDbb23193Encode(out, in.Widget)
	out.RawByte('}')
}

// MarshalJSON supports json.Marshaler interface
func (v BenchStruct) MarshalEasyJSONFromData() ([]byte, error) {
	w := jwriter.Writer{}
	easyjsonDbb23193EncodeGithubComTidwallSjson(&w, v)
	return w.Buffer.BuildBytes(), w.Error
}

// MarshalEasyJSON supports easyjson.Marshaler interface
func (v BenchStruct) MarshalEasyJSON(w *jwriter.Writer) {
	easyjsonDbb23193EncodeGithubComTidwallSjson(w, v)
}

// UnmarshalJSON supports json.Unmarshaler interface
func (v *BenchStruct) UnmarshalEasyJSONFromData(data []byte) error {
	r := jlexer.Lexer{Data: data}
	easyjsonDbb23193DecodeGithubComTidwallSjson(&r, v)
	return r.Error()
}

// UnmarshalEasyJSON supports easyjson.Unmarshaler interface
func (v *BenchStruct) UnmarshalEasyJSON(l *jlexer.Lexer) {
	easyjsonDbb23193DecodeGithubComTidwallSjson(l, v)
}
func easyjsonDbb23193Decode(in *jlexer.Lexer, out *struct {
	Debug  string "json:\"debug\""
	Window struct {
		Title  string "json:\"title\""
		Name   string "json:\"name\""
		Width  int    "json:\"width\""
		Height int    "json:\"height\""
	} "json:\"window\""
	Image struct {
		Src       string "json:\"src\""
		HOffset   int    "json:\"hOffset\""
		VOffset   int    "json:\"vOffset\""
		Alignment string "json:\"alignment\""
	} "json:\"image\""
	Text struct {
		Data      string "json:\"data\""
		Size      int    "json:\"size\""
		Style     string "json:\"style\""
		VOffset   int    "json:\"vOffset\""
		Alignment string "json:\"alignment\""
		OnMouseUp string "json:\"onMouseUp\""
	} "json:\"text\""
}) {
	if in.IsNull() {
		in.Skip()
		return
	}
	in.Delim('{')
	for !in.IsDelim('}') {
		key := in.UnsafeString()
		in.WantColon()
		if in.IsNull() {
			in.Skip()
			in.WantComma()
			continue
		}
		switch key {
		case "debug":
			out.Debug = string(in.String())
		case "window":
			easyjsonDbb23193Decode1(in, &out.Window)
		case "image":
			easyjsonDbb23193Decode2(in, &out.Image)
		case "text":
			easyjsonDbb23193Decode3(in, &out.Text)
		default:
			in.SkipRecursive()
		}
		in.WantComma()
	}
	in.Delim('}')
}
func easyjsonDbb23193Encode(out *jwriter.Writer, in struct {
	Debug  string "json:\"debug\""
	Window struct {
		Title  string "json:\"title\""
		Name   string "json:\"name\""
		Width  int    "json:\"width\""
		Height int    "json:\"height\""
	} "json:\"window\""
	Image struct {
		Src       string "json:\"src\""
		HOffset   int    "json:\"hOffset\""
		VOffset   int    "json:\"vOffset\""
		Alignment string "json:\"alignment\""
	} "json:\"image\""
	Text struct {
		Data      string "json:\"data\""
		Size      int    "json:\"size\""
		Style     string "json:\"style\""
		VOffset   int    "json:\"vOffset\""
		Alignment string "json:\"alignment\""
		OnMouseUp string "json:\"onMouseUp\""
	} "json:\"text\""
}) {
	out.RawByte('{')
	first := true
	_ = first
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"debug\":")
	out.String(string(in.Debug))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"window\":")
	easyjsonDbb23193Encode1(out, in.Window)
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"image\":")
	easyjsonDbb23193Encode2(out, in.Image)
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"text\":")
	easyjsonDbb23193Encode3(out, in.Text)
	out.RawByte('}')
}
func easyjsonDbb23193Decode3(in *jlexer.Lexer, out *struct {
	Data      string "json:\"data\""
	Size      int    "json:\"size\""
	Style     string "json:\"style\""
	VOffset   int    "json:\"vOffset\""
	Alignment string "json:\"alignment\""
	OnMouseUp string "json:\"onMouseUp\""
}) {
	if in.IsNull() {
		in.Skip()
		return
	}
	in.Delim('{')
	for !in.IsDelim('}') {
		key := in.UnsafeString()
		in.WantColon()
		if in.IsNull() {
			in.Skip()
			in.WantComma()
			continue
		}
		switch key {
		case "data":
			out.Data = string(in.String())
		case "size":
			out.Size = int(in.Int())
		case "style":
			out.Style = string(in.String())
		case "vOffset":
			out.VOffset = int(in.Int())
		case "alignment":
			out.Alignment = string(in.String())
		case "onMouseUp":
			out.OnMouseUp = string(in.String())
		default:
			in.SkipRecursive()
		}
		in.WantComma()
	}
	in.Delim('}')
}
func easyjsonDbb23193Encode3(out *jwriter.Writer, in struct {
	Data      string "json:\"data\""
	Size      int    "json:\"size\""
	Style     string "json:\"style\""
	VOffset   int    "json:\"vOffset\""
	Alignment string "json:\"alignment\""
	OnMouseUp string "json:\"onMouseUp\""
}) {
	out.RawByte('{')
	first := true
	_ = first
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"data\":")
	out.String(string(in.Data))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"size\":")
	out.Int(int(in.Size))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"style\":")
	out.String(string(in.Style))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"vOffset\":")
	out.Int(int(in.VOffset))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"alignment\":")
	out.String(string(in.Alignment))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"onMouseUp\":")
	out.String(string(in.OnMouseUp))
	out.RawByte('}')
}
func easyjsonDbb23193Decode2(in *jlexer.Lexer, out *struct {
	Src       string "json:\"src\""
	HOffset   int    "json:\"hOffset\""
	VOffset   int    "json:\"vOffset\""
	Alignment string "json:\"alignment\""
}) {
	if in.IsNull() {
		in.Skip()
		return
	}
	in.Delim('{')
	for !in.IsDelim('}') {
		key := in.UnsafeString()
		in.WantColon()
		if in.IsNull() {
			in.Skip()
			in.WantComma()
			continue
		}
		switch key {
		case "src":
			out.Src = string(in.String())
		case "hOffset":
			out.HOffset = int(in.Int())
		case "vOffset":
			out.VOffset = int(in.Int())
		case "alignment":
			out.Alignment = string(in.String())
		default:
			in.SkipRecursive()
		}
		in.WantComma()
	}
	in.Delim('}')
}
func easyjsonDbb23193Encode2(out *jwriter.Writer, in struct {
	Src       string "json:\"src\""
	HOffset   int    "json:\"hOffset\""
	VOffset   int    "json:\"vOffset\""
	Alignment string "json:\"alignment\""
}) {
	out.RawByte('{')
	first := true
	_ = first
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"src\":")
	out.String(string(in.Src))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"hOffset\":")
	out.Int(int(in.HOffset))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"vOffset\":")
	out.Int(int(in.VOffset))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"alignment\":")
	out.String(string(in.Alignment))
	out.RawByte('}')
}
func easyjsonDbb23193Decode1(in *jlexer.Lexer, out *struct {
	Title  string "json:\"title\""
	Name   string "json:\"name\""
	Width  int    "json:\"width\""
	Height int    "json:\"height\""
}) {
	if in.IsNull() {
		in.Skip()
		return
	}
	in.Delim('{')
	for !in.IsDelim('}') {
		key := in.UnsafeString()
		in.WantColon()
		if in.IsNull() {
			in.Skip()
			in.WantComma()
			continue
		}
		switch key {
		case "title":
			out.Title = string(in.String())
		case "name":
			out.Name = string(in.String())
		case "width":
			out.Width = int(in.Int())
		case "height":
			out.Height = int(in.Int())
		default:
			in.SkipRecursive()
		}
		in.WantComma()
	}
	in.Delim('}')
}
func easyjsonDbb23193Encode1(out *jwriter.Writer, in struct {
	Title  string "json:\"title\""
	Name   string "json:\"name\""
	Width  int    "json:\"width\""
	Height int    "json:\"height\""
}) {
	out.RawByte('{')
	first := true
	_ = first
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"title\":")
	out.String(string(in.Title))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"name\":")
	out.String(string(in.Name))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"width\":")
	out.Int(int(in.Width))
	if !first {
		out.RawByte(',')
	}
	first = false
	out.RawString("\"height\":")
	out.Int(int(in.Height))
	out.RawByte('}')
}
func (mj *BenchStruct) MarshalFFJSONFromData() ([]byte, error) {
	var buf fflib.Buffer
	if mj == nil {
		buf.WriteString("null")
		return buf.Bytes(), nil
	}
	err := mj.MarshalJSONBufFFJSON(&buf)
	if err != nil {
		return nil, err
	}
	return buf.Bytes(), nil
}
func (mj *BenchStruct) MarshalJSONBufFFJSON(buf fflib.EncodingBuffer) error {
	if mj == nil {
		buf.WriteString("null")
		return nil
	}
	var err error
	var obj []byte
	_ = obj
	_ = err
	/* Inline struct. type=struct { Debug string "json:\"debug\""; Window struct { Title string "json:\"title\""; Name string "json:\"name\""; Width int "json:\"width\""; Height int "json:\"height\"" } "json:\"window\""; Image struct { Src string "json:\"src\""; HOffset int "json:\"hOffset\""; VOffset int "json:\"vOffset\""; Alignment string "json:\"alignment\"" } "json:\"image\""; Text struct { Data string "json:\"data\""; Size int "json:\"size\""; Style string "json:\"style\""; VOffset int "json:\"vOffset\""; Alignment string "json:\"alignment\""; OnMouseUp string "json:\"onMouseUp\"" } "json:\"text\"" } kind=struct */
	buf.WriteString(`{"widget":{ "debug":`)
	fflib.WriteJsonString(buf, string(mj.Widget.Debug))
	/* Inline struct. type=struct { Title string "json:\"title\""; Name string "json:\"name\""; Width int "json:\"width\""; Height int "json:\"height\"" } kind=struct */
	buf.WriteString(`,"window":{ "title":`)
	fflib.WriteJsonString(buf, string(mj.Widget.Window.Title))
	buf.WriteString(`,"name":`)
	fflib.WriteJsonString(buf, string(mj.Widget.Window.Name))
	buf.WriteString(`,"width":`)
	fflib.FormatBits2(buf, uint64(mj.Widget.Window.Width), 10, mj.Widget.Window.Width < 0)
	buf.WriteString(`,"height":`)
	fflib.FormatBits2(buf, uint64(mj.Widget.Window.Height), 10, mj.Widget.Window.Height < 0)
	buf.WriteByte('}')
	/* Inline struct. type=struct { Src string "json:\"src\""; HOffset int "json:\"hOffset\""; VOffset int "json:\"vOffset\""; Alignment string "json:\"alignment\"" } kind=struct */
	buf.WriteString(`,"image":{ "src":`)
	fflib.WriteJsonString(buf, string(mj.Widget.Image.Src))
	buf.WriteString(`,"hOffset":`)
	fflib.FormatBits2(buf, uint64(mj.Widget.Image.HOffset), 10, mj.Widget.Image.HOffset < 0)
	buf.WriteString(`,"vOffset":`)
	fflib.FormatBits2(buf, uint64(mj.Widget.Image.VOffset), 10, mj.Widget.Image.VOffset < 0)
	buf.WriteString(`,"alignment":`)
	fflib.WriteJsonString(buf, string(mj.Widget.Image.Alignment))
	buf.WriteByte('}')
	/* Inline struct. type=struct { Data string "json:\"data\""; Size int "json:\"size\""; Style string "json:\"style\""; VOffset int "json:\"vOffset\""; Alignment string "json:\"alignment\""; OnMouseUp string "json:\"onMouseUp\"" } kind=struct */
	buf.WriteString(`,"text":{ "data":`)
	fflib.WriteJsonString(buf, string(mj.Widget.Text.Data))
	buf.WriteString(`,"size":`)
	fflib.FormatBits2(buf, uint64(mj.Widget.Text.Size), 10, mj.Widget.Text.Size < 0)
	buf.WriteString(`,"style":`)
	fflib.WriteJsonString(buf, string(mj.Widget.Text.Style))
	buf.WriteString(`,"vOffset":`)
	fflib.FormatBits2(buf, uint64(mj.Widget.Text.VOffset), 10, mj.Widget.Text.VOffset < 0)
	buf.WriteString(`,"alignment":`)
	fflib.WriteJsonString(buf, string(mj.Widget.Text.Alignment))
	buf.WriteString(`,"onMouseUp":`)
	fflib.WriteJsonString(buf, string(mj.Widget.Text.OnMouseUp))
	buf.WriteByte('}')
	buf.WriteByte('}')
	buf.WriteByte('}')
	return nil
}

const (
	ffj_t_BenchStructbase = iota
	ffj_t_BenchStructno_such_key

	ffj_t_BenchStruct_Widget
)

var ffj_key_BenchStruct_Widget = []byte("widget")

func (uj *BenchStruct) UnmarshalFFJSONFromData(input []byte) error {
	fs := fflib.NewFFLexer(input)
	return uj.UnmarshalJSONFFLexer(fs, fflib.FFParse_map_start)
}

func (uj *BenchStruct) UnmarshalJSONFFLexer(fs *fflib.FFLexer, state fflib.FFParseState) error {
	var err error = nil
	currentKey := ffj_t_BenchStructbase
	_ = currentKey
	tok := fflib.FFTok_init
	wantedTok := fflib.FFTok_init

mainparse:
	for {
		tok = fs.Scan()
		//	println(fmt.Sprintf("debug: tok: %v  state: %v", tok, state))
		if tok == fflib.FFTok_error {
			goto tokerror
		}

		switch state {

		case fflib.FFParse_map_start:
			if tok != fflib.FFTok_left_bracket {
				wantedTok = fflib.FFTok_left_bracket
				goto wrongtokenerror
			}
			state = fflib.FFParse_want_key
			continue

		case fflib.FFParse_after_value:
			if tok == fflib.FFTok_comma {
				state = fflib.FFParse_want_key
			} else if tok == fflib.FFTok_right_bracket {
				goto done
			} else {
				wantedTok = fflib.FFTok_comma
				goto wrongtokenerror
			}

		case fflib.FFParse_want_key:
			// json {} ended. goto exit. woo.
			if tok == fflib.FFTok_right_bracket {
				goto done
			}
			if tok != fflib.FFTok_string {
				wantedTok = fflib.FFTok_string
				goto wrongtokenerror
			}

			kn := fs.Output.Bytes()
			if len(kn) <= 0 {
				// "" case. hrm.
				currentKey = ffj_t_BenchStructno_such_key
				state = fflib.FFParse_want_colon
				goto mainparse
			} else {
				switch kn[0] {

				case 'w':

					if bytes.Equal(ffj_key_BenchStruct_Widget, kn) {
						currentKey = ffj_t_BenchStruct_Widget
						state = fflib.FFParse_want_colon
						goto mainparse
					}

				}

				if fflib.SimpleLetterEqualFold(ffj_key_BenchStruct_Widget, kn) {
					currentKey = ffj_t_BenchStruct_Widget
					state = fflib.FFParse_want_colon
					goto mainparse
				}

				currentKey = ffj_t_BenchStructno_such_key
				state = fflib.FFParse_want_colon
				goto mainparse
			}

		case fflib.FFParse_want_colon:
			if tok != fflib.FFTok_colon {
				wantedTok = fflib.FFTok_colon
				goto wrongtokenerror
			}
			state = fflib.FFParse_want_value
			continue
		case fflib.FFParse_want_value:

			if tok == fflib.FFTok_left_brace || tok == fflib.FFTok_left_bracket || tok == fflib.FFTok_integer || tok == fflib.FFTok_double || tok == fflib.FFTok_string || tok == fflib.FFTok_bool || tok == fflib.FFTok_null {
				switch currentKey {

				case ffj_t_BenchStruct_Widget:
					goto handle_Widget

				case ffj_t_BenchStructno_such_key:
					err = fs.SkipField(tok)
					if err != nil {
						return fs.WrapErr(err)
					}
					state = fflib.FFParse_after_value
					goto mainparse
				}
			} else {
				goto wantedvalue
			}
		}
	}

handle_Widget:

	/* handler: uj.Widget type=struct { Debug string "json:\"debug\""; Window struct { Title string "json:\"title\""; Name string "json:\"name\""; Width int "json:\"width\""; Height int "json:\"height\"" } "json:\"window\""; Image struct { Src string "json:\"src\""; HOffset int "json:\"hOffset\""; VOffset int "json:\"vOffset\""; Alignment string "json:\"alignment\"" } "json:\"image\""; Text struct { Data string "json:\"data\""; Size int "json:\"size\""; Style string "json:\"style\""; VOffset int "json:\"vOffset\""; Alignment string "json:\"alignment\""; OnMouseUp string "json:\"onMouseUp\"" } "json:\"text\"" } kind=struct quoted=false*/

	{
		/* Falling back. type=struct { Debug string "json:\"debug\""; Window struct { Title string "json:\"title\""; Name string "json:\"name\""; Width int "json:\"width\""; Height int "json:\"height\"" } "json:\"window\""; Image struct { Src string "json:\"src\""; HOffset int "json:\"hOffset\""; VOffset int "json:\"vOffset\""; Alignment string "json:\"alignment\"" } "json:\"image\""; Text struct { Data string "json:\"data\""; Size int "json:\"size\""; Style string "json:\"style\""; VOffset int "json:\"vOffset\""; Alignment string "json:\"alignment\""; OnMouseUp string "json:\"onMouseUp\"" } "json:\"text\"" } kind=struct */
		tbuf, err := fs.CaptureField(tok)
		if err != nil {
			return fs.WrapErr(err)
		}

		err = gojson.Unmarshal(tbuf, &uj.Widget)
		if err != nil {
			return fs.WrapErr(err)
		}
	}

	state = fflib.FFParse_after_value
	goto mainparse

wantedvalue:
	return fs.WrapErr(fmt.Errorf("wanted value token, but got token: %v", tok))
wrongtokenerror:
	return fs.WrapErr(fmt.Errorf("ffjson: wanted token: %v, but got token: %v output=%s", wantedTok, tok, fs.Output.String()))
tokerror:
	if fs.BigError != nil {
		return fs.WrapErr(fs.BigError)
	}
	err = fs.Error.ToError()
	if err != nil {
		return fs.WrapErr(err)
	}
	panic("ffjson-generated: unreachable, please report bug.")
done:
	return nil
}