// Copyright (c) 2015 Arista Networks, Inc.
// Use of this source code is governed by the Apache License 2.0
// that can be found in the COPYING file.

package key_test

import (
	"encoding/json"
	"fmt"
	"strconv"
	"testing"

	. "github.com/aristanetworks/goarista/key"
	"github.com/aristanetworks/goarista/test"
	"github.com/aristanetworks/goarista/value"
)

type compareMe struct {
	i int
}

func (c compareMe) Equal(other interface{}) bool {
	o, ok := other.(compareMe)
	return ok && c == o
}

type customKey struct {
	i int
}

var _ value.Value = customKey{}

func (c customKey) String() string {
	return fmt.Sprintf("customKey=%d", c.i)
}

func (c customKey) MarshalJSON() ([]byte, error) {
	return nil, nil
}

func (c customKey) ToBuiltin() interface{} {
	return c.i
}

func TestKeyEqual(t *testing.T) {
	tests := []struct {
		a      Key
		b      Key
		result bool
	}{{
		a:      New("foo"),
		b:      New("foo"),
		result: true,
	}, {
		a:      New("foo"),
		b:      New("bar"),
		result: false,
	}, {
		a:      New([]interface{}{}),
		b:      New("bar"),
		result: false,
	}, {
		a:      New([]interface{}{}),
		b:      New([]interface{}{}),
		result: true,
	}, {
		a:      New([]interface{}{"a", "b"}),
		b:      New([]interface{}{"a"}),
		result: false,
	}, {
		a:      New([]interface{}{"a", "b"}),
		b:      New([]interface{}{"b", "a"}),
		result: false,
	}, {
		a:      New([]interface{}{"a", "b"}),
		b:      New([]interface{}{"a", "b"}),
		result: true,
	}, {
		a:      New([]interface{}{"a", map[string]interface{}{"b": "c"}}),
		b:      New([]interface{}{"a", map[string]interface{}{"c": "b"}}),
		result: false,
	}, {
		a:      New([]interface{}{"a", map[string]interface{}{"b": "c"}}),
		b:      New([]interface{}{"a", map[string]interface{}{"b": "c"}}),
		result: true,
	}, {
		a:      New(map[string]interface{}{}),
		b:      New("bar"),
		result: false,
	}, {
		a:      New(map[string]interface{}{}),
		b:      New(map[string]interface{}{}),
		result: true,
	}, {
		a:      New(map[string]interface{}{"a": 3}),
		b:      New(map[string]interface{}{}),
		result: false,
	}, {
		a:      New(map[string]interface{}{"a": 3}),
		b:      New(map[string]interface{}{"b": 4}),
		result: false,
	}, {
		a:      New(map[string]interface{}{"a": 4, "b": 5}),
		b:      New(map[string]interface{}{"a": 4}),
		result: false,
	}, {
		a:      New(map[string]interface{}{"a": 3}),
		b:      New(map[string]interface{}{"a": 4}),
		result: false,
	}, {
		a:      New(map[string]interface{}{"a": 3}),
		b:      New(map[string]interface{}{"a": 3}),
		result: true,
	}, {
		a:      New(map[string]interface{}{"a": map[Key]interface{}{New("b"): 3}}),
		b:      New(map[string]interface{}{"a": map[Key]interface{}{New("b"): 4}}),
		result: false,
	}, {
		a:      New(map[string]interface{}{"a": map[Key]interface{}{New("b"): 4, New("c"): 5}}),
		b:      New(map[string]interface{}{"a": map[Key]interface{}{New("b"): 4}}),
		result: false,
	}, {
		a:      New(map[string]interface{}{"a": map[Key]interface{}{New("b"): 4, New("c"): 5}}),
		b:      New(map[string]interface{}{"a": map[Key]interface{}{New("b"): 4, New("c"): 5}}),
		result: true,
	}, {
		a:      New(map[string]interface{}{"a": map[Key]interface{}{New("b"): 4}}),
		b:      New(map[string]interface{}{"a": map[Key]interface{}{New("b"): 4}}),
		result: true,
	}, {
		a:      New(map[string]interface{}{"a": compareMe{i: 3}}),
		b:      New(map[string]interface{}{"a": compareMe{i: 3}}),
		result: true,
	}, {
		a:      New(map[string]interface{}{"a": compareMe{i: 3}}),
		b:      New(map[string]interface{}{"a": compareMe{i: 4}}),
		result: false,
	}, {
		a:      New(customKey{i: 42}),
		b:      New(customKey{i: 42}),
		result: true,
	}}

	for _, tcase := range tests {
		if tcase.a.Equal(tcase.b) != tcase.result {
			t.Errorf("Wrong result for case:\na: %#v\nb: %#v\nresult: %#v",
				tcase.a,
				tcase.b,
				tcase.result)
		}
	}

	if New("a").Equal(32) {
		t.Error("Wrong result for different types case")
	}
}

