// +build go1.18

/*
 * Copyright 2022 ByteDance Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package generic_test

import (
    `os`
    `testing`
    `reflect`
    `fmt`
    `github.com/bytedance/sonic`
    `encoding/json`
    gojson `github.com/goccy/go-json`
    jsoniter `github.com/json-iterator/go`
    jsonv2 `github.com/go-json-experiment/json`
)

var (
    validFlag = os.Getenv("SONIC_VALID_GENERIC_BENCH")  != ""
    pretouchFlag = os.Getenv("SONIC_NO_PRETOUCH_BENCH") == ""
)

type jsonLibEntry struct {
    name      string
    marshal   func(any) ([]byte, error)
    unmarshal func([]byte, any) error
}

var jsonLibs = []jsonLibEntry {
    {"Std", json.Marshal, json.Unmarshal},
    {"StdV2", jsonv2.Marshal, jsonv2.Unmarshal},
    {"Sonic", sonic.Marshal, sonic.Unmarshal},
    {"SonicStd", sonic.ConfigStd.Marshal, sonic.ConfigStd.Unmarshal},
    {"GoJson", gojson.Marshal, gojson.Unmarshal},
    {"JsonIter", jsoniter.Marshal, jsoniter.Unmarshal},
    {"JsonIterStd", jsoniter.ConfigCompatibleWithStandardLibrary.Marshal, jsoniter.ConfigCompatibleWithStandardLibrary.Unmarshal},
}

func BenchmarkUnmarshalConcrete(b *testing.B)     { 
    runUnmarshalC(b)
}

func BenchmarkUnmarshalInterface(b *testing.B)     { 
    runUnmarshalI(b)
}

func BenchmarkMarshalConcrete(b *testing.B)     { 
    runMarshalC(b) 
}

func BenchmarkMarshalInterface(b *testing.B)     { 
    runMarshalI(b) 
}

func runUnmarshalC(b *testing.B) {
    for _, tt := range jsonTestdata() {
        for _, lib := range jsonLibs {
            var val any = tt.new()
            pretouch := func() {
                _ = lib.unmarshal(tt.data, val)
            }
    
            run := func(b *testing.B) {
                val = tt.new()
                if err := lib.unmarshal(tt.data, val); err != nil {
                    b.Fatalf("%s Unmarshal error: %v", lib.name, err)
                }
            }

            valid := func(b *testing.B) {
                val1, val2 := tt.new(), tt.new()
                if err := json.Unmarshal(tt.data, val1); err != nil {
                    panic(err)
                }
                if err := lib.unmarshal(tt.data, val2); err != nil {
                    panic(err)
                }
                if !reflect.DeepEqual(val1, val2) {
                    b.Fatalf("%s Unmarshal output mismatch:\ngot  %v\nwant %v", lib.name, val2, val1)
                }
            }

            name := fmt.Sprintf("%s_%s", tt.name, lib.name)
            b.Run(name, func(b *testing.B) {
                if pretouchFlag {
                    pretouch()
                }
                valid(b)
                b.ResetTimer()
                b.ReportAllocs()
                b.SetBytes(int64(len(tt.data)))
                for i := 0; i < b.N; i++ {
                    run(b)
                }
            })
        }
    }
}

func runUnmarshalI(b *testing.B) {
    for _, tt := range jsonTestdata() {
        for _, lib := range jsonLibs {
            var val any = tt.newI()
            pretouch := func() {
                _ = lib.unmarshal(tt.data, val)
            }
    
            run := func(b *testing.B) {
                val = tt.newI()
                if err := lib.unmarshal(tt.data, val); err != nil {
                    b.Fatalf("%s Unmarshal error: %v", lib.name, err)
                }
            }

            valid := func(b *testing.B) {
                val1, val2 := tt.newI(), tt.newI()
                if err := json.Unmarshal(tt.data, val1); err != nil {
                    panic(err)
                }
                if err := lib.unmarshal(tt.data, val2); err != nil {
                    panic(err)
                }
                if !reflect.DeepEqual(val1, val2) {
                    b.Fatalf("%s Unmarshal output mismatch:\ngot  %v\nwant %v", lib.name, val2, val1)
                }
            }

            name := fmt.Sprintf("%s_%s", tt.name, lib.name)
            b.Run(name, func(b *testing.B) {
                if pretouchFlag {
                    pretouch()
                }
                valid(b)
                b.ResetTimer()
                b.ReportAllocs()
                b.SetBytes(int64(len(tt.data)))
                for i := 0; i < b.N; i++ {
                    run(b)
                }
            })
        }
    }
}

func runMarshalC(b *testing.B) {
    for _, tt := range jsonTestdata() {
        for _, lib := range jsonLibs {
            pretouch := func() {
                _, _ = lib.marshal(tt.val)
            }

            run := func(b *testing.B) {
                if _, err := lib.marshal(tt.val); err != nil {
                    b.Fatalf("%s Marshal error: %v", lib.name, err)
                }
            }

            valid := func(b *testing.B) {
                // some details are different with encoding/json, so we compare the unmarshal results from marshaled buffer
                var buf1, buf2 []byte
                buf1, err := json.Marshal(tt.val)
                if err != nil {
                    b.Fatalf("encoding/json Marshal error: %v", err)
                }
                buf2, err = lib.marshal(tt.val); 
                if err != nil {
                    b.Fatalf("%s Marshal error: %v", lib.name, err)
                }
                var val1, val2 = tt.new(), tt.new()
                if err := json.Unmarshal(buf1, val1); err != nil {
                    b.Fatalf("encoding/json Unmarshal again error: %v", err)
                }
                if err := lib.unmarshal(buf2, val2); err != nil {
                    b.Fatalf("%s Unmarshal again error: %v", lib.name, err)
                }
                if !reflect.DeepEqual(val1, val2) {
                    b.Fatalf("Unmarshal again output mismatch\n")
                }
            }

            name := fmt.Sprintf("%s_%s", tt.name, lib.name)
            b.Run(name, func(b *testing.B) {
                if pretouchFlag {
                    pretouch()
                }
                if (validFlag) {
                    valid(b)
                }
                b.ResetTimer()
                b.ReportAllocs()
                b.SetBytes(int64(len(tt.data)))
                for i := 0; i < b.N; i++ {
                    run(b)
                }
            })
        }
    }
}

func runMarshalI(b *testing.B) {
    for _, tt := range jsonTestdata() {
        for _, lib := range jsonLibs {
            pretouch := func() {
                _, _ = lib.marshal(tt.valI)
            }

            run := func(b *testing.B) {
                if _, err := lib.marshal(tt.valI); err != nil {
                    b.Fatalf("%s Marshal error: %v", lib.name, err)
                }
            }

            valid := func(b *testing.B) {
                // some details are different with encoding/json, so we compare the unmarshal results from marshaled buffer
                var buf1, buf2 []byte
                buf1, err := json.Marshal(tt.valI)
                if err != nil {
                    b.Fatalf("encoding/json Marshal error: %v", err)
                }
                buf2, err = lib.marshal(tt.valI); 
                if err != nil {
                    b.Fatalf("%s Marshal error: %v", lib.name, err)
                }
                var val1, val2 = tt.new(), tt.new()
                if err := json.Unmarshal(buf1, val1); err != nil {
                    b.Fatalf("encoding/json Unmarshal again error: %v", err)
                }
                if err := lib.unmarshal(buf2, val2); err != nil {
                    b.Fatalf("%s Unmarshal again error: %v", lib.name, err)
                }
                if !reflect.DeepEqual(val1, val2) {
                    b.Fatalf("Unmarshal again output mismatch\n")
                }
            }

            name := fmt.Sprintf("%s_%s", tt.name, lib.name)
            b.Run(name, func(b *testing.B) {
                if pretouchFlag {
                    pretouch()
                }
                if (validFlag) {
                    valid(b)
                }
                b.ResetTimer()
                b.ReportAllocs()
                b.SetBytes(int64(len(tt.data)))
                for i := 0; i < b.N; i++ {
                    run(b)
                }
            })
        }
    }
}