package serialize

import (
	"encoding/json"
	"reflect"
	"testing"

	"code.justin.tv/rhys/nursery/serialize/protobuf"
	"github.com/golang/protobuf/proto"
)

func testProtobufRecode(tb testing.TB,
	enc func(proto.Message) ([]byte, error),
	dec func([]byte, proto.Message) error) {

	start := new(protobuf.Msg)
	err := toProtobuf(start, golden)
	if err != nil {
		tb.Fatalf("convert: %v", err)
	}

	buf, err := enc(start)
	if err != nil {
		tb.Fatalf("marshal: %v", err)
	}

	switch tb := tb.(type) {
	case *testing.T:
		tb.Logf("len: %d", len(buf))
		if dec == nil {
			tb.Fatalf("test case is attempting to skip unmarshal step")
		}
	case *testing.B:
		tb.SetBytes(int64(len(buf)))
	}

	var end protobuf.Msg
	err = dec(buf, &end)
	if err != nil {
		tb.Fatalf("unmarshal: %v", err)
	}

	silver := new(talGolden)
	err = fromProtobuf(silver, &end)
	if err != nil {
		tb.Fatalf("deconvert: %v", err)
	}

	if _, ok := tb.(*testing.B); ok {
		return
	}

	if have, want := silver, golden; !reflect.DeepEqual(have, want) {
		tb.Errorf("values don't match:\n%#v\n!=\n%#v", have, want)
	}
}

func TestProtobufRecode(t *testing.T) {
	enc := proto.Marshal
	dec := proto.Unmarshal

	testProtobufRecode(t, enc, dec)
}

func TestProtobufTextRecode(t *testing.T) {
	enc := func(pb proto.Message) ([]byte, error) {
		buf := proto.MarshalTextString(pb)
		return []byte(buf), nil
	}
	dec := func(buf []byte, pb proto.Message) error {
		return proto.UnmarshalText(string(buf), pb)
	}

	testProtobufRecode(t, enc, dec)
}

func TestProtobufCompactRecode(t *testing.T) {
	enc := func(pb proto.Message) ([]byte, error) {
		buf := proto.CompactTextString(pb)
		return []byte(buf), nil
	}
	dec := func(buf []byte, pb proto.Message) error {
		return proto.UnmarshalText(string(buf), pb)
	}

	testProtobufRecode(t, enc, dec)
}

func TestProtobufJsonRecode(t *testing.T) {
	enc := func(pb proto.Message) ([]byte, error) { return json.Marshal(pb) }
	dec := func(buf []byte, pb proto.Message) error { return json.Unmarshal(buf, pb) }

	testProtobufRecode(t, enc, dec)
}

func BenchmarkProtobufEncode(b *testing.B) {
	for i := 0; i < b.N; i++ {
		start := new(protobuf.Msg)
		err := toProtobuf(start, golden)
		if err != nil {
			b.Fatalf("convert: %v", err)
		}

		buf, err := proto.Marshal(start)
		if err != nil {
			b.Fatalf("marshal: %v", err)
		}
		if i == 0 {
			b.SetBytes(int64(len(buf)))
		}
	}
}

func BenchmarkProtobufDecode(b *testing.B) {
	start := new(protobuf.Msg)
	err := toProtobuf(start, golden)
	if err != nil {
		b.Fatalf("convert: %v", err)
	}

	buf, err := proto.Marshal(start)
	if err != nil {
		b.Fatalf("marshal: %v", err)
	}
	b.SetBytes(int64(len(buf)))

	for i := 0; i < b.N; i++ {
		var end protobuf.Msg
		err := proto.Unmarshal(buf, &end)
		if err != nil {
			b.Fatalf("unmarshal: %v", err)
		}

		silver := new(talGolden)
		err = fromProtobuf(silver, &end)
		if err != nil {
			b.Fatalf("deconvert: %v", err)
		}
	}
}

func BenchmarkProtobufRecode(b *testing.B) {
	enc := proto.Marshal
	dec := proto.Unmarshal

	for i := 0; i < b.N; i++ {
		testProtobufRecode(b, enc, dec)
	}
}

func BenchmarkProtobufTextRecode(b *testing.B) {
	enc := func(pb proto.Message) ([]byte, error) {
		buf := proto.MarshalTextString(pb)
		return []byte(buf), nil
	}
	dec := func(buf []byte, pb proto.Message) error {
		return proto.UnmarshalText(string(buf), pb)
	}

	for i := 0; i < b.N; i++ {
		testProtobufRecode(b, enc, dec)
	}
}

func BenchmarkProtobufCompactRecode(b *testing.B) {
	enc := func(pb proto.Message) ([]byte, error) {
		buf := proto.CompactTextString(pb)
		return []byte(buf), nil
	}
	dec := func(buf []byte, pb proto.Message) error {
		return proto.UnmarshalText(string(buf), pb)
	}

	for i := 0; i < b.N; i++ {
		testProtobufRecode(b, enc, dec)
	}
}

func BenchmarkProtobufJsonRecode(b *testing.B) {
	enc := func(pb proto.Message) ([]byte, error) { return json.Marshal(pb) }
	dec := func(buf []byte, pb proto.Message) error { return json.Unmarshal(buf, pb) }

	for i := 0; i < b.N; i++ {
		testProtobufRecode(b, enc, dec)
	}
}

func BenchmarkProtobufFindTraceid(b *testing.B) {
	start := new(protobuf.Msg)
	err := toProtobuf(start, golden)
	if err != nil {
		b.Fatalf("convert: %v", err)
	}

	buf, err := proto.Marshal(start)
	if err != nil {
		b.Fatalf("marshal: %v", err)
	}
	b.SetBytes(int64(len(buf)))

	for i := 0; i < b.N; i++ {
		traceid, err := getProtobufTraceid(buf)
		if err != nil {
			b.Fatalf("unmarshal: %v", err)
		}
		if have, want := traceid, golden.TraceID; have != want {
			b.Fatalf("traceid; %d != %d", have, want)
		}
	}
}

func BenchmarkProtobufDecodeTraceid(b *testing.B) {
	start := new(protobuf.Msg)
	err := toProtobuf(start, golden)
	if err != nil {
		b.Fatalf("convert: %v", err)
	}

	buf, err := proto.Marshal(start)
	if err != nil {
		b.Fatalf("marshal: %v", err)
	}
	b.SetBytes(int64(len(buf)))

	for i := 0; i < b.N; i++ {
		var end protobuf.MsgLite
		err := proto.Unmarshal(buf, &end)
		if err != nil {
			b.Fatalf("unmarshal: %v", err)
		}
		if have, want := end.GetTraceid(), golden.TraceID; have != want {
			b.Fatalf("traceid; %d != %d", have, want)
		}
	}
}