func TestGetFromMap(t *testing.T) {
	tests := []struct {
		k     Key
		m     map[Key]interface{}
		v     interface{}
		found bool
	}{{
		k:     New("a"),
		m:     map[Key]interface{}{New("a"): "b"},
		v:     "b",
		found: true,
	}, {
		k:     New(uint32(35)),
		m:     map[Key]interface{}{New(uint32(35)): "c"},
		v:     "c",
		found: true,
	}, {
		k:     New(uint32(37)),
		m:     map[Key]interface{}{New(uint32(36)): "c"},
		found: false,
	}, {
		k:     New(uint32(37)),
		m:     map[Key]interface{}{},
		found: false,
	}, {
		k: New([]interface{}{"a", "b"}),
		m: map[Key]interface{}{
			New([]interface{}{"a", "b"}): "foo",
		},
		v:     "foo",
		found: true,
	}, {
		k: New([]interface{}{"a", "b"}),
		m: map[Key]interface{}{
			New([]interface{}{"a", "b", "c"}): "foo",
		},
		found: false,
	}, {
		k: New([]interface{}{"a", map[string]interface{}{"b": "c"}}),
		m: map[Key]interface{}{
			New([]interface{}{"a", map[string]interface{}{"b": "c"}}): "foo",
		},
		v:     "foo",
		found: true,
	}, {
		k: New([]interface{}{"a", map[string]interface{}{"b": "c"}}),
		m: map[Key]interface{}{
			New([]interface{}{"a", map[string]interface{}{"c": "b"}}): "foo",
		},
		found: false,
	}, {
		k: New(map[string]interface{}{"a": "b", "c": uint64(4)}),
		m: map[Key]interface{}{
			New(map[string]interface{}{"a": "b", "c": uint64(4)}): "foo",
		},
		v:     "foo",
		found: true,
	}, {
		k: New(map[string]interface{}{"a": "b", "c": uint64(4)}),
		m: map[Key]interface{}{
			New(map[string]interface{}{"a": "b", "c": uint64(5)}): "foo",
		},
		found: false,
	}, {
		k:     New(customKey{i: 42}),
		m:     map[Key]interface{}{New(customKey{i: 42}): "c"},
		v:     "c",
		found: true,
	}, {
		k:     New(customKey{i: 42}),
		m:     map[Key]interface{}{New(customKey{i: 43}): "c"},
		found: false,
	}, {
		k: New(map[string]interface{}{
			"damn": map[Key]interface{}{
				New(map[string]interface{}{"a": uint32(42),
					"b": uint32(51)}): true}}),
		m: map[Key]interface{}{
			New(map[string]interface{}{
				"damn": map[Key]interface{}{
					New(map[string]interface{}{"a": uint32(42),
						"b": uint32(51)}): true}}): "foo",
		},
		v:     "foo",
		found: true,
	}, {
		k: New(map[string]interface{}{
			"damn": map[Key]interface{}{
				New(map[string]interface{}{"a": uint32(42),
					"b": uint32(52)}): true}}),
		m: map[Key]interface{}{
			New(map[string]interface{}{
				"damn": map[Key]interface{}{
					New(map[string]interface{}{"a": uint32(42),
						"b": uint32(51)}): true}}): "foo",
		},
		found: false,
	}, {
		k: New(map[string]interface{}{
			"nested": map[string]interface{}{
				"a": uint32(42), "b": uint32(51)}}),
		m: map[Key]interface{}{
			New(map[string]interface{}{
				"nested": map[string]interface{}{
					"a": uint32(42), "b": uint32(51)}}): "foo",
		},
		v:     "foo",
		found: true,
	}, {
		k: New(map[string]interface{}{
			"nested": map[string]interface{}{
				"a": uint32(42), "b": uint32(52)}}),
		m: map[Key]interface{}{
			New(map[string]interface{}{
				"nested": map[string]interface{}{
					"a": uint32(42), "b": uint32(51)}}): "foo",
		},
		found: false,
	}}

	for _, tcase := range tests {
		v, ok := tcase.m[tcase.k]
		if tcase.found != ok {
			t.Errorf("Wrong retrieval result for case:\nk: %#v\nm: %#v\nv: %#v",
				tcase.k,
				tcase.m,
				tcase.v)
		} else if tcase.found && !ok {
			t.Errorf("Unable to retrieve value for case:\nk: %#v\nm: %#v\nv: %#v",
				tcase.k,
				tcase.m,
				tcase.v)
		} else if tcase.found && !test.DeepEqual(tcase.v, v) {
			t.Errorf("Wrong result for case:\nk: %#v\nm: %#v\nv: %#v",
				tcase.k,
				tcase.m,
				tcase.v)
		}
	}
}

