/*
 * Copyright 2021 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 encoder

import (
    `encoding/hex`
    `encoding/json`
    `math`
    `reflect`
    `runtime`
    `strings`
    `testing`
    `unsafe`

    `github.com/bytedance/sonic/option`
    `github.com/bytedance/sonic/internal/rt`
    `github.com/davecgh/go-spew/spew`
    `github.com/stretchr/testify/assert`
)

func TestEncoderMemoryCorruption(t *testing.T) {
    println("TestEncoderMemoryCorruption")
    runtime.GC()
    var m = map[string]interface{}{
        "1": map[string]interface{} {
            `"`+strings.Repeat("a", int(option.DefaultEncoderBufferSize) - 38)+`"`: "b",
            "1": map[string]int32{
                "b": 1658219785,
            },
        },
    }
    out, err := Encode(m, SortMapKeys)
    if err != nil {
        t.Fatal(err)
    }
    println(len(out))
    if err := json.Unmarshal(out, &m); err != nil {
        t.Fatal(err)
    }
}

func TestAssembler_CompileAndLoad(t *testing.T) {
    p, err := newCompiler().compile(reflect.TypeOf((*bool)(nil)), true)
    assert.Nil(t, err)
    a := newAssembler(p)
    f := a.Load()
    s := newStack()
    b := []byte(nil)

    /* true */
    v := true
    u := &v
    e := f(&b, unsafe.Pointer(&u), s, 0)
    assert.Nil(t, e)
    println(cap(b))
    println(hex.Dump(b))

    /* false */
    v = false
    u = &v
    b = b[:0]
    e = f(&b, unsafe.Pointer(&u), s, 0)
    assert.Nil(t, e)
    println(cap(b))
    println(hex.Dump(b))

    /* nil */
    u = nil
    b = b[:0]
    e = f(&b, unsafe.Pointer(&u), s, 0)
    assert.Nil(t, e)
    println(cap(b))
    println(hex.Dump(b))
}

type testOps struct {
    key string
    ins _Program
    exp string
    err error
    val interface{}
}

func testOpCode(t *testing.T, v interface{}, ex string, err error, ins _Program) {
    p := ins
    m := []byte(nil)
    s := new(_Stack)
    a := newAssembler(p)
    f := a.Load()
    e := f(&m, rt.UnpackEface(v).Value, s, 0)
    if err != nil {
        assert.EqualError(t, e, err.Error())
    } else {
        assert.Nil(t, e)
        assert.Equal(t, ex, string(m))
    }
}

type IfaceValue int
func (IfaceValue) Error() string {
    return "not really implemented"
}

type JsonMarshalerValue int
func (JsonMarshalerValue) MarshalJSON() ([]byte, error) {
    return []byte("123456789"), nil
}

type RecursiveValue struct {
    A int                       `json:"a"`
    P *RecursiveValue           `json:"p,omitempty"`
    Q []RecursiveValue          `json:"q"`
    R map[string]RecursiveValue `json:"r"`
    Z int                       `json:"z"`
}

func mustCompile(t interface{}) _Program {
    p, err := newCompiler().compile(reflect.TypeOf(t), !rt.UnpackEface(t).Type.Indirect())
    if err != nil {
        panic(err)
    }
    return p
}

