/*
 * Copyright 2022 CloudWeGo Authors
 *
 * 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 bthrift

import (
	"encoding/binary"
	"fmt"
	"testing"

	thrift "github.com/cloudwego/kitex/pkg/protocol/bthrift/apache"
	"github.com/cloudwego/kitex/pkg/protocol/bthrift/internal/test"
)

// TestWriteMessageEnd test binary WriteMessageEnd function
func TestWriteMessageEnd(t *testing.T) {
	buf := make([]byte, 0)
	test.Assert(t, Binary.WriteMessageEnd(buf) == 0)
}

// TestWriteStructBegin test binary WriteStructBegin and ReadStructBegin func
func TestWriteStructBegin(t *testing.T) {
	buf := make([]byte, 0)
	test.Assert(t, Binary.WriteStructBegin(buf, "") == 0)

	name, length, err := Binary.ReadStructBegin(nil)
	test.Assert(t, name == "")
	test.Assert(t, length == 0)
	test.Assert(t, nil == err)
}

// TestWriteStructEnd test binary WriteStructEnd and ReadStructEnd func
func TestWriteStructEnd(t *testing.T) {
	buf := make([]byte, 0)
	test.Assert(t, Binary.WriteStructEnd(buf) == 0)

	length, err := Binary.ReadStructEnd(nil)
	test.Assert(t, length == 0)
	test.Assert(t, nil == err)
}

// TestWriteAndReadFieldBegin test binary WriteFieldBegin and ReadFieldBegin func
func TestWriteAndReadFieldBegin(t *testing.T) {
	buf := make([]byte, 64)
	exceptWs := "080020"
	exceptSize := 3
	wn := Binary.WriteFieldBegin(buf, "", thrift.I32, 32)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	name, tID, id, length, err := Binary.ReadFieldBegin(buf)
	test.Assert(t, nil == err)
	test.Assert(t, thrift.I32 == tID)
	test.Assert(t, id == 32)
	test.Assert(t, length == exceptSize)
	test.Assert(t, name == "")

	length, err = Binary.ReadFieldEnd(nil)
	test.Assert(t, length == 0)
	test.Assert(t, nil == err)
}

// TestWriteFieldEnd test binary WriteFieldEnd func
func TestWriteFieldEnd(t *testing.T) {
	buf := make([]byte, 0)
	test.Assert(t, Binary.WriteFieldEnd(buf) == 0)
}

// TestWriteFieldStop test binary WriteFieldStop func
func TestWriteFieldStop(t *testing.T) {
	buf := make([]byte, 64)
	test.Assert(t, Binary.WriteFieldStop(buf) == 1)
}

// TestWriteAndReadMapBegin test binary WriteMapBegin and ReadMapBegin
func TestWriteAndReadMapBegin(t *testing.T) {
	buf := make([]byte, 64)
	exceptWs := "0d2000000001"
	exceptSize := 6
	wn := Binary.WriteMapBegin(buf, thrift.MAP, 32, 1)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	keyTp, valTp, size, length, err := Binary.ReadMapBegin(buf)
	test.Assert(t, nil == err)
	test.Assert(t, thrift.MAP == keyTp)
	test.Assert(t, valTp == 32)
	test.Assert(t, size == 1)
	test.Assert(t, length == exceptSize)

	length, err = Binary.ReadMapEnd(nil)
	test.Assert(t, length == 0)
	test.Assert(t, nil == err)
}

// TestWriteMapEnd test binary WriteMapEnd
func TestWriteMapEnd(t *testing.T) {
	buf := make([]byte, 64)
	test.Assert(t, Binary.WriteMapEnd(buf) == 0)
}

// TestWriteAndReadListBegin test binary WriteListBegin and ReadListBegin
func TestWriteAndReadListBegin(t *testing.T) {
	buf := make([]byte, 128)
	exceptWs := "0f00000020"
	exceptSize := 5
	wn := Binary.WriteListBegin(buf, thrift.LIST, 32)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	tp, size, length, err := Binary.ReadListBegin(buf)
	test.Assert(t, thrift.LIST == tp)
	test.Assert(t, nil == err)
	test.Assert(t, size == 32)
	test.Assert(t, length == exceptSize)

	length, err = Binary.ReadListEnd(nil)
	test.Assert(t, length == 0)
	test.Assert(t, nil == err)
}

// TestWriteListEnd test binary WriteListEnd
func TestWriteListEnd(t *testing.T) {
	buf := make([]byte, 64)
	test.Assert(t, Binary.WriteListEnd(buf) == 0)
}

// TestWriteAndReadSetBegin test binary WriteSetBegin and ReadSetBegin
func TestWriteAndReadSetBegin(t *testing.T) {
	buf := make([]byte, 128)
	exceptWs := "0e00000020"
	exceptSize := 5
	wn := Binary.WriteSetBegin(buf, thrift.SET, 32)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	tp, size, length, err := Binary.ReadSetBegin(buf)
	test.Assert(t, nil == err)
	test.Assert(t, size == 32)
	test.Assert(t, thrift.SET == tp)
	test.Assert(t, length == exceptSize)

	length, err = Binary.ReadSetEnd(nil)
	test.Assert(t, length == 0)
	test.Assert(t, nil == err)
}

// TestWriteSetEnd test binary WriteSetEnd
func TestWriteSetEnd(t *testing.T) {
	buf := make([]byte, 64)
	test.Assert(t, Binary.WriteSetEnd(buf) == 0)
}

// TestWriteAndReadBool test binary WriteBool and ReadBool
func TestWriteAndReadBool(t *testing.T) {
	buf := make([]byte, 8)
	exceptWs := "01"
	exceptSize := 1
	wn := Binary.WriteBool(buf, true)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	v, length, err := Binary.ReadBool(buf)
	test.Assert(t, nil == err)
	test.Assert(t, exceptSize == length)
	test.Assert(t, v)

	exceptWs = "00"
	wn = Binary.WriteBool(buf, false)
	ws = fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	v, length, err = Binary.ReadBool(buf)
	test.Assert(t, nil == err)
	test.Assert(t, exceptSize == length)
	test.Assert(t, !v)
}

// TestWriteAndReadByte test binary WriteByte and ReadByte
func TestWriteAndReadByte(t *testing.T) {
	buf := make([]byte, 1)
	exceptWs := "31"
	exceptSize := 1
	wn := Binary.WriteByte(buf, '1')
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	v, length, err := Binary.ReadByte(buf)
	test.Assert(t, nil == err)
	test.Assert(t, exceptSize == length)
	test.Assert(t, v == '1')
}

// TestWriteAndReadI16 test binary WriteI16 and ReadI16
func TestWriteAndReadI16(t *testing.T) {
	buf := make([]byte, 4)
	exceptWs := "0001"
	exceptSize := 2
	wn := Binary.WriteI16(buf, 1)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	v, length, err := Binary.ReadI16(buf)
	test.Assert(t, nil == err)
	test.Assert(t, exceptSize == length)
	test.Assert(t, v == 1)
}

// TestWriteAndReadI32 test binary WriteI32 and ReadI32
func TestWriteAndReadI32(t *testing.T) {
	buf := make([]byte, 4)
	exceptWs := "00000001"
	exceptSize := 4
	wn := Binary.WriteI32(buf, 1)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	v, length, err := Binary.ReadI32(buf)
	test.Assert(t, nil == err)
	test.Assert(t, exceptSize == length)
	test.Assert(t, v == 1)
}

// TestWriteAndReadI64 test binary WriteI64 and ReadI64
func TestWriteAndReadI64(t *testing.T) {
	buf := make([]byte, 8)
	exceptWs := "0000000000000001"
	exceptSize := 8
	wn := Binary.WriteI64(buf, 1)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	v, length, err := Binary.ReadI64(buf)
	test.Assert(t, nil == err)
	test.Assert(t, exceptSize == length)
	test.Assert(t, v == 1)
}

// TestWriteAndReadDouble test binary WriteDouble and ReadDouble
func TestWriteAndReadDouble(t *testing.T) {
	buf := make([]byte, 8)
	exceptWs := "3ff0000000000000"
	exceptSize := 8
	wn := Binary.WriteDouble(buf, 1)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	v, length, err := Binary.ReadDouble(buf)
	test.Assert(t, nil == err)
	test.Assert(t, exceptSize == length)
	test.Assert(t, v == 1)
}

// TestWriteAndReadString test binary WriteString and ReadString
func TestWriteAndReadString(t *testing.T) {
	buf := make([]byte, 128)
	exceptWs := "000000056b69746578"
	exceptSize := 9
	wn := Binary.WriteString(buf, "kitex")
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	v, length, err := Binary.ReadString(buf)
	test.Assert(t, nil == err)
	test.Assert(t, exceptSize == length)
	test.Assert(t, v == "kitex")
}

// TestWriteAndReadBinary test binary WriteBinary and ReadBinary
func TestWriteAndReadBinary(t *testing.T) {
	buf := make([]byte, 128)
	exceptWs := "000000056b69746578"
	exceptSize := 9
	val := []byte("kitex")
	wn := Binary.WriteBinary(buf, val)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	v, length, err := Binary.ReadBinary(buf)
	test.Assert(t, nil == err)
	test.Assert(t, exceptSize == length)
	for i := 0; i < len(v); i++ {
		test.Assert(t, val[i] == v[i])
	}
}

// TestWriteStringNocopy test binary WriteStringNocopy with small content
func TestWriteStringNocopy(t *testing.T) {
	buf := make([]byte, 128)
	exceptWs := "0000000c6d657373616765426567696e"
	exceptSize := 16
	wn := Binary.WriteStringNocopy(buf, nil, "messageBegin")
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)
}

// TestWriteBinaryNocopy test binary NewReaderWriterByteBuffer
func TestWriteBinaryNocopy(t *testing.T) {
	buf := make([]byte, 128)
	exceptWs := "0000000c6d657373616765426567696e"
	exceptSize := 16
	wn := Binary.WriteBinaryNocopy(buf, nil, []byte("messageBegin"))
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)
}

// TestLength test binary length functions
func TestLength(t *testing.T) {
	// Message Length
	test.Assert(t, Binary.MessageBeginLength("kitex", thrift.CALL, 1) == 17)
	test.Assert(t, Binary.MessageEndLength() == 0)

	// Struct Length
	test.Assert(t, Binary.StructBeginLength("kitex") == 0)
	test.Assert(t, Binary.StructEndLength() == 0)

	// Field Length
	test.Assert(t, Binary.FieldBeginLength("kitex", thrift.I32, 1) == 3)
	test.Assert(t, Binary.FieldEndLength() == 0)
	test.Assert(t, Binary.FieldStopLength() == 1)

	// Map Length
	test.Assert(t, Binary.MapBeginLength(thrift.MAP, thrift.MAP, 4) == 6)
	test.Assert(t, Binary.MapEndLength() == 0)

	// List Length
	test.Assert(t, Binary.ListBeginLength(thrift.LIST, 4) == 5)
	test.Assert(t, Binary.ListEndLength() == 0)

	// Set Length
	test.Assert(t, Binary.SetBeginLength(thrift.SET, 4) == 5)
	test.Assert(t, Binary.SetEndLength() == 0)

	// Bool Length
	test.Assert(t, Binary.BoolLength(true) == 1)
	test.Assert(t, Binary.BoolLength(false) == 1)

	// Byte Length
	test.Assert(t, Binary.ByteLength(1) == 1)

	// Int Length
	test.Assert(t, Binary.I16Length(1) == 2)
	test.Assert(t, Binary.I32Length(1) == 4)
	test.Assert(t, Binary.I64Length(1) == 8)

	// Double Length
	test.Assert(t, Binary.DoubleLength(1) == 8)

	// String Length
	test.Assert(t, Binary.StringLength("1") == 5)
	test.Assert(t, Binary.StringLengthNocopy("1") == 5)

	// Bytes Length
	test.Assert(t, Binary.BinaryLength([]byte("1")) == 5)
	test.Assert(t, Binary.BinaryLengthNocopy([]byte("1")) == 5)
}

// TestWriteMessageBegin test binary WriteMessageBegin and ReadMessageBegin with four thrift TMessageType
func TestWriteMessageBegin(t *testing.T) {
	buf := make([]byte, 128)
	exceptWs := "800100010000000c6d657373616765426567696e00000001"
	exceptSize := 24
	wn := Binary.WriteMessageBegin(buf, "messageBegin", thrift.CALL, 1)
	ws := fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	name, id, seqid, length, err := Binary.ReadMessageBegin(buf)
	test.Assert(t, nil == err)
	test.Assert(t, seqid == 1)
	test.Assert(t, thrift.CALL == id)
	test.Assert(t, name == "messageBegin")
	test.Assert(t, length == exceptSize)

	length, err = Binary.ReadMessageEnd(nil)
	test.Assert(t, length == 0)
	test.Assert(t, nil == err)

	exceptWs = "800100020000000c6d657373616765426567696e00000001"
	wn = Binary.WriteMessageBegin(buf, "messageBegin", thrift.REPLY, 1)
	ws = fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	name, id, seqid, length, err = Binary.ReadMessageBegin(buf)
	test.Assert(t, nil == err)
	test.Assert(t, seqid == 1)
	test.Assert(t, thrift.REPLY == id)
	test.Assert(t, name == "messageBegin")
	test.Assert(t, length == exceptSize)

	length, err = Binary.ReadMessageEnd(nil)
	test.Assert(t, length == 0)
	test.Assert(t, nil == err)

	exceptWs = "800100030000000c6d657373616765426567696e00000001"
	wn = Binary.WriteMessageBegin(buf, "messageBegin", thrift.EXCEPTION, 1)
	ws = fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	name, id, seqid, length, err = Binary.ReadMessageBegin(buf)
	test.Assert(t, nil == err)
	test.Assert(t, seqid == 1)
	test.Assert(t, thrift.EXCEPTION == id)
	test.Assert(t, name == "messageBegin")
	test.Assert(t, length == exceptSize)

	length, err = Binary.ReadMessageEnd(nil)
	test.Assert(t, length == 0)
	test.Assert(t, nil == err)

	exceptWs = "800100040000000c6d657373616765426567696e00000001"
	wn = Binary.WriteMessageBegin(buf, "messageBegin", thrift.ONEWAY, 1)
	ws = fmt.Sprintf("%x", buf[:wn])
	test.Assert(t, wn == exceptSize, wn, exceptSize)
	test.Assert(t, ws == exceptWs, ws, exceptWs)

	name, id, seqid, length, err = Binary.ReadMessageBegin(buf)
	test.Assert(t, nil == err)
	test.Assert(t, seqid == 1)
	test.Assert(t, thrift.ONEWAY == id)
	test.Assert(t, name == "messageBegin")
	test.Assert(t, length == exceptSize)

	length, err = Binary.ReadMessageEnd(nil)
	test.Assert(t, length == 0)
	test.Assert(t, nil == err)
}

// TestSkip test binary Skip with write functions
func TestSkip(t *testing.T) {
	buf := make([]byte, 1024)
	offset, length := 0, 0
	length += Binary.WriteByte(buf[length:], '1')
	length += Binary.WriteString(buf[length:], "2")
	length += Binary.WriteDouble(buf[length:], 3)
	length += Binary.WriteByte(buf[length:], '4')
	length += Binary.WriteBool(buf[length:], true)
	length += Binary.WriteI16(buf[length:], 6)
	length += Binary.WriteI32(buf[length:], 7)
	length += Binary.WriteI64(buf[length:], 8)
	length += Binary.WriteListBegin(buf[length:], thrift.LIST, 9)
	length += Binary.WriteListEnd(buf[length:])
	length += Binary.WriteMapBegin(buf[length:], thrift.MAP, thrift.STRING, 10)
	length += Binary.WriteMapEnd(buf[length:])
	length += Binary.WriteSetBegin(buf[length:], thrift.STRING, 11)
	length += Binary.WriteSetEnd(buf[length:])

	test.Assert(t, length == 46)

	size, _ := Binary.Skip(buf, thrift.BYTE)
	offset += size
	valStr, _, _ := Binary.ReadString(buf[offset:])
	test.Assert(t, valStr == "2")

	size, _ = Binary.Skip(buf[offset:], thrift.STRING)
	offset += size
	valDouble, _, _ := Binary.ReadDouble(buf[offset:])
	test.Assert(t, valDouble == 3)

	size, _ = Binary.Skip(buf[offset:], thrift.DOUBLE)
	offset += size
	valInt8, _, _ := Binary.ReadByte(buf[offset:])
	test.Assert(t, valInt8 == '4')

	size, _ = Binary.Skip(buf[offset:], thrift.BYTE)
	offset += size
	valBool, _, _ := Binary.ReadBool(buf[offset:])
	test.Assert(t, valBool)

	size, _ = Binary.Skip(buf[offset:], thrift.BOOL)
	offset += size
	valI16, _, _ := Binary.ReadI16(buf[offset:])
	test.Assert(t, valI16 == 6)

	size, _ = Binary.Skip(buf[offset:], thrift.I16)
	offset += size
	valI32, _, _ := Binary.ReadI32(buf[offset:])
	test.Assert(t, valI32 == 7)

	size, _ = Binary.Skip(buf[offset:], thrift.I32)
	offset += size
	valI64, _, _ := Binary.ReadI64(buf[offset:])
	test.Assert(t, valI64 == 8)

	size, _ = Binary.Skip(buf[offset:], thrift.I64)
	offset += size
	_, valList, _, _ := Binary.ReadListBegin(buf[offset:])
	test.Assert(t, valList == 9)

	size, _ = Binary.Skip(buf[offset+valList:], thrift.LIST)
	offset += size
	_, _, valMap, _, _ := Binary.ReadMapBegin(buf[offset:])
	test.Assert(t, valMap == 10)

	size, _ = Binary.Skip(buf[offset+valMap+valList:], thrift.MAP)
	offset += size
	_, valSet, _, _ := Binary.ReadSetBegin(buf[offset:])
	test.Assert(t, valSet == 11)
}

func BenchmarkBinaryProtocolReadString(b *testing.B) {
	stringSizes := []int{64, 128, 1024, 4096, 10240, 102400}
	strings := 32
	for _, sz := range stringSizes {
		b.Run(fmt.Sprintf("string_size=%d", sz), func(b *testing.B) {
			stringBuffer := make([]byte, sz*strings+4*strings)
			start := 0
			for i := 0; i < strings; i++ {
				binary.BigEndian.PutUint32(stringBuffer[start:start+4], uint32(sz))
				start += sz + 4
			}
			b.ReportAllocs()
			b.ResetTimer()
			for i := 0; i < b.N; i++ {
				for i := 0; i < strings; i++ {
					_, l, err := Binary.ReadString(stringBuffer)
					if l-4 != sz || err != nil {
						b.Fatal("read string failed", l, err)
					}
				}
			}
		})
	}
}
