// Copyright (C) 2020-2021  Nexedi SA and Contributors.
//                          Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.

package msgpack

import (
	hexpkg "encoding/hex"
	"testing"
)

// hex decodes string as hex; panics on error.
func hex(s string) string {
	b, err := hexpkg.DecodeString(s)
	if err != nil {
		panic(err)
	}
	return string(b)
}



// tGetPutSize is interface with Get/Put/Size methods, e.g. with
// GetBinHead/PutBinHead/BinHeadSize.
type tGetPutSize interface {
	// TODO Get(data []byte) (n int, ret interface{})
	Size(arg interface{}) int
	Put(data []byte, arg interface{}) int
}

// test1 verifies enc functions on one argument.
func test1(t *testing.T, enc tGetPutSize, arg interface{}, encoded string) {
	t.Helper()
	data := make([]byte, 16)
	n := enc.Put(data, arg)
	got := string(data[:n])
	if got != encoded {
		t.Errorf("%v -> %x  ; want %x", arg, got, encoded)
	}
	if sz := enc.Size(arg); sz != n {
		t.Errorf("size(%v) -> %d  ; len(data)=%d", arg, sz, n)
	}
	// TODO decode == arg, n
	// TODO decode([:n-1]) -> overflow
}

type tEncUint64 struct{}
func (_ *tEncUint64) Size(xi interface{}) int              { return Uint64Size(xi.(uint64)) }
func (_ *tEncUint64) Put(data []byte, xi interface{}) int  { return PutUint64(data, xi.(uint64)) }
func TestUint(t *testing.T) {
	h := hex
	testv := []struct{i uint64; encoded string}{
		{0,    h("00")}, // posfixint
		{1,    h("01")},
		{0x7f, h("7f")},
		{0x80,  h("cc80")}, // uint8
		{0xff,  h("ccff")},
		{0x100,  h("cd0100")}, // uint16
		{0xffff, h("cdffff")},
		{0x10000,    h("ce00010000")}, // uint32
		{0xffffffff, h("ceffffffff")},
		{0x100000000,        h("cf0000000100000000")}, // uint64
		{0xffffffffffffffff, h("cfffffffffffffffff")},
	}

	for _, tt := range testv {
		test1(t, &tEncUint64{}, tt.i, tt.encoded)
	}
}

type tEncInt64 struct{}
func (_ *tEncInt64) Size(xi interface{}) int              { return Int64Size(xi.(int64)) }
func (_ *tEncInt64) Put(data []byte, xi interface{}) int  { return PutInt64(data, xi.(int64)) }
func TestInt(t *testing.T) {
	h := hex
	testv := []struct{i int64; encoded string}{
		{0,    h("00")}, // posfixint
		{1,    h("01")},
		{0x7f, h("7f")},
		{-1,   h("ff")}, // negfixint
		{-2,   h("fe")},
		{-31,  h("e1")},
		{-32,  h("e0")},
		{-33,   h("d0df")}, // int8
		{-0x7f, h("d081")},
		{-0x80, h("d080")},
		{0x80,    h("d10080")}, // int16
		{0x7fff,  h("d17fff")},
		{-0x7fff, h("d18001")},
		{-0x8000, h("d18000")},
		{0x8000,      h("d200008000")}, // int32
		{0x7fffffff,  h("d27fffffff")},
		{-0x8001,     h("d2ffff7fff")},
		{-0x7fffffff, h("d280000001")},
		{-0x80000000, h("d280000000")},
		{0x80000000,          h("d30000000080000000")}, // int64
		{0x7fffffffffffffff,  h("d37fffffffffffffff")},
		{-0x80000001,         h("d3ffffffff7fffffff")},
		{-0x7fffffffffffffff, h("d38000000000000001")},
		{-0x8000000000000000, h("d38000000000000000")},
	}

	for _, tt := range testv {
		test1(t, &tEncInt64{}, tt.i, tt.encoded)
	}
}

type tEncBinHead struct{}
func (_ *tEncBinHead) Size(xl interface{}) int              { return BinHeadSize(xl.(int)) }
func (_ *tEncBinHead) Put(data []byte, xl interface{}) int  { return PutBinHead(data, xl.(int)) }
func TestBin(t *testing.T) {
	h := hex
	testv := []struct{l int; encoded string}{
		{0,    h("c400")}, // bin8
		{1,    h("c401")},
		{0xff, h("c4ff")},
		{0x100,  h("c50100")}, // bin16
		{0xffff, h("c5ffff")},
		{0x10000,    h("c600010000")}, // bin32
		{0xffffffff, h("c6ffffffff")},
	}

	for _, tt := range testv {
		test1(t, &tEncBinHead{}, tt.l, tt.encoded)
	}
}

type tEncArrayHead struct{}
func (_ *tEncArrayHead) Size(xl interface{}) int              { return ArrayHeadSize(xl.(int)) }
func (_ *tEncArrayHead) Put(data []byte, xl interface{}) int  { return PutArrayHead(data, xl.(int)) }
func TestArray(t *testing.T) {
	h := hex
	testv := []struct{l int; encoded string}{
		{0,    h("90")}, // fixarray
		{1,    h("91")},
		{14,   h("9e")},
		{15,   h("9f")},
		{0x10,   h("dc0010")}, // array16
		{0x11,   h("dc0011")},
		{0x100,  h("dc0100")},
		{0xffff, h("dcffff")},
		{0x10000,    h("dd00010000")}, // array32
		{0xffffffff, h("ddffffffff")},
	}

	for _, tt := range testv {
		test1(t, &tEncArrayHead{}, tt.l, tt.encoded)
	}
}

type tEncMapHead struct{}
func (_ *tEncMapHead) Size(xl interface{}) int              { return MapHeadSize(xl.(int)) }
func (_ *tEncMapHead) Put(data []byte, xl interface{}) int  { return PutMapHead(data, xl.(int)) }
func TestMap(t *testing.T) {
	h := hex
	testv := []struct{l int; encoded string}{
		{0,    h("80")}, // fixmap
		{1,    h("81")},
		{14,   h("8e")},
		{15,   h("8f")},
		{0x10,   h("de0010")}, // map16
		{0x11,   h("de0011")},
		{0x100,  h("de0100")},
		{0xffff, h("deffff")},
		{0x10000,    h("df00010000")}, // map32
		{0xffffffff, h("dfffffffff")},
	}

	for _, tt := range testv {
		test1(t, &tEncMapHead{}, tt.l, tt.encoded)
	}
}