func TestAssembler_OpCode(t *testing.T) {
    var iface error = IfaceValue(12345)
    var eface interface{} = 12345
    var jval = new(JsonMarshalerValue)
    var jifv json.Marshaler = JsonMarshalerValue(0)
    var jifp json.Marshaler = jval
    var rec = &RecursiveValue {
        A: 123,
        Z: 456,
        P: &RecursiveValue {
            A: 789,
            Z: 666,
            P: &RecursiveValue {
                A: 777,
                Z: 888,
                Q: []RecursiveValue {{
                    A: 999,
                    Z: 222,
                    R: map[string]RecursiveValue {
                        "xxx": {
                            A: 333,
                        },
                    },
                }},
            },
        },
    }
    tests := []testOps {
    {
        key: "_OP_null",
        ins: []_Instr{newInsOp(_OP_null)},
        exp: "null",
        val: nil,
    }, {
        key: "_OP_bool/true",
        ins: []_Instr{newInsOp(_OP_bool)},
        exp: "true",
        val: true,
    }, {
        key: "_OP_bool/false",
        ins: []_Instr{newInsOp(_OP_bool)},
        exp: "false",
        val: false,
    }, {
        key: "_OP_i8",
        ins: []_Instr{newInsOp(_OP_i8)},
        exp: "-128",
        val: int8(-128),
    }, {
        key: "_OP_i16",
        ins: []_Instr{newInsOp(_OP_i16)},
        exp: "-32768",
        val: int16(-32768),
    }, {
        key: "_OP_i32",
        ins: []_Instr{newInsOp(_OP_i32)},
        exp: "-2147483648",
        val: int32(-2147483648),
    }, {
        key: "_OP_i64",
        ins: []_Instr{newInsOp(_OP_i64)},
        exp: "-9223372036854775808",
        val: int64(math.MinInt64),
    }, {
        key: "_OP_u8",
        ins: []_Instr{newInsOp(_OP_u8)},
        exp: "255",
        val: uint8(255),
    }, {
        key: "_OP_u16",
        ins: []_Instr{newInsOp(_OP_u16)},
        exp: "65535",
        val: uint16(65535),
    }, {
        key: "_OP_u32",
        ins: []_Instr{newInsOp(_OP_u32)},
        exp: "4294967295",
        val: uint32(4294967295),
    }, {
        key: "_OP_u64",
        ins: []_Instr{newInsOp(_OP_u64)},
        exp: "18446744073709551615",
        val: uint64(18446744073709551615),
    }, {
        key: "_OP_f32",
        ins: []_Instr{newInsOp(_OP_f32)},
        exp: "-12.5",
        val: float32(-12.5),
    }, {
        key: "_OP_f32/nan",
        ins: []_Instr{newInsOp(_OP_f32)},
        err: _ERR_nan_or_infinite,
        val: float32(math.NaN()),
    }, {
        key: "_OP_f32/+inf",
        ins: []_Instr{newInsOp(_OP_f32)},
        err: _ERR_nan_or_infinite,
        val: float32(math.Inf(1)),
    }, {
        key: "_OP_f32/-inf",
        ins: []_Instr{newInsOp(_OP_f32)},
        err: _ERR_nan_or_infinite,
        val: float32(math.Inf(-1)),
    }, {
        key: "_OP_f64",
        ins: []_Instr{newInsOp(_OP_f64)},
        exp: "-2.2250738585072014e-308",
        val: -2.2250738585072014e-308,
    }, {
        key: "_OP_f64/nan",
        ins: []_Instr{newInsOp(_OP_f64)},
        err: _ERR_nan_or_infinite,
        val: math.NaN(),
    }, {
        key: "_OP_f64/+inf",
        ins: []_Instr{newInsOp(_OP_f64)},
        err: _ERR_nan_or_infinite,
        val: math.Inf(1),
    }, {
        key: "_OP_f64/-inf",
        ins: []_Instr{newInsOp(_OP_f64)},
        err: _ERR_nan_or_infinite,
        val: math.Inf(-1),
    }, {
        key: "_OP_str",
        ins: []_Instr{newInsOp(_OP_str)},
        exp: `"Cartoonist, Illustrator, and T-Shirt connoisseur"`,
        val: "Cartoonist, Illustrator, and T-Shirt connoisseur",
    }, {
        key: "_OP_str/empty",
        ins: []_Instr{newInsOp(_OP_str)},
        exp: `""`,
        val: "",
    }, {
        key: "_OP_bin",
        ins: []_Instr{newInsOp(_OP_bin)},
        exp: `"AQIDBAU="`,
        val: []byte{1, 2, 3, 4, 5},
    }, {
        key: "_OP_bin/empty",
        ins: []_Instr{newInsOp(_OP_bin)},
        exp: `""`,
        val: []byte{},
    }, {
        key: "_OP_quote",
        ins: []_Instr{newInsOp(_OP_quote)},
        exp: `"\"test\""`,
        val: "test",
    }, {
        key: "_OP_quote/escape",
        ins: []_Instr{newInsOp(_OP_quote)},
        exp: `"\"hello\\n\\t\\rworld\""`,
        val: "hello\n\t\rworld",
    }, {
        key: "_OP_number",
        ins: []_Instr{newInsOp(_OP_number)},
        exp: "1.2345",
        val: "1.2345",
    }, {
        key: "_OP_number/invalid",
        ins: []_Instr{newInsOp(_OP_number)},
        err: error_number("not a number"),
        val: "not a number",
    }, {
        key: "_OP_eface",
        ins: []_Instr{newInsOp(_OP_eface)},
        exp: `12345`,
        val: &eface,
    }, {
        key: "_OP_iface",
        ins: []_Instr{newInsOp(_OP_iface)},
        exp: `12345`,
        val: &iface,
    }, {
        key: "_OP_byte",
        ins: []_Instr{newInsVi(_OP_byte, 'x')},
        exp: "x",
        val: nil,
    }, {
        key: "_OP_text",
        ins: []_Instr{newInsVs(_OP_text, "hello, world !!")},
        exp: "hello, world !!",
        val: nil,
    }, {
        key: "_OP_map_[iter,next,value]",
        ins: mustCompile(map[string]map[int64]int{}),
        exp: `{"asdf":{"-9223372036854775808":1234}}`,
        val: &map[string]map[int64]int{"asdf": {math.MinInt64: 1234}},
    }, {
        key: "_OP_slice_[len,next]",
        ins: mustCompile([][]int{}),
        exp: `[[1,2,3],[4,5,6]]`,
        val: &[][]int{{1, 2, 3}, {4, 5, 6}},
    }, {
        key: "_OP_marshal[_text]",
        ins: []_Instr{newInsVt(_OP_marshal, reflect.TypeOf(JsonMarshalerValue(0)))},
        exp: "123456789",
        val: new(JsonMarshalerValue),
    }, {
        key: "_OP_marshal[_text]/ptr",
        ins: []_Instr{newInsVt(_OP_marshal, reflect.TypeOf(new(JsonMarshalerValue)))},
        exp: "123456789",
        val: &jval,
    }, {
        key: "_OP_marshal[_text]/iface_v",
        ins: []_Instr{newInsVt(_OP_marshal, jsonMarshalerType)},
        exp: "123456789",
        val: &jifv,
    }, {
        key: "_OP_marshal[_text]/iface_p",
        ins: []_Instr{newInsVt(_OP_marshal, jsonMarshalerType)},
        exp: "123456789",
        val: &jifp,
    }, 
    {
        key: "_OP_recurse",
        ins: mustCompile(rec),
        exp: `{"a":123,"p":{"a":789,"p":{"a":777,"q":[{"a":999,"q":null,"r":{"` +
             `xxx":{"a":333,"q":null,"r":null,"z":0}},"z":222}],"r":null,"z":8` +
             `88},"q":null,"r":null,"z":666},"q":null,"r":null,"z":456}`,
        val: &rec,
    }}
    for _, tv := range tests {
        t.Run(tv.key, func(t *testing.T) {
            testOpCode(t, tv.val, tv.exp, tv.err, tv.ins)
        })
    }
}