func TestDeleteFromMap(t *testing.T) {
	tests := []struct {
		k Key
		m map[Key]interface{}
		r map[Key]interface{}
	}{{
		k: New("a"),
		m: map[Key]interface{}{New("a"): "b"},
		r: map[Key]interface{}{},
	}, {
		k: New("b"),
		m: map[Key]interface{}{New("a"): "b"},
		r: map[Key]interface{}{New("a"): "b"},
	}, {
		k: New("a"),
		m: map[Key]interface{}{},
		r: map[Key]interface{}{},
	}, {
		k: New(uint32(35)),
		m: map[Key]interface{}{New(uint32(35)): "c"},
		r: map[Key]interface{}{},
	}, {
		k: New(uint32(36)),
		m: map[Key]interface{}{New(uint32(35)): "c"},
		r: map[Key]interface{}{New(uint32(35)): "c"},
	}, {
		k: New(uint32(37)),
		m: map[Key]interface{}{},
		r: map[Key]interface{}{},
	}, {
		k: New(map[string]interface{}{"a": "b", "c": uint64(4)}),
		m: map[Key]interface{}{
			New(map[string]interface{}{"a": "b", "c": uint64(4)}): "foo",
		},
		r: map[Key]interface{}{},
	}, {
		k: New(customKey{i: 42}),
		m: map[Key]interface{}{New(customKey{i: 42}): "c"},
		r: map[Key]interface{}{},
	}}

	for _, tcase := range tests {
		delete(tcase.m, tcase.k)
		if !test.DeepEqual(tcase.m, tcase.r) {
			t.Errorf("Wrong result for case:\nk: %#v\nm: %#v\nr: %#v",
				tcase.k,
				tcase.m,
				tcase.r)
		}
	}
}

