// Copyright (c) 2018 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"
	"testing"

	"github.com/aristanetworks/goarista/key"
	"github.com/aristanetworks/goarista/path"
)

func TestPointer(t *testing.T) {
	p := key.NewPointer(path.New("foo"))
	if expected, actual := path.New("foo"), p.Pointer(); !path.Equal(expected, actual) {
		t.Errorf("Expected %#v but got %#v", expected, actual)
	}
	if expected, actual := "{/foo}", fmt.Sprintf("%s", p); actual != expected {
		t.Errorf("Expected %q but got %q", expected, actual)
	}
	if js, err := json.Marshal(p); err != nil {
		t.Errorf("JSON marshaling failed: %s", err)
	} else if expected, actual := `{"_ptr":"/foo"}`, string(js); actual != expected {
		t.Errorf("Expected %q but got %q", expected, actual)
	}
}

type pointer string

func (ptr pointer) Pointer() key.Path {
	return path.FromString(string(ptr))
}

func (ptr pointer) ToBuiltin() interface{} {
	panic("NOT IMPLEMENTED")
}

func (ptr pointer) String() string {
	panic("NOT IMPLEMENTED")
}

func (ptr pointer) MarshalJSON() ([]byte, error) {
	panic("NOT IMPLEMENTED")
}

func TestPointerEqual(t *testing.T) {
	tests := []struct {
		a      key.Pointer
		b      key.Pointer
		result bool
	}{{
		a:      key.NewPointer(nil),
		b:      key.NewPointer(path.New()),
		result: true,
	}, {
		a:      key.NewPointer(path.New()),
		b:      key.NewPointer(nil),
		result: true,
	}, {
		a:      key.NewPointer(path.New("foo")),
		b:      key.NewPointer(path.New("foo")),
		result: true,
	}, {
		a:      key.NewPointer(path.New("foo")),
		b:      key.NewPointer(path.New("bar")),
		result: false,
	}, {
		a:      key.NewPointer(path.New(true)),
		b:      key.NewPointer(path.New("true")),
		result: false,
	}, {
		a:      key.NewPointer(path.New(int8(0))),
		b:      key.NewPointer(path.New(int16(0))),
		result: false,
	}, {
		a:      key.NewPointer(path.New(path.Wildcard, "bar")),
		b:      key.NewPointer(path.New("foo", path.Wildcard)),
		result: false,
	}, {
		a:      key.NewPointer(path.New(map[string]interface{}{"a": "x", "b": "y"})),
		b:      key.NewPointer(path.New(map[string]interface{}{"b": "y", "a": "x"})),
		result: true,
	}, {
		a:      key.NewPointer(path.New(map[string]interface{}{"a": "x", "b": "y"})),
		b:      key.NewPointer(path.New(map[string]interface{}{"x": "x", "y": "y"})),
		result: false,
	}, {
		a:      key.NewPointer(path.New("foo")),
		b:      pointer("/foo"),
		result: true,
	}, {
		a:      pointer("/foo"),
		b:      key.NewPointer(path.New("foo")),
		result: true,
	}, {
		a:      key.NewPointer(path.New("foo")),
		b:      pointer("/foo/bar"),
		result: false,
	}, {
		a:      pointer("/foo/bar"),
		b:      key.NewPointer(path.New("foo")),
		result: false,
	}}

	for i, tcase := range tests {
		if key.New(tcase.a).Equal(key.New(tcase.b)) != tcase.result {
			t.Errorf("Error in pointer comparison for test %d", i)
		}
	}
}

func TestPointerAsKey(t *testing.T) {
	a := key.NewPointer(path.New("foo", path.Wildcard, map[string]interface{}{
		"bar": key.NewMap(
			// Should be able to embed pointer key.
			key.New(key.NewPointer(path.New("baz"))),
			// Should be able to embed pointer value.
			key.NewPointer(path.New("baz"))),
	}))
	m := key.NewMap(key.New(a), "a")
	if s, ok := m.Get(key.New(a)); !ok {
		t.Error("pointer to path not keyed in map")
	} else if s != "a" {
		t.Errorf("pointer to path not mapped to correct value in map: %s", s)
	}

	// Ensure that we preserve custom pointer implementations.
	b := key.New(pointer("/foo/bar"))
	if _, ok := b.Key().(pointer); !ok {
		t.Errorf("pointer implementation not preserved: %T", b.Key())
	}
}

func BenchmarkPointer(b *testing.B) {
	benchmarks := []key.Path{
		path.New(),
		path.New("foo"),
		path.New("foo", "bar"),
		path.New("foo", "bar", "baz"),
		path.New("foo", "bar", "baz", "qux"),
	}
	for i, benchmark := range benchmarks {
		b.Run(fmt.Sprintf("%d", i), func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				key.NewPointer(benchmark)
			}
		})
	}
}

func BenchmarkPointerAsKey(b *testing.B) {
	benchmarks := []key.Pointer{
		key.NewPointer(path.New()),
		key.NewPointer(path.New("foo")),
		key.NewPointer(path.New("foo", "bar")),
		key.NewPointer(path.New("foo", "bar", "baz")),
		key.NewPointer(path.New("foo", "bar", "baz", "qux")),
	}
	for i, benchmark := range benchmarks {
		b.Run(fmt.Sprintf("%d", i), func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				key.New(benchmark)
			}
		})
	}
}

func BenchmarkEmbeddedPointerAsKey(b *testing.B) {
	benchmarks := [][]interface{}{
		[]interface{}{key.NewPointer(path.New())},
		[]interface{}{key.NewPointer(path.New("foo"))},
		[]interface{}{key.NewPointer(path.New("foo", "bar"))},
		[]interface{}{key.NewPointer(path.New("foo", "bar", "baz"))},
		[]interface{}{key.NewPointer(path.New("foo", "bar", "baz", "qux"))},
	}
	for i, benchmark := range benchmarks {
		b.Run(fmt.Sprintf("%d", i), func(b *testing.B) {
			for i := 0; i < b.N; i++ {
				key.New(benchmark)
			}
		})
	}
}
