// Copyright (c) 2015-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package treap

import (
	"encoding/binary"
	"encoding/hex"
	"math/rand"
	"reflect"
	"testing"
)

// fromHex converts the passed hex string into a byte slice and will panic if
// there is an error.  This is only provided for the hard-coded constants so
// errors in the source code can be detected. It will only (and must only) be
// called for initialization purposes.
func fromHex(s string) []byte {
	r, err := hex.DecodeString(s)
	if err != nil {
		panic("invalid hex in source file: " + s)
	}
	return r
}

// serializeUint32 returns the big-endian encoding of the passed uint32.
func serializeUint32(ui uint32) []byte {
	var ret [4]byte
	binary.BigEndian.PutUint32(ret[:], ui)
	return ret[:]
}

// TestParentStack ensures the treapParentStack functionality works as intended.
func TestParentStack(t *testing.T) {
	t.Parallel()

	tests := []struct {
		numNodes int
	}{
		{numNodes: 1},
		{numNodes: staticDepth},
		{numNodes: staticDepth + 1}, // Test dynamic code paths
	}

testLoop:
	for i, test := range tests {
		nodes := make([]*treapNode, 0, test.numNodes)
		for j := 0; j < test.numNodes; j++ {
			var key [4]byte
			binary.BigEndian.PutUint32(key[:], uint32(j))
			node := newTreapNode(key[:], key[:], 0)
			nodes = append(nodes, node)
		}

		// Push all of the nodes onto the parent stack while testing
		// various stack properties.
		stack := &parentStack{}
		for j, node := range nodes {
			stack.Push(node)

			// Ensure the stack length is the expected value.
			if stack.Len() != j+1 {
				t.Errorf("Len #%d (%d): unexpected stack "+
					"length - got %d, want %d", i, j,
					stack.Len(), j+1)
				continue testLoop
			}

			// Ensure the node at each index is the expected one.
			for k := 0; k <= j; k++ {
				atNode := stack.At(j - k)
				if !reflect.DeepEqual(atNode, nodes[k]) {
					t.Errorf("At #%d (%d): mismatched node "+
						"- got %v, want %v", i, j-k,
						atNode, nodes[k])
					continue testLoop
				}
			}
		}

		// Ensure each popped node is the expected one.
		for j := 0; j < len(nodes); j++ {
			node := stack.Pop()
			expected := nodes[len(nodes)-j-1]
			if !reflect.DeepEqual(node, expected) {
				t.Errorf("At #%d (%d): mismatched node - "+
					"got %v, want %v", i, j, node, expected)
				continue testLoop
			}
		}

		// Ensure the stack is now empty.
		if stack.Len() != 0 {
			t.Errorf("Len #%d: stack is not empty - got %d", i,
				stack.Len())
			continue testLoop
		}

		// Ensure attempting to retrieve a node at an index beyond the
		// stack's length returns nil.
		if node := stack.At(2); node != nil {
			t.Errorf("At #%d: did not give back nil - got %v", i,
				node)
			continue testLoop
		}

		// Ensure attempting to pop a node from an empty stack returns
		// nil.
		if node := stack.Pop(); node != nil {
			t.Errorf("Pop #%d: did not give back nil - got %v", i,
				node)
			continue testLoop
		}
	}
}

func init() {
	// Force the same pseudo random numbers for each test run.
	rand.Seed(0)
}