func TestSetToMap(t *testing.T) {
	tests := []struct {
		k Key
		v interface{}
		m map[Key]interface{}
		r map[Key]interface{}
	}{{
		k: New("a"),
		v: "c",
		m: map[Key]interface{}{New("a"): "b"},
		r: map[Key]interface{}{New("a"): "c"},
	}, {
		k: New("b"),
		v: uint64(56),
		m: map[Key]interface{}{New("a"): "b"},
		r: map[Key]interface{}{
			New("a"): "b",
			New("b"): uint64(56),
		},
	}, {
		k: New("a"),
		v: "foo",
		m: map[Key]interface{}{},
		r: map[Key]interface{}{New("a"): "foo"},
	}, {
		k: New(uint32(35)),
		v: "d",
		m: map[Key]interface{}{New(uint32(35)): "c"},
		r: map[Key]interface{}{New(uint32(35)): "d"},
	}, {
		k: New(uint32(36)),
		v: true,
		m: map[Key]interface{}{New(uint32(35)): "c"},
		r: map[Key]interface{}{
			New(uint32(35)): "c",
			New(uint32(36)): true,
		},
	}, {
		k: New(uint32(37)),
		v: false,
		m: map[Key]interface{}{New(uint32(36)): "c"},
		r: map[Key]interface{}{
			New(uint32(36)): "c",
			New(uint32(37)): false,
		},
	}, {
		k: New(uint32(37)),
		v: "foobar",
		m: map[Key]interface{}{},
		r: map[Key]interface{}{New(uint32(37)): "foobar"},
	}, {
		k: New(map[string]interface{}{"a": "b", "c": uint64(4)}),
		v: "foobar",
		m: map[Key]interface{}{
			New(map[string]interface{}{"a": "b", "c": uint64(4)}): "foo",
		},
		r: map[Key]interface{}{
			New(map[string]interface{}{"a": "b", "c": uint64(4)}): "foobar",
		},
	}, {
		k: New(map[string]interface{}{"a": "b", "c": uint64(7)}),
		v: "foobar",
		m: map[Key]interface{}{
			New(map[string]interface{}{"a": "b", "c": uint64(4)}): "foo",
		},
		r: map[Key]interface{}{
			New(map[string]interface{}{"a": "b", "c": uint64(4)}): "foo",
			New(map[string]interface{}{"a": "b", "c": uint64(7)}): "foobar",
		},
	}, {
		k: New(map[string]interface{}{"a": "b", "d": uint64(6)}),
		v: "barfoo",
		m: map[Key]interface{}{
			New(map[string]interface{}{"a": "b", "c": uint64(4)}): "foo",
		},
		r: map[Key]interface{}{
			New(map[string]interface{}{"a": "b", "c": uint64(4)}): "foo",
			New(map[string]interface{}{"a": "b", "d": uint64(6)}): "barfoo",
		},
	}, {
		k: New(customKey{i: 42}),
		v: "foo",
		m: map[Key]interface{}{},
		r: map[Key]interface{}{New(customKey{i: 42}): "foo"},
	}}

	for i, tcase := range tests {
		tcase.m[tcase.k] = tcase.v
		if !test.DeepEqual(tcase.m, tcase.r) {
			t.Errorf("Wrong result for case %d:\nk: %#v\nm: %#v\nr: %#v",
				i,
				tcase.k,
				tcase.m,
				tcase.r)
		}
	}
}

func TestGoString(t *testing.T) {
	tcases := []struct {
		in  Key
		out string
	}{{
		in:  New(uint8(1)),
		out: "key.New(uint8(1))",
	}, {
		in:  New(uint16(1)),
		out: "key.New(uint16(1))",
	}, {
		in:  New(uint32(1)),
		out: "key.New(uint32(1))",
	}, {
		in:  New(uint64(1)),
		out: "key.New(uint64(1))",
	}, {
		in:  New(int8(1)),
		out: "key.New(int8(1))",
	}, {
		in:  New(int16(1)),
		out: "key.New(int16(1))",
	}, {
		in:  New(int32(1)),
		out: "key.New(int32(1))",
	}, {
		in:  New(int64(1)),
		out: "key.New(int64(1))",
	}, {
		in:  New(float32(1)),
		out: "key.New(float32(1))",
	}, {
		in:  New(float64(1)),
		out: "key.New(float64(1))",
	}, {
		in:  New(map[string]interface{}{"foo": true}),
		out: `key.New(map[string]interface {}{"foo":true})`,
	}}
	for i, tcase := range tcases {
		t.Run(strconv.Itoa(i), func(t *testing.T) {
			if out := fmt.Sprintf("%#v", tcase.in); out != tcase.out {
				t.Errorf("Wanted Go representation %q but got %q", tcase.out, out)
			}
		})
	}
}

func TestMisc(t *testing.T) {
	k := New(map[string]interface{}{"foo": true})
	js, err := json.Marshal(k)
	if err != nil {
		t.Error("JSON encoding failed:", err)
	} else if expected := `{"foo":true}`; string(js) != expected {
		t.Errorf("Wanted JSON %q but got %q", expected, js)
	}

	test.ShouldPanic(t, func() { New(42) })

	k = New(customKey{i: 42})
	if expected, str := "customKey=42", k.String(); expected != str {
		t.Errorf("Wanted string representation %q but got %q", expected, str)
	}
}

func BenchmarkSetToMapWithStringKey(b *testing.B) {
	m := map[Key]interface{}{
		New("a"):   true,
		New("a1"):  true,
		New("a2"):  true,
		New("a3"):  true,
		New("a4"):  true,
		New("a5"):  true,
		New("a6"):  true,
		New("a7"):  true,
		New("a8"):  true,
		New("a9"):  true,
		New("a10"): true,
		New("a11"): true,
		New("a12"): true,
		New("a13"): true,
		New("a14"): true,
		New("a15"): true,
		New("a16"): true,
		New("a17"): true,
		New("a18"): true,
	}
	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		m[New(strconv.Itoa(i))] = true
	}
}

