package gtreap

import (
	"bytes"
	"testing"
)

func stringCompare(a, b interface{}) int {
	return bytes.Compare([]byte(a.(string)), []byte(b.(string)))
}

func TestTreap(t *testing.T) {
	x := NewTreap(stringCompare)
	if x == nil {
		t.Errorf("expected NewTreap to work")
	}

	tests := []struct {
		op  string
		val string
		pri int
		exp string
	}{
		{"get", "not-there", -1, "NIL"},
		{"ups", "a", 100, ""},
		{"get", "a", -1, "a"},
		{"ups", "b", 200, ""},
		{"get", "a", -1, "a"},
		{"get", "b", -1, "b"},
		{"ups", "c", 300, ""},
		{"get", "a", -1, "a"},
		{"get", "b", -1, "b"},
		{"get", "c", -1, "c"},
		{"get", "not-there", -1, "NIL"},
		{"ups", "a", 400, ""},
		{"get", "a", -1, "a"},
		{"get", "b", -1, "b"},
		{"get", "c", -1, "c"},
		{"get", "not-there", -1, "NIL"},
		{"del", "a", -1, ""},
		{"get", "a", -1, "NIL"},
		{"get", "b", -1, "b"},
		{"get", "c", -1, "c"},
		{"get", "not-there", -1, "NIL"},
		{"ups", "a", 10, ""},
		{"get", "a", -1, "a"},
		{"get", "b", -1, "b"},
		{"get", "c", -1, "c"},
		{"get", "not-there", -1, "NIL"},
		{"del", "a", -1, ""},
		{"del", "b", -1, ""},
		{"del", "c", -1, ""},
		{"get", "a", -1, "NIL"},
		{"get", "b", -1, "NIL"},
		{"get", "c", -1, "NIL"},
		{"get", "not-there", -1, "NIL"},
		{"del", "a", -1, ""},
		{"del", "b", -1, ""},
		{"del", "c", -1, ""},
		{"get", "a", -1, "NIL"},
		{"get", "b", -1, "NIL"},
		{"get", "c", -1, "NIL"},
		{"get", "not-there", -1, "NIL"},
		{"ups", "a", 10, ""},
		{"get", "a", -1, "a"},
		{"get", "b", -1, "NIL"},
		{"get", "c", -1, "NIL"},
		{"get", "not-there", -1, "NIL"},
		{"ups", "b", 1000, "b"},
		{"del", "b", -1, ""}, // cover join that is nil
		{"ups", "b", 20, "b"},
		{"ups", "c", 12, "c"},
		{"del", "b", -1, ""}, // cover join second return
		{"ups", "a", 5, "a"}, // cover upsert existing with lower priority
	}

	for testIdx, test := range tests {
		switch test.op {
		case "get":
			i := x.Get(test.val)
			if i != test.exp && !(i == nil && test.exp == "NIL") {
				t.Errorf("test: %v, on Get, expected: %v, got: %v", testIdx, test.exp, i)
			}
		case "ups":
			x = x.Upsert(test.val, test.pri)
		case "del":
			x = x.Delete(test.val)
		}
	}
}

func load(x *Treap, arr []string) *Treap {
	for i, s := range arr {
		x = x.Upsert(s, i)
	}
	return x
}

func visitExpect(t *testing.T, x *Treap, start string, arr []string) {
	n := 0
	x.VisitAscend(start, func(i Item) bool {
		if i.(string) != arr[n] {
			t.Errorf("expected visit item: %v, saw: %v", arr[n], i)
		}
		n++
		return true
	})
	if n != len(arr) {
		t.Errorf("expected # visit callbacks: %v, saw: %v", len(arr), n)
	}
}