func TestAssembler_StringMoreSpace(t *testing.T) {
    p := _Program{newInsOp(_OP_str)}
    m := make([]byte, 0, 8)
    s := new(_Stack)
    a := newAssembler(p)
    f := a.Load()
    v := "\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\u000a\u000b\u000c\u000d\u000e\u000f\u0010"
    e := f(&m, unsafe.Pointer(&v), s, 0)
    assert.Nil(t, e)
    spew.Dump(m)
}

func TestAssembler_TwitterJSON_Generic(t *testing.T) {
    p := mustCompile(&_GenericValue)
    m := []byte(nil)
    s := new(_Stack)
    a := newAssembler(p)
    f := a.Load()
    v := &_GenericValue
    e := f(&m, unsafe.Pointer(&v), s, 0)
    assert.Nil(t, e)
    println(string(m))
}

func TestAssembler_TwitterJSON_Structure(t *testing.T) {
    p := mustCompile(_BindingValue)
    m := []byte(nil)
    s := new(_Stack)
    a := newAssembler(p)
    f := a.Load()
    e := f(&m, unsafe.Pointer(&_BindingValue), s, 0)
    assert.Nil(t, e)
    println(string(m))
    runtime.KeepAlive(s)
}

func TestScratchedString(t *testing.T) {
    fatal := *(*string)(unsafe.Pointer(&rt.GoString{nil, 1}))
    defer func(){
        if v := recover(); v == nil {
            t.Fatal()
        } else if s, ok := v.(string); !ok {
            t.Fatal(v)
        }else{
            if !strings.Contains(s, "has nil pointer while its length is not zero") {
                t.Fatal(s)
            }
        }
    }()
    _, _ = Encode(fatal, 0)
    t.Fatal()
}

func TestScratchedNumber(t *testing.T) {
    fatal := *(*json.Number)(unsafe.Pointer(&rt.GoString{nil, 1}))
    defer func(){
        if v := recover(); v == nil {
            t.Fatal()
        } else if s, ok := v.(string); !ok {
            t.Fatal(v)
        }else{
            if !strings.Contains(s, "has nil pointer while its length is not zero") {
                t.Fatal(s)
            }
        }
    }()
    _, _ = Encode(fatal, 0)
    t.Fatal()
}