package gostruct

import (
	"reflect"
	"testing"
)

func TestBinaryPack_CalcSize(t *testing.T) {
	cases := []struct {
		in   []string
		want int
		e    bool
	}{
		{[]string{}, 0, false},
		{[]string{"I", "I", "I", "4s"}, 16, false},
		{[]string{"H", "H", "I", "H", "8s", "H"}, 20, false},
		{[]string{"i", "?", "H", "f", "d", "h", "I", "5s"}, 30, false},
		{[]string{"?", "h", "H", "i", "I", "l", "L", "q", "Q", "f", "d", "1s"}, 50, false},
		// Unknown tokens
		{[]string{"a", "b", "c"}, 0, true},
	}

	for _, c := range cases {
		got, err := CalcSize(c.in)

		if err != nil && !c.e {
			t.Errorf("CalcSize(%v) raised %v", c.in, err)
		}

		if err == nil && got != c.want {
			t.Errorf("CalcSize(%v) == %d want %d", c.in, got, c.want)
		}
	}
}

func TestBinaryPack_Pack(t *testing.T) {
	cases := []struct {
		f    []string
		a    []interface{}
		want []byte
		e    bool
	}{
		{[]string{"?", "?"}, []interface{}{true, false}, []byte{1, 0}, false},
		{[]string{"h", "h", "h"}, []interface{}{0, 5, -5},
			[]byte{0, 0, 5, 0, 251, 255}, false},
		{[]string{"H", "H", "H"}, []interface{}{0, 5, 2300},
			[]byte{0, 0, 5, 0, 252, 8}, false},
		{[]string{"i", "i", "i"}, []interface{}{0, 5, -5},
			[]byte{0, 0, 0, 0, 5, 0, 0, 0, 251, 255, 255, 255}, false},
		{[]string{"I", "I", "I"}, []interface{}{0, 5, 2300},
			[]byte{0, 0, 0, 0, 5, 0, 0, 0, 252, 8, 0, 0}, false},
		{[]string{"f", "f", "f"}, []interface{}{float32(0.0), float32(5.3), float32(-5.3)},
			[]byte{0, 0, 0, 0, 154, 153, 169, 64, 154, 153, 169, 192}, false},
		{[]string{"d", "d", "d"}, []interface{}{0.0, 5.3, -5.3},
			[]byte{0, 0, 0, 0, 0, 0, 0, 0, 51, 51, 51, 51, 51, 51, 21, 64, 51, 51, 51, 51, 51, 51, 21, 192}, false},
		{[]string{"1s", "2s", "10s"}, []interface{}{"a", "bb", "1234567890"},
			[]byte{97, 98, 98, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48}, false},
		{[]string{"I", "I", "I", "4s"}, []interface{}{1, 2, 4, "DUMP"},
			[]byte{1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 68, 85, 77, 80}, false},
		// Wrong format length
		{[]string{"I", "I", "I", "4s"}, []interface{}{1, 4, "DUMP"}, nil, true},
		// Wrong format token
		{[]string{"I", "a", "I", "4s"}, []interface{}{1, 2, 4, "DUMP"}, nil, true},
		// Wrong types
		{[]string{"?"}, []interface{}{1.0}, nil, true},
		{[]string{"H"}, []interface{}{int8(1)}, nil, true},
		{[]string{"I"}, []interface{}{int32(2)}, nil, true},
		{[]string{"Q"}, []interface{}{int64(3)}, nil, true},
		{[]string{"f"}, []interface{}{float64(2.5)}, nil, true},
		{[]string{"d"}, []interface{}{float32(2.5)}, nil, true},
		{[]string{"1s"}, []interface{}{'a'}, nil, true},
	}

	for _, c := range cases {
		got, err := Pack(c.f, c.a)

		if err != nil && !c.e {
			t.Errorf("Pack(%v, %v) raised %v", c.f, c.a, err)
		}

		if err == nil && !reflect.DeepEqual(got, c.want) {
			t.Errorf("Pack(%v, %v) == %v want %v", c.f, c.a, got, c.want)
		}
	}
}

