// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package v2store

import (
	"reflect"
	"testing"
	"time"
	"unsafe"

	"go.etcd.io/etcd/pkg/testutil"
)

func TestNodeExternClone(t *testing.T) {
	var eNode *NodeExtern
	if g := eNode.Clone(); g != nil {
		t.Fatalf("nil.Clone=%v, want nil", g)
	}

	const (
		key string = "/foo/bar"
		ttl int64  = 123456789
		ci  uint64 = 123
		mi  uint64 = 321
	)
	var (
		val    = "some_data"
		valp   = &val
		exp    = time.Unix(12345, 67890)
		expp   = &exp
		child  = NodeExtern{}
		childp = &child
		childs = []*NodeExtern{childp}
	)

	eNode = &NodeExtern{
		Key:           key,
		TTL:           ttl,
		CreatedIndex:  ci,
		ModifiedIndex: mi,
		Value:         valp,
		Expiration:    expp,
		Nodes:         childs,
	}

	gNode := eNode.Clone()
	// Check the clone is as expected
	testutil.AssertEqual(t, gNode.Key, key)
	testutil.AssertEqual(t, gNode.TTL, ttl)
	testutil.AssertEqual(t, gNode.CreatedIndex, ci)
	testutil.AssertEqual(t, gNode.ModifiedIndex, mi)
	// values should be the same
	testutil.AssertEqual(t, *gNode.Value, val)
	testutil.AssertEqual(t, *gNode.Expiration, exp)
	testutil.AssertEqual(t, len(gNode.Nodes), len(childs))
	testutil.AssertEqual(t, *gNode.Nodes[0], child)
	// but pointers should differ
	if gNode.Value == eNode.Value {
		t.Fatalf("expected value pointers to differ, but got same!")
	}
	if gNode.Expiration == eNode.Expiration {
		t.Fatalf("expected expiration pointers to differ, but got same!")
	}
	if sameSlice(gNode.Nodes, eNode.Nodes) {
		t.Fatalf("expected nodes pointers to differ, but got same!")
	}
	// Original should be the same
	testutil.AssertEqual(t, eNode.Key, key)
	testutil.AssertEqual(t, eNode.TTL, ttl)
	testutil.AssertEqual(t, eNode.CreatedIndex, ci)
	testutil.AssertEqual(t, eNode.ModifiedIndex, mi)
	testutil.AssertEqual(t, eNode.Value, valp)
	testutil.AssertEqual(t, eNode.Expiration, expp)
	if !sameSlice(eNode.Nodes, childs) {
		t.Fatalf("expected nodes pointer to same, but got different!")
	}
	// Change the clone and ensure the original is not affected
	gNode.Key = "/baz"
	gNode.TTL = 0
	gNode.Nodes[0].Key = "uno"
	testutil.AssertEqual(t, eNode.Key, key)
	testutil.AssertEqual(t, eNode.TTL, ttl)
	testutil.AssertEqual(t, eNode.CreatedIndex, ci)
	testutil.AssertEqual(t, eNode.ModifiedIndex, mi)
	testutil.AssertEqual(t, *eNode.Nodes[0], child)
	// Change the original and ensure the clone is not affected
	eNode.Key = "/wuf"
	testutil.AssertEqual(t, eNode.Key, "/wuf")
	testutil.AssertEqual(t, gNode.Key, "/baz")
}

func sameSlice(a, b []*NodeExtern) bool {
	ah := (*reflect.SliceHeader)(unsafe.Pointer(&a))
	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	return *ah == *bh
}