func BenchmarkSetToMapWithUint64Key(b *testing.B) {
	m := map[Key]interface{}{
		New(uint64(1)):  true,
		New(uint64(2)):  true,
		New(uint64(3)):  true,
		New(uint64(4)):  true,
		New(uint64(5)):  true,
		New(uint64(6)):  true,
		New(uint64(7)):  true,
		New(uint64(8)):  true,
		New(uint64(9)):  true,
		New(uint64(10)): true,
		New(uint64(11)): true,
		New(uint64(12)): true,
		New(uint64(13)): true,
		New(uint64(14)): true,
		New(uint64(15)): true,
		New(uint64(16)): true,
		New(uint64(17)): true,
		New(uint64(18)): true,
		New(uint64(19)): true,
	}
	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		m[New(uint64(i))] = true
	}
}

func BenchmarkGetFromMapWithMapKey(b *testing.B) {
	m := map[Key]interface{}{
		New(map[string]interface{}{"a": true}): true,
		New(map[string]interface{}{"b": true}): true,
		New(map[string]interface{}{"c": true}): true,
		New(map[string]interface{}{"d": true}): true,
		New(map[string]interface{}{"e": true}): true,
		New(map[string]interface{}{"f": true}): true,
		New(map[string]interface{}{"g": true}): true,
		New(map[string]interface{}{"h": true}): true,
		New(map[string]interface{}{"i": true}): true,
		New(map[string]interface{}{"j": true}): true,
		New(map[string]interface{}{"k": true}): true,
		New(map[string]interface{}{"l": true}): true,
		New(map[string]interface{}{"m": true}): true,
		New(map[string]interface{}{"n": true}): true,
		New(map[string]interface{}{"o": true}): true,
		New(map[string]interface{}{"p": true}): true,
		New(map[string]interface{}{"q": true}): true,
		New(map[string]interface{}{"r": true}): true,
		New(map[string]interface{}{"s": true}): true,
	}
	b.ReportAllocs()
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		key := New(map[string]interface{}{string('a' + i%19): true})
		_, found := m[key]
		if !found {
			b.Fatalf("WTF: %#v", key)
		}
	}
}

func mkKey(i int) Key {
	return New(map[string]interface{}{
		"foo": map[string]interface{}{
			"aaaa1": uint32(0),
			"aaaa2": uint32(0),
			"aaaa3": uint32(i),
		},
		"bar": map[string]interface{}{
			"nested": uint32(42),
		},
	})
}

func BenchmarkBigMapWithCompositeKeys(b *testing.B) {
	const size = 10000
	m := make(map[Key]interface{}, size)
	for i := 0; i < size; i++ {
		m[mkKey(i)] = true
	}
	k := mkKey(0)
	submap := k.Key().(map[string]interface{})["foo"].(map[string]interface{})
	b.ResetTimer()
	for i := 0; i < b.N; i++ {
		submap["aaaa3"] = uint32(i)
		_, found := m[k]
		if found != (i < size) {
			b.Fatalf("WTF: %#v", k)
		}
	}
}

func BenchmarkBuiltInType(b *testing.B) {
	benches := []struct {
		val interface{}
	}{
		{
			val: "foo",
		},
		{
			val: int8(-12),
		},
		{
			val: int16(123),
		},
		{
			val: int32(123),
		},
		{
			val: int64(123456),
		},
		{
			val: uint8(12),
		},
		{
			val: uint16(123),
		},
		{
			val: uint32(123),
		},
		{
			val: uint64(123456),
		},
		{
			val: float32(123456.12),
		},
		{
			val: float64(123456.12),
		},
		{
			val: true,
		},
	}

	for _, bench := range benches {
		var k Key
		b.Run(fmt.Sprintf("%T", bench.val), func(b *testing.B) {
			b.ReportAllocs()
			for i := 0; i < b.N; i++ {
				// create the key.Key and call some function here
				if k = New(bench.val); k == nil {
					b.Fatalf("expect to get key.Key, but got nil")
				}
				if !k.Equal(New(bench.val)) {
					b.Fatalf("k is not equal to itself: %v", bench.val)
				}
			}
		})
	}
}