func TestBinaryPack_UnPack(t *testing.T) {
	cases := []struct {
		f    []string
		a    []byte
		want []interface{}
		e    bool
	}{
		{[]string{"?", "?"}, []byte{1, 0}, []interface{}{true, false}, false},
		{[]string{"h", "h", "h"}, []byte{0, 0, 5, 0, 251, 255},
			[]interface{}{0, 5, -5}, false},
		{[]string{"H", "H", "H"}, []byte{0, 0, 5, 0, 252, 8},
			[]interface{}{0, 5, 2300}, false},
		{[]string{"i", "i", "i"}, []byte{0, 0, 0, 0, 5, 0, 0, 0, 251, 255, 255, 255},
			[]interface{}{0, 5, -5}, false},
		{[]string{"I", "I", "I"}, []byte{0, 0, 0, 0, 5, 0, 0, 0, 252, 8, 0, 0},
			[]interface{}{0, 5, 2300}, false},
		{[]string{"f", "f", "f"},
			[]byte{0, 0, 0, 0, 154, 153, 169, 64, 154, 153, 169, 192},
			[]interface{}{float32(0.0), float32(5.3), float32(-5.3)}, false},
		{[]string{"d", "d", "d"},
			[]byte{0, 0, 0, 0, 0, 0, 0, 0, 51, 51, 51, 51, 51, 51, 21, 64, 51, 51, 51, 51, 51, 51, 21, 192},
			[]interface{}{0.0, 5.3, -5.3}, false},
		{[]string{"1s", "2s", "10s"},
			[]byte{97, 98, 98, 49, 50, 51, 52, 53, 54, 55, 56, 57, 48},
			[]interface{}{"a", "bb", "1234567890"}, false},
		{[]string{"I", "I", "I", "4s"},
			[]byte{1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 68, 85, 77, 80},
			[]interface{}{1, 2, 4, "DUMP"}, false},
		// Wrong format length
		{[]string{"I", "I", "I", "4s", "H"}, []byte{1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 68, 85, 77, 80},
			nil, true},
		// Wrong format token
		{[]string{"I", "a", "I", "4s"}, []byte{1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 68, 85, 77, 80},
			nil, true},
	}

	for _, c := range cases {
		got, err := UnPack(c.f, c.a)

		if err != nil && !c.e {
			t.Errorf("UnPack(%v, %v) raised %v", c.f, c.a, err)
		}

		if err == nil && !reflect.DeepEqual(got, c.want) {
			t.Errorf("UnPack(%v, %v) == %v want %v", c.f, c.a, got, c.want)
		}
	}
}

func TestBinaryPackPartialRead(t *testing.T) {
	cases := []struct {
		f    []string
		a    []byte
		i    int // Position of expected value
		want interface{}
		e    bool
	}{
		{[]string{"I", "I", "I"}, // []interface{}{1, 2, 4, "DUMP"} <- encoded collection has 4 values
			[]byte{1, 0, 0, 0, 2, 0, 0, 0, 4, 0, 0, 0, 68, 85, 77, 80}, 2, 4, false},
	}

	for _, c := range cases {
		got, err := UnPack(c.f, c.a)

		if err != nil && !c.e {
			t.Errorf("UnPack(%v, %v) raised %v", c.f, c.a, err)
		}

		if err == nil && got[c.i] != c.want {
			t.Errorf("UnPack(%v, %v) == %v want %v", c.f, c.a, got[c.i], c.want)
		}
	}
}

func TestBinaryPackUsageExample(t *testing.T) {
	// Prepare format (slice of strings)
	format := []string{"I", "?", "d", "6s"}

	// Prepare values to pack
	values := []interface{}{4, true, 3.14, "Golang"}

	// Pack values to struct
	data, _ := Pack(format, values)

	// Unpack binary data to []interface{}
	unpacked_values, _ := UnPack(format, data)

	if !reflect.DeepEqual(unpacked_values, values) {
		t.Errorf("Unpacked %v != original %v", unpacked_values, values)
	}

	// You can calculate size of expected binary data by format
	size, _ := CalcSize(format)

	if size != len(data) {
		t.Errorf("Size(%v) != %v", size, len(data))
	}
}
