// Copyright 2021 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

//go:build go1.13
// +build go1.13

package trie

import (
	"math/rand"
	"testing"
)

func TestMask(t *testing.T) {
	for _, c := range []struct {
		p    prefix
		b    bitpos
		want prefix
	}{
		{
			p:    0b00001000,
			b:    0b00000100,
			want: 0b00001011,
		}, {
			p:    0b01011011,
			b:    0b00000000,
			want: ^prefix(0),
		}, {
			p:    0b01011011,
			b:    0b00000001,
			want: 0b01011010,
		}, {
			p:    0b01011011,
			b:    0b00000010,
			want: 0b01011001,
		}, {
			p:    0b01011011,
			b:    0b00000100,
			want: 0b01011011,
		}, {
			p:    0b01011011,
			b:    0b00001000,
			want: 0b01010111,
		}, {
			p:    0b01011011,
			b:    0b00010000,
			want: 0b01001111,
		}, {
			p:    0b01011011,
			b:    0b00100000,
			want: 0b01011111,
		}, {
			p:    0b01011011,
			b:    0b01000000,
			want: 0b00111111,
		}, {
			p:    0b01011011,
			b:    0b01000000,
			want: 0b00111111,
		}, {
			p:    0b01011011,
			b:    0b10000000,
			want: 0b01111111,
		},
	} {
		if got := mask(c.p, c.b); got != c.want {
			t.Errorf("mask(%#b,%#b) got %#b. want %#b", c.p, c.b, got, c.want)
		}
	}
}

func TestMaskImpotent(t *testing.T) {
	// test mask(mask(p, b), b) == mask(p,b)
	for _, p := range []prefix{
		0b0, 0b1, 0b100, ^prefix(0b0), ^prefix(0b10),
	} {
		for _, b := range []bitpos{
			0, 0b1, 1 << 2, 1 << 63,
		} {
			once := mask(p, b)
			twice := mask(once, b)
			if once != twice {
				t.Errorf("mask(mask(%#b,%#b), %#b) != mask(%#b,%#b) got %#b. want %#b",
					p, b, b, p, b, twice, once)
			}
		}
	}
}

func TestMatchPrefix(t *testing.T) {
	for _, c := range []struct {
		k prefix
		p prefix
		b bitpos
	}{
		{
			k: 0b1000,
			p: 0b1011,
			b: 0b0100,
		}, {
			k: 0b1001,
			p: 0b1011,
			b: 0b0100,
		}, {
			k: 0b1010,
			p: 0b1011,
			b: 0b0100,
		}, {
			k: 0b1011,
			p: 0b1011,
			b: 0b0100,
		}, {
			k: 0b1100,
			p: 0b1011,
			b: 0b0100,
		}, {
			k: 0b1101,
			p: 0b1011,
			b: 0b0100,
		}, {
			k: 0b1110,
			p: 0b1011,
			b: 0b0100,
		}, {
			k: 0b1111,
			p: 0b1011,
			b: 0b0100,
		},
	} {
		if !matchPrefix(c.k, c.p, c.b) {
			t.Errorf("matchPrefix(%#b, %#b,%#b) should be true", c.k, c.p, c.b)
		}
	}
}

func TestNotMatchPrefix(t *testing.T) {
	for _, c := range []struct {
		k prefix
		p prefix
		b bitpos
	}{
		{
			k: 0b0000,
			p: 0b1011,
			b: 0b0100,
		}, {
			k: 0b0010,
			p: 0b1011,
			b: 0b0100,
		},
	} {
		if matchPrefix(c.k, c.p, c.b) {
			t.Errorf("matchPrefix(%#b, %#b,%#b) should be false", c.k, c.p, c.b)
		}
	}
}

func TestBranchingBit(t *testing.T) {
	for _, c := range []struct {
		x    prefix
		y    prefix
		want bitpos
	}{
		{
			x:    0b0000,
			y:    0b1011,
			want: 0b1000,
		}, {
			x:    0b1010,
			y:    0b1011,
			want: 0b0001,
		}, {
			x:    0b1011,
			y:    0b1111,
			want: 0b0100,
		}, {
			x:    0b1011,
			y:    0b1001,
			want: 0b0010,
		},
	} {
		if got := branchingBit(c.x, c.y); got != c.want {
			t.Errorf("branchingBit(%#b, %#b,) is not expected value. got %#b want %#b",
				c.x, c.y, got, c.want)
		}
	}
}

