package inomap

import (
	"sync"
	"testing"
)

const (
	// bit 63 is used as the spill bit
	spillBit = 1 << 63
)

func TestTranslate(t *testing.T) {
	m := New(0)
	q := QIno{Ino: 1}
	out := m.Translate(q)
	if out != 1 {
		t.Errorf("expected 1, got %d", out)
	}
	q.Ino = maxPassthruIno
	out = m.Translate(q)
	if out < maxPassthruIno {
		t.Errorf("got %d", out)
	}
	out2 := m.Translate(q)
	if out2 != out {
		t.Errorf("unstable mapping: %d %d", out2, out)
	}
}

func TestTranslateStress(t *testing.T) {
	const baseDev = 12345
	m := New(baseDev)

	var wg sync.WaitGroup
	wg.Add(4)
	go func() {
		// Some normal inode numbers on baseDev
		var q QIno
		q.Dev = baseDev
		for i := uint64(1); i <= 10000; i++ {
			q.Ino = i
			out := m.Translate(q)
			if out != i {
				t.Errorf("i=%d out=%d", i, out)
				break
			}
		}
		wg.Done()
	}()
	go func() {
		// Very high (>maxPassthruIno) inode numbers on baseDev
		var q QIno
		q.Dev = baseDev
		for i := uint64(1); i <= 10000; i++ {
			q.Ino = maxPassthruIno + i
			out := m.Translate(q)
			if out < maxPassthruIno {
				t.Errorf("out=%d", out)
				break
			}
		}
		wg.Done()
	}()
	go func() {
		// Device 9999999
		var q QIno
		q.Dev = 9999999
		for i := uint64(1); i <= 10000; i++ {
			q.Ino = i
			out := m.Translate(q)
			if out < maxPassthruIno {
				t.Errorf("out=%d", out)
				break
			}
		}
		wg.Done()
	}()
	go func() {
		// Device 4444444
		var q QIno
		q.Dev = 4444444
		for i := uint64(1); i <= 10000; i++ {
			q.Ino = i
			out := m.Translate(q)
			if out < maxPassthruIno {
				t.Errorf("out=%d", out)
				break
			}
		}
		wg.Done()
	}()
	wg.Wait()
	if len(m.spillMap) != 10000 {
		t.Errorf("len=%d", len(m.spillMap))
	}
	if len(m.namespaceMap) != 3 {
		t.Errorf("len=%d", len(m.namespaceMap))
	}
}

func TestSpill(t *testing.T) {
	m := New(0)
	var q QIno
	q.Ino = maxPassthruIno + 1
	out1 := m.Translate(q)
	if out1&spillBit == 0 {
		t.Error("spill bit not set")
	}
	if out1 != spillSpaceStart {
		t.Errorf("unexpected first spill inode number %d", out1)
	}
	out2 := m.Translate(q)
	if out2&spillBit == 0 {
		t.Error("spill bit not set")
	}
	if out1 != out2 {
		t.Errorf("unstable mapping: %d vs %d", out1, out2)
	}
	q.Ino = maxPassthruIno + 2
	out3 := m.Translate(q)
	if out3 != out1+1 {
		t.Errorf("unexpected 2nd spill inode number %d", out1)
	}
}

// TestUniqueness checks that unique (Dev, Flags, Ino) tuples get unique inode
// numbers
func TestUniqueness(t *testing.T) {
	m := New(0)
	var q QIno
	outMap := make(map[uint64]struct{})
	for q.Dev = 0; q.Dev < 10; q.Dev++ {
		for q.Tag = 0; q.Tag < 10; q.Tag++ {
			// some go into spill
			for q.Ino = maxPassthruIno - 100; q.Ino < maxPassthruIno+100; q.Ino++ {
				out := m.Translate(q)
				_, found := outMap[out]
				if found {
					t.Fatalf("inode number %d already used", out)
				}
				outMap[out] = struct{}{}
			}
		}
	}
	if len(outMap) != 10*10*200 {
		t.Errorf("%d", len(outMap))
	}
}

func BenchmarkTranslateSingleDev(b *testing.B) {
	m := New(0)
	var q QIno
	for n := 0; n < b.N; n++ {
		q.Ino = uint64(n % 1000)
		m.Translate(q)
	}
}

func BenchmarkTranslateManyDevs(b *testing.B) {
	m := New(0)
	var q QIno
	for n := 0; n < b.N; n++ {
		q.Dev = uint64(n % 10)
		q.Ino = uint64(n % 1000)
		m.Translate(q)
	}
}