func TestVisit(t *testing.T) {
	x := NewTreap(stringCompare)
	visitExpect(t, x, "a", []string{})

	x = load(x, []string{"e", "d", "c", "c", "a", "b", "a"})

	visitX := func() {
		visitExpect(t, x, "a", []string{"a", "b", "c", "d", "e"})
		visitExpect(t, x, "a1", []string{"b", "c", "d", "e"})
		visitExpect(t, x, "b", []string{"b", "c", "d", "e"})
		visitExpect(t, x, "b1", []string{"c", "d", "e"})
		visitExpect(t, x, "c", []string{"c", "d", "e"})
		visitExpect(t, x, "c1", []string{"d", "e"})
		visitExpect(t, x, "d", []string{"d", "e"})
		visitExpect(t, x, "d1", []string{"e"})
		visitExpect(t, x, "e", []string{"e"})
		visitExpect(t, x, "f", []string{})
	}
	visitX()

	var y *Treap
	y = x.Upsert("f", 1)
	y = y.Delete("a")
	y = y.Upsert("cc", 2)
	y = y.Delete("c")

	visitExpect(t, y, "a", []string{"b", "cc", "d", "e", "f"})
	visitExpect(t, y, "a1", []string{"b", "cc", "d", "e", "f"})
	visitExpect(t, y, "b", []string{"b", "cc", "d", "e", "f"})
	visitExpect(t, y, "b1", []string{"cc", "d", "e", "f"})
	visitExpect(t, y, "c", []string{"cc", "d", "e", "f"})
	visitExpect(t, y, "c1", []string{"cc", "d", "e", "f"})
	visitExpect(t, y, "d", []string{"d", "e", "f"})
	visitExpect(t, y, "d1", []string{"e", "f"})
	visitExpect(t, y, "e", []string{"e", "f"})
	visitExpect(t, y, "f", []string{"f"})
	visitExpect(t, y, "z", []string{})

	// an uninitialized treap
	z := NewTreap(stringCompare)

	// a treap to force left traversal of min
	lmt := NewTreap(stringCompare)
	lmt = lmt.Upsert("b", 2)
	lmt = lmt.Upsert("a", 1)

	// The x treap should be unchanged.
	visitX()

	if x.Min() != "a" {
		t.Errorf("expected min of a")
	}
	if x.Max() != "e" {
		t.Errorf("expected max of d")
	}
	if y.Min() != "b" {
		t.Errorf("expected min of b")
	}
	if y.Max() != "f" {
		t.Errorf("expected max of f")
	}
	if z.Min() != nil {
		t.Errorf("expected min of nil")
	}
	if z.Max() != nil {
		t.Error("expected max of nil")
	}
	if lmt.Min() != "a" {
		t.Errorf("expected min of a")
	}
	if lmt.Max() != "b" {
		t.Errorf("expeced max of b")
	}
}

func visitExpectEndAtC(t *testing.T, x *Treap, start string, arr []string) {
	n := 0
	x.VisitAscend(start, func(i Item) bool {
		if stringCompare(i, "c") > 0 {
			return false
		}
		if i.(string) != arr[n] {
			t.Errorf("expected visit item: %v, saw: %v", arr[n], i)
		}
		n++
		return true
	})
	if n != len(arr) {
		t.Errorf("expected # visit callbacks: %v, saw: %v", len(arr), n)
	}
}

func TestVisitEndEarly(t *testing.T) {
	x := NewTreap(stringCompare)
	visitExpectEndAtC(t, x, "a", []string{})

	x = load(x, []string{"e", "d", "c", "c", "a", "b", "a", "e"})

	visitX := func() {
		visitExpectEndAtC(t, x, "a", []string{"a", "b", "c"})
		visitExpectEndAtC(t, x, "a1", []string{"b", "c"})
		visitExpectEndAtC(t, x, "b", []string{"b", "c"})
		visitExpectEndAtC(t, x, "b1", []string{"c"})
		visitExpectEndAtC(t, x, "c", []string{"c"})
		visitExpectEndAtC(t, x, "c1", []string{})
		visitExpectEndAtC(t, x, "d", []string{})
		visitExpectEndAtC(t, x, "d1", []string{})
		visitExpectEndAtC(t, x, "e", []string{})
		visitExpectEndAtC(t, x, "f", []string{})
	}
	visitX()
}

func TestPriorityAfterUpsert(t *testing.T) {
	// See https://github.com/steveyen/gtreap/issues/3 found by icexin.

	var check func(n *node, level int, expectedPriority map[string]int)
	check = func(n *node, level int, expectedPriority map[string]int) {
		if n == nil {
			return
		}
		if n.priority != expectedPriority[n.item.(string)] {
			t.Errorf("wrong priority")
		}
		check(n.left, level+1, expectedPriority)
		check(n.right, level+1, expectedPriority)
	}

	s := NewTreap(stringCompare)
	s = s.Upsert("m", 20)
	s = s.Upsert("l", 18)
	s = s.Upsert("n", 19)

	check(s.root, 0, map[string]int{
		"m": 20,
		"l": 18,
		"n": 19,
	})

	s = s.Upsert("m", 4)

	check(s.root, 0, map[string]int{
		"m": 20,
		"l": 18,
		"n": 19,
	})
}