func TestZeroBit(t *testing.T) {
	for _, c := range []struct {
		k prefix
		b bitpos
	}{
		{
			k: 0b1000,
			b: 0b0100,
		}, {
			k: 0b1001,
			b: 0b0100,
		}, {
			k: 0b1010,
			b: 0b0100,
		},
	} {
		if !zeroBit(c.k, c.b) {
			t.Errorf("zeroBit(%#b, %#b) should be true", c.k, c.b)
		}
	}
}
func TestZeroBitFails(t *testing.T) {
	for _, c := range []struct {
		k prefix
		b bitpos
	}{
		{
			k: 0b1000,
			b: 0b1000,
		}, {
			k: 0b1001,
			b: 0b0001,
		}, {
			k: 0b1010,
			b: 0b0010,
		}, {
			k: 0b1011,
			b: 0b0001,
		},
	} {
		if zeroBit(c.k, c.b) {
			t.Errorf("zeroBit(%#b, %#b) should be false", c.k, c.b)
		}
	}
}

func TestOrd(t *testing.T) {
	a := bitpos(0b0010)
	b := bitpos(0b1000)
	if ord(a, b) {
		t.Errorf("ord(%#b, %#b) should be false", a, b)
	}
	if !ord(b, a) {
		t.Errorf("ord(%#b, %#b) should be true", b, a)
	}
	if ord(a, a) {
		t.Errorf("ord(%#b, %#b) should be false", a, a)
	}
	if !ord(a, 0) {
		t.Errorf("ord(%#b, %#b) should be true", a, 0)
	}
}

func TestPrefixesOverlapLemma(t *testing.T) {
	// test
	//   mask(p, fbb) == mask(q, fbb)
	// iff
	//   m > n && matchPrefix(q, p, m) or  (note: big endian encoding)
	//   m < n && matchPrefix(p, q, n) or  (note: big endian encoding)
	//   m ==n && p == q

	// Case 1: mask(p, fbb) == mask(q, fbb) => m > n && matchPrefix(q, p, m)
	m, n := bitpos(1<<2), bitpos(1<<1)
	p, q := mask(0b100, m), mask(0b010, n)
	if !(prefixesOverlap(p, m, q, n) && m > n && matchPrefix(q, p, m)) {
		t.Errorf("prefixesOverlap(%#b, %#b, %#b, %#b) lemma does not hold",
			p, m, q, n)
	}
	// Case 2: mask(p, fbb) == mask(q, fbb) => m < n && matchPrefix(p, q, n)
	m, n = bitpos(1<<2), bitpos(1<<3)
	p, q = mask(0b100, m), mask(0b1000, n)
	if !(prefixesOverlap(p, m, q, n) && m < n && matchPrefix(p, q, n)) {
		t.Errorf("prefixesOverlap(%#b, %#b, %#b, %#b) lemma does not hold",
			p, m, q, n)
	}
	// Case 3: mask(p, fbb) == mask(q, fbb) => m < n && matchPrefix(p, q, n)
	m, n = bitpos(1<<2), bitpos(1<<2)
	p, q = mask(0b100, m), mask(0b001, n)
	if !(prefixesOverlap(p, m, q, n) && m == n && p == q) {
		t.Errorf("prefixesOverlap(%#b, %#b, %#b, %#b) lemma does not hold",
			p, m, q, n)
	}
	// Case 4: mask(p, fbb) != mask(q, fbb)
	m, n = bitpos(1<<1), bitpos(1<<1)
	p, q = mask(0b100, m), mask(0b001, n)
	if prefixesOverlap(p, m, q, n) ||
		(m > n && matchPrefix(q, p, m)) ||
		(m < n && matchPrefix(p, q, n)) ||
		(m == n && p == q) {
		t.Errorf("prefixesOverlap(%#b, %#b, %#b, %#b) lemma does not hold",
			p, m, q, n)
	}

	// Do a few more random cases
	r := rand.New(rand.NewSource(123))
	N := 2000
	for i := 0; i < N; i++ {
		m := bitpos(1 << (r.Uint64() % (64 + 1)))
		n := bitpos(1 << (r.Uint64() % (64 + 1)))

		p := mask(prefix(r.Uint64()), m)
		q := mask(prefix(r.Uint64()), n)

		lhs := prefixesOverlap(p, m, q, n)
		rhs := (m > n && matchPrefix(q, p, m)) ||
			(m < n && matchPrefix(p, q, n)) ||
			(m == n && p == q)

		if lhs != rhs {
			t.Errorf("prefixesOverlap(%#b, %#b, %#b, %#b) != <lemma> got %v. want %v",
				p, m, q, n, lhs, rhs)
		}
	}
}
