package msgpack_test

import (
	"bufio"
	"bytes"
	"math"
	"reflect"
	"strings"
	"testing"
	"time"

	. "gopkg.in/check.v1"

	"gopkg.in/vmihailenco/msgpack.v2"
	"gopkg.in/vmihailenco/msgpack.v2/codes"
)

type nameStruct struct {
	Name string
}

func Test(t *testing.T) { TestingT(t) }

type MsgpackTest struct {
	buf *bytes.Buffer
	enc *msgpack.Encoder
	dec *msgpack.Decoder
}

var _ = Suite(&MsgpackTest{})

func (t *MsgpackTest) SetUpTest(c *C) {
	t.buf = &bytes.Buffer{}
	t.enc = msgpack.NewEncoder(t.buf)
	t.dec = msgpack.NewDecoder(bufio.NewReader(t.buf))
}

func (t *MsgpackTest) TestUint64(c *C) {
	table := []struct {
		v uint64
		b []byte
	}{
		{0, []byte{0x00}},
		{1, []byte{0x01}},
		{math.MaxInt8 - 1, []byte{0x7e}},
		{math.MaxInt8, []byte{0x7f}},
		{math.MaxInt8 + 1, []byte{0xcc, 0x80}},
		{math.MaxUint8 - 1, []byte{0xcc, 0xfe}},
		{math.MaxUint8, []byte{0xcc, 0xff}},
		{math.MaxUint8 + 1, []byte{0xcd, 0x1, 0x0}},
		{math.MaxUint16 - 1, []byte{0xcd, 0xff, 0xfe}},
		{math.MaxUint16, []byte{0xcd, 0xff, 0xff}},
		{math.MaxUint16 + 1, []byte{0xce, 0x0, 0x1, 0x0, 0x0}},
		{math.MaxUint32 - 1, []byte{0xce, 0xff, 0xff, 0xff, 0xfe}},
		{math.MaxUint32, []byte{0xce, 0xff, 0xff, 0xff, 0xff}},
		{math.MaxUint32 + 1, []byte{0xcf, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}},
		{math.MaxInt64 - 1, []byte{0xcf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}},
		{math.MaxInt64, []byte{0xcf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
	}
	for _, r := range table {
		var int64v int64
		c.Assert(t.enc.Encode(r.v), IsNil)
		c.Assert(t.buf.Bytes(), DeepEquals, r.b, Commentf("n=%d", r.v))
		c.Assert(t.dec.Decode(&int64v), IsNil, Commentf("n=%d", r.v))
		c.Assert(int64v, Equals, int64(r.v), Commentf("n=%d", r.v))

		var uint64v uint64
		c.Assert(t.enc.Encode(r.v), IsNil)
		c.Assert(t.buf.Bytes(), DeepEquals, r.b, Commentf("n=%d", r.v))
		c.Assert(t.dec.Decode(&uint64v), IsNil, Commentf("n=%d", r.v))
		c.Assert(uint64v, Equals, uint64(r.v), Commentf("n=%d", r.v))

		c.Assert(t.enc.Encode(r.v), IsNil)
		iface, err := t.dec.DecodeInterface()
		c.Assert(err, IsNil)
		c.Assert(iface, Equals, r.v)
	}
}

func (t *MsgpackTest) TestInt64(c *C) {
	table := []struct {
		v int64
		b []byte
	}{
		{math.MinInt64, []byte{0xd3, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}},
		{math.MinInt32 - 1, []byte{0xd3, 0xff, 0xff, 0xff, 0xff, 0x7f, 0xff, 0xff, 0xff}},
		{math.MinInt32, []byte{0xd2, 0x80, 0x00, 0x00, 0x00}},
		{math.MinInt32 + 1, []byte{0xd2, 0x80, 0x00, 0x00, 0x01}},
		{math.MinInt16 - 1, []byte{0xd2, 0xff, 0xff, 0x7f, 0xff}},
		{math.MinInt16, []byte{0xd1, 0x80, 0x00}},
		{math.MinInt16 + 1, []byte{0xd1, 0x80, 0x01}},
		{math.MinInt8 - 1, []byte{0xd1, 0xff, 0x7f}},
		{math.MinInt8, []byte{0xd0, 0x80}},
		{math.MinInt8 + 1, []byte{0xd0, 0x81}},
		{-33, []byte{0xd0, 0xdf}},
		{-32, []byte{0xe0}},
		{-31, []byte{0xe1}},
		{-1, []byte{0xff}},
		{0, []byte{0x00}},
		{1, []byte{0x01}},
		{math.MaxInt8 - 1, []byte{0x7e}},
		{math.MaxInt8, []byte{0x7f}},
		{math.MaxInt8 + 1, []byte{0xcc, 0x80}},
		{math.MaxUint8 - 1, []byte{0xcc, 0xfe}},
		{math.MaxUint8, []byte{0xcc, 0xff}},
		{math.MaxUint8 + 1, []byte{0xcd, 0x1, 0x0}},
		{math.MaxUint16 - 1, []byte{0xcd, 0xff, 0xfe}},
		{math.MaxUint16, []byte{0xcd, 0xff, 0xff}},
		{math.MaxUint16 + 1, []byte{0xce, 0x0, 0x1, 0x0, 0x0}},
		{math.MaxUint32 - 1, []byte{0xce, 0xff, 0xff, 0xff, 0xfe}},
		{math.MaxUint32, []byte{0xce, 0xff, 0xff, 0xff, 0xff}},
		{math.MaxUint32 + 1, []byte{0xcf, 0x0, 0x0, 0x0, 0x1, 0x0, 0x0, 0x0, 0x0}},
		{math.MaxInt64 - 1, []byte{0xcf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfe}},
		{math.MaxInt64, []byte{0xcf, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
	}
	for _, r := range table {
		var int64v int64
		c.Assert(t.enc.Encode(r.v), IsNil)
		c.Assert(t.buf.Bytes(), DeepEquals, r.b, Commentf("n=%d", r.v))
		c.Assert(t.dec.Decode(&int64v), IsNil, Commentf("n=%d", r.v))
		c.Assert(int64v, Equals, int64(r.v), Commentf("n=%d", r.v))

		var uint64v uint64
		c.Assert(t.enc.Encode(r.v), IsNil)
		c.Assert(t.buf.Bytes(), DeepEquals, r.b, Commentf("n=%d", r.v))
		c.Assert(t.dec.Decode(&uint64v), IsNil, Commentf("n=%d", r.v))
		c.Assert(uint64v, Equals, uint64(r.v), Commentf("n=%d", r.v))

		c.Assert(t.enc.Encode(r.v), IsNil)
		v, err := t.dec.DecodeInterface()
		c.Assert(err, IsNil)
		if r.v < 0 {
			c.Assert(v, Equals, r.v)
		} else {
			c.Assert(v, Equals, uint64(r.v))
		}
	}
}

func (t *MsgpackTest) TestFloat32(c *C) {
	table := []struct {
		v float32
		b []byte
	}{
		{0.1, []byte{codes.Float, 0x3d, 0xcc, 0xcc, 0xcd}},
		{0.2, []byte{codes.Float, 0x3e, 0x4c, 0xcc, 0xcd}},
		{-0.1, []byte{codes.Float, 0xbd, 0xcc, 0xcc, 0xcd}},
		{-0.2, []byte{codes.Float, 0xbe, 0x4c, 0xcc, 0xcd}},
		{float32(math.Inf(1)), []byte{codes.Float, 0x7f, 0x80, 0x00, 0x00}},
		{float32(math.Inf(-1)), []byte{codes.Float, 0xff, 0x80, 0x00, 0x00}},
		{math.MaxFloat32, []byte{codes.Float, 0x7f, 0x7f, 0xff, 0xff}},
		{math.SmallestNonzeroFloat32, []byte{codes.Float, 0x0, 0x0, 0x0, 0x1}},
	}
	for _, r := range table {
		c.Assert(t.enc.Encode(r.v), IsNil)
		c.Assert(t.buf.Bytes(), DeepEquals, r.b, Commentf("err encoding %v", r.v))

		var f32 float32
		c.Assert(t.dec.Decode(&f32), IsNil)
		c.Assert(f32, Equals, r.v)

		// Pass pointer to skip fast-path and trigger reflect.
		c.Assert(t.enc.Encode(&r.v), IsNil)
		c.Assert(t.buf.Bytes(), DeepEquals, r.b, Commentf("err encoding %v", r.v))

		var f64 float64
		c.Assert(t.dec.Decode(&f64), IsNil)
		c.Assert(float32(f64), Equals, r.v)

		c.Assert(t.enc.Encode(r.v), IsNil)
		iface, err := t.dec.DecodeInterface()
		c.Assert(err, IsNil)
		c.Assert(iface, Equals, r.v)
	}

	in := float32(math.NaN())
	c.Assert(t.enc.Encode(in), IsNil)
	var out float32
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(math.IsNaN(float64(out)), Equals, true)
}

func (t *MsgpackTest) TestFloat64(c *C) {
	table := []struct {
		v float64
		b []byte
	}{
		{.1, []byte{0xcb, 0x3f, 0xb9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a}},
		{.2, []byte{0xcb, 0x3f, 0xc9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a}},
		{-.1, []byte{0xcb, 0xbf, 0xb9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a}},
		{-.2, []byte{0xcb, 0xbf, 0xc9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9a}},
		{math.Inf(1), []byte{0xcb, 0x7f, 0xf0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
		{math.Inf(-1), []byte{0xcb, 0xff, 0xf0, 0x00, 0x00, 0x0, 0x0, 0x0, 0x0}},
		{math.MaxFloat64, []byte{0xcb, 0x7f, 0xef, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}},
		{math.SmallestNonzeroFloat64, []byte{0xcb, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1}},
	}
	for _, r := range table {
		c.Assert(t.enc.Encode(r.v), IsNil)
		c.Assert(t.buf.Bytes(), DeepEquals, r.b, Commentf("err encoding %v", r.v))

		var v float64
		c.Assert(t.dec.Decode(&v), IsNil)
		c.Assert(v, Equals, r.v)

		c.Assert(t.enc.Encode(r.v), IsNil)
		iface, err := t.dec.DecodeInterface()
		c.Assert(err, IsNil)
		c.Assert(iface, Equals, r.v)
	}

	in := math.NaN()
	c.Assert(t.enc.Encode(in), IsNil)
	var out float64
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(math.IsNaN(out), Equals, true)
}

func (t *MsgpackTest) TestDecodeNil(c *C) {
	c.Assert(t.dec.Decode(nil), NotNil)
}

func (t *MsgpackTest) TestTime(c *C) {
	in := time.Now()
	var out time.Time
	c.Assert(t.enc.Encode(in), IsNil)
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(out.Equal(in), Equals, true)

	var zero time.Time
	c.Assert(t.enc.Encode(zero), IsNil)
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(out.Equal(zero), Equals, true)
	c.Assert(out.IsZero(), Equals, true)
}

func (t *MsgpackTest) TestBin(c *C) {
	lowBin8 := []byte(strings.Repeat("w", 32))
	highBin8 := []byte(strings.Repeat("w", 255))
	lowBin16 := []byte(strings.Repeat("w", 256))
	highBin16 := []byte(strings.Repeat("w", 65535))
	lowBin32 := []byte(strings.Repeat("w", 65536))
	for _, i := range []struct {
		src []byte
		b   []byte
	}{
		{
			lowBin8,
			append([]byte{0xc4, byte(len(lowBin8))}, lowBin8...),
		},
		{
			highBin8,
			append([]byte{0xc4, byte(len(highBin8))}, highBin8...),
		},
		{
			lowBin16,
			append([]byte{0xc5, 1, 0}, lowBin16...),
		},
		{
			highBin16,
			append([]byte{0xc5, 255, 255}, highBin16...),
		},
		{
			lowBin32,
			append([]byte{0xc6, 0, 1, 0, 0}, lowBin32...),
		},
	} {
		c.Assert(t.enc.Encode(i.src), IsNil)
		c.Assert(t.buf.Bytes(), DeepEquals, i.b)

		var dst []byte
		c.Assert(t.dec.Decode(&dst), IsNil)
		c.Assert(dst, DeepEquals, i.src)

		c.Assert(t.enc.Encode(i.src), IsNil)
		iface, err := t.dec.DecodeInterface()
		c.Assert(err, IsNil)
		c.Assert(iface, DeepEquals, i.src)
	}
}

func (t *MsgpackTest) TestString(c *C) {
	highFixStr := strings.Repeat("w", 31)
	lowStr8 := strings.Repeat("w", 32)
	highStr8 := strings.Repeat("w", 255)
	lowStr16 := strings.Repeat("w", 256)
	highStr16 := strings.Repeat("w", 65535)
	lowStr32 := strings.Repeat("w", 65536)
	for _, i := range []struct {
		src string
		b   []byte
	}{
		{"", []byte{0xa0}},                          // fixstr
		{"a", []byte{0xa1, 'a'}},                    // fixstr
		{"hello", append([]byte{0xa5}, "hello"...)}, // fixstr
		{
			highFixStr,
			append([]byte{0xbf}, highFixStr...),
		},
		{
			lowStr8,
			append([]byte{0xd9, byte(len(lowStr8))}, lowStr8...),
		},
		{
			highStr8,
			append([]byte{0xd9, byte(len(highStr8))}, highStr8...),
		},
		{
			lowStr16,
			append([]byte{0xda, 1, 0}, lowStr16...),
		},
		{
			highStr16,
			append([]byte{0xda, 255, 255}, highStr16...),
		},
		{
			lowStr32,
			append([]byte{0xdb, 0, 1, 0, 0}, lowStr32...),
		},
	} {
		c.Assert(t.enc.Encode(i.src), IsNil)
		c.Assert(t.buf.Bytes(), DeepEquals, i.b)

		var dst string
		c.Assert(t.dec.Decode(&dst), IsNil)
		c.Assert(dst, Equals, i.src)

		c.Assert(t.enc.Encode(i.src), IsNil)
		iface, err := t.dec.DecodeInterface()
		c.Assert(err, IsNil)
		c.Assert(iface, DeepEquals, i.src)
	}
}

func (t *MsgpackTest) TestLargeBytes(c *C) {
	N := int(1e6)

	src := bytes.Repeat([]byte{'1'}, N)
	c.Assert(t.enc.Encode(src), IsNil)
	var dst []byte
	c.Assert(t.dec.Decode(&dst), IsNil)
	c.Assert(dst, DeepEquals, src)
}

func (t *MsgpackTest) TestLargeString(c *C) {
	N := int(1e6)

	src := string(bytes.Repeat([]byte{'1'}, N))
	c.Assert(t.enc.Encode(src), IsNil)
	var dst string
	c.Assert(t.dec.Decode(&dst), IsNil)
	c.Assert(dst, Equals, src)
}

func (t *MsgpackTest) TestSliceOfStructs(c *C) {
	in := []*nameStruct{&nameStruct{"hello"}}
	var out []*nameStruct
	c.Assert(t.enc.Encode(in), IsNil)
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(out, DeepEquals, in)
}

func (t *MsgpackTest) TestMap(c *C) {
	for _, i := range []struct {
		m map[string]string
		b []byte
	}{
		{map[string]string{}, []byte{0x80}},
		{map[string]string{"hello": "world"}, []byte{0x81, 0xa5, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0xa5, 0x77, 0x6f, 0x72, 0x6c, 0x64}},
	} {
		c.Assert(t.enc.Encode(i.m), IsNil)
		c.Assert(t.buf.Bytes(), DeepEquals, i.b, Commentf("err encoding %v", i.m))
		var m map[string]string
		c.Assert(t.dec.Decode(&m), IsNil)
		c.Assert(m, DeepEquals, i.m)
	}
}

func (t *MsgpackTest) TestStructNil(c *C) {
	var dst *nameStruct

	c.Assert(t.enc.Encode(nameStruct{Name: "foo"}), IsNil)
	c.Assert(t.dec.Decode(&dst), IsNil)
	c.Assert(dst, Not(IsNil))
	c.Assert(dst.Name, Equals, "foo")
}

func (t *MsgpackTest) TestStructUnknownField(c *C) {
	in := struct {
		Field1 string
		Field2 string
		Field3 string
	}{
		Field1: "value1",
		Field2: "value2",
		Field3: "value3",
	}
	c.Assert(t.enc.Encode(in), IsNil)

	out := struct {
		Field2 string
	}{}
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(out.Field2, Equals, "value2")
}

//------------------------------------------------------------------------------

type coderStruct struct {
	name string
}

type wrapperStruct struct {
	coderStruct `msgpack:",inline"`
}

var (
	_ msgpack.CustomEncoder = &coderStruct{}
	_ msgpack.CustomDecoder = &coderStruct{}
)

func (s *coderStruct) Name() string {
	return s.name
}

func (s *coderStruct) EncodeMsgpack(enc *msgpack.Encoder) error {
	return enc.Encode(s.name)
}

func (s *coderStruct) DecodeMsgpack(dec *msgpack.Decoder) error {
	return dec.Decode(&s.name)
}

func (t *MsgpackTest) TestCoder(c *C) {
	in := &coderStruct{name: "hello"}
	var out coderStruct
	c.Assert(t.enc.Encode(in), IsNil)
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(out.Name(), Equals, "hello")
}

func (t *MsgpackTest) TestNilCoder(c *C) {
	in := &coderStruct{name: "hello"}
	var out *coderStruct
	c.Assert(t.enc.Encode(in), IsNil)
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(out.Name(), Equals, "hello")
}

func (t *MsgpackTest) TestNilCoderValue(c *C) {
	c.Skip("TODO")

	in := &coderStruct{name: "hello"}
	var out *coderStruct
	v := reflect.ValueOf(out)
	c.Assert(t.enc.Encode(in), IsNil)
	c.Assert(t.dec.DecodeValue(v), IsNil)
	c.Assert(out.Name(), Equals, "hello")
}

func (t *MsgpackTest) TestPtrToCoder(c *C) {
	in := &coderStruct{name: "hello"}
	var out coderStruct
	out2 := &out
	c.Assert(t.enc.Encode(in), IsNil)
	c.Assert(t.dec.Decode(&out2), IsNil)
	c.Assert(out.Name(), Equals, "hello")
}

func (t *MsgpackTest) TestWrappedCoder(c *C) {
	in := &wrapperStruct{coderStruct: coderStruct{name: "hello"}}
	var out wrapperStruct
	c.Assert(t.enc.Encode(in), IsNil)
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(out.Name(), Equals, "hello")
}

//------------------------------------------------------------------------------

type struct2 struct {
	Name string
}

type struct1 struct {
	Name    string
	Struct2 struct2
}

func (t *MsgpackTest) TestNestedStructs(c *C) {
	in := &struct1{Name: "hello", Struct2: struct2{Name: "world"}}
	var out struct1
	c.Assert(t.enc.Encode(in), IsNil)
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(out.Name, Equals, in.Name)
	c.Assert(out.Struct2.Name, Equals, in.Struct2.Name)
}

type Struct4 struct {
	Name2 string
}

type Struct3 struct {
	Struct4
	Name1 string
}

func TestEmbedding(t *testing.T) {
	in := &Struct3{
		Name1: "hello",
		Struct4: Struct4{
			Name2: "world",
		},
	}
	var out Struct3

	b, err := msgpack.Marshal(in)
	if err != nil {
		t.Fatal(err)
	}

	err = msgpack.Unmarshal(b, &out)
	if err != nil {
		t.Fatal(err)
	}
	if out.Name1 != in.Name1 {
		t.Fatalf("")
	}
	if out.Name2 != in.Name2 {
		t.Fatalf("")
	}
}

func (t *MsgpackTest) TestSliceNil(c *C) {
	in := [][]*int{nil}
	var out [][]*int

	c.Assert(t.enc.Encode(in), IsNil)
	c.Assert(t.dec.Decode(&out), IsNil)
	c.Assert(out, DeepEquals, in)
}

//------------------------------------------------------------------------------

func (t *MsgpackTest) TestMapStringInterface(c *C) {
	in := map[string]interface{}{
		"foo": "bar",
		"hello": map[string]interface{}{
			"foo": "bar",
		},
	}
	var out map[string]interface{}

	c.Assert(t.enc.Encode(in), IsNil)
	c.Assert(t.dec.Decode(&out), IsNil)

	c.Assert(out["foo"], Equals, "bar")
	mm := out["hello"].(map[interface{}]interface{})
	c.Assert(mm["foo"], Equals, "bar")
}

func (t *MsgpackTest) TestMapStringInterface2(c *C) {
	buf := &bytes.Buffer{}
	enc := msgpack.NewEncoder(buf)
	dec := msgpack.NewDecoder(buf)
	dec.DecodeMapFunc = func(d *msgpack.Decoder) (interface{}, error) {
		n, err := d.DecodeMapLen()
		if err != nil {
			return nil, err
		}

		m := make(map[string]interface{}, n)
		for i := 0; i < n; i++ {
			mk, err := d.DecodeString()
			if err != nil {
				return nil, err
			}

			mv, err := d.DecodeInterface()
			if err != nil {
				return nil, err
			}

			m[mk] = mv
		}
		return m, nil
	}

	in := map[string]interface{}{
		"foo": "bar",
		"hello": map[string]interface{}{
			"foo": "bar",
		},
	}
	var out map[string]interface{}

	c.Assert(enc.Encode(in), IsNil)
	c.Assert(dec.Decode(&out), IsNil)

	c.Assert(out["foo"], Equals, "bar")
	mm := out["hello"].(map[string]interface{})
	c.Assert(mm["foo"], Equals, "bar")
}

func TestDecodeExtWithMap(t *testing.T) {
	type S struct {
		I int
	}
	msgpack.RegisterExt(2, S{})
	b, err := msgpack.Marshal(&S{I: 42})
	if err != nil {
		t.Fatal(err)
	}
	v := make(map[string]interface{})
	if err := msgpack.Unmarshal(b, &v); err != nil {
		t.Fatal(err)
	}
	ev := map[string]interface{}{"I": uint64(42)}
	if !reflect.DeepEqual(v, ev) {
		t.Fatalf("expect %#v but got %#v", ev, v)
	}
}
