package test

import (
	"fmt"
	"reflect"
	"testing"

	"github.com/blevesearch/bleve/index/store"
)

// tests focused on verifying that readers are isolated from writers

func CommonTestReaderIsolation(t *testing.T, s store.KVStore) {
	// insert a kv pair
	writer, err := s.Writer()
	if err != nil {
		t.Error(err)
	}

	// **************************************************
	// this is a hack only required for BoltDB
	// however its harmless so to keep the tests
	// the same everywhere, we include it here
	//
	// this is a hack to try to pre-emptively overflow
	// boltdb writes *MAY* block a long reader
	// in particular, if the write requires additional
	// allocation, it must acquire the same lock as
	// the reader, thus cannot continue until that
	// reader is closed.
	// in general this is not a problem for bleve
	// (though it may affect performance in some cases)
	// but it is a problem for this test which attempts
	// to easily verify that readers are isolated
	// this hack writes enough initial data such that
	// the subsequent writes do not require additional
	// space
	hackSize := 1000
	batch := writer.NewBatch()
	for i := 0; i < hackSize; i++ {
		k := fmt.Sprintf("x%d", i)
		batch.Set([]byte(k), []byte("filler"))
	}
	err = writer.ExecuteBatch(batch)
	if err != nil {
		t.Fatal(err)
	}
	// **************************************************

	batch = writer.NewBatch()
	batch.Set([]byte("a"), []byte("val-a"))
	err = writer.ExecuteBatch(batch)
	if err != nil {
		t.Fatal(err)
	}
	err = writer.Close()
	if err != nil {
		t.Fatal(err)
	}

	// create an isolated reader
	reader, err := s.Reader()
	if err != nil {
		t.Error(err)
	}
	defer func() {
		err := reader.Close()
		if err != nil {
			t.Fatal(err)
		}
	}()

	// verify that we see the value already inserted
	val, err := reader.Get([]byte("a"))
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(val, []byte("val-a")) {
		t.Errorf("expected val-a, got nil")
	}

	// verify that an iterator sees it
	count := 0
	it := reader.RangeIterator([]byte{0}, []byte{'x'})
	defer func() {
		err := it.Close()
		if err != nil {
			t.Fatal(err)
		}
	}()
	for it.Valid() {
		it.Next()
		count++
	}
	if count != 1 {
		t.Errorf("expected iterator to see 1, saw %d", count)
	}

	// add something after the reader was created
	writer, err = s.Writer()
	if err != nil {
		t.Error(err)
	}
	batch = writer.NewBatch()
	batch.Set([]byte("b"), []byte("val-b"))
	err = writer.ExecuteBatch(batch)
	if err != nil {
		t.Fatal(err)
	}
	err = writer.Close()
	if err != nil {
		t.Fatal(err)
	}

	// ensure that a newer reader sees it
	newReader, err := s.Reader()
	if err != nil {
		t.Error(err)
	}
	defer func() {
		err := newReader.Close()
		if err != nil {
			t.Fatal(err)
		}
	}()
	val, err = newReader.Get([]byte("b"))
	if err != nil {
		t.Error(err)
	}
	if !reflect.DeepEqual(val, []byte("val-b")) {
		t.Errorf("expected val-b, got nil")
	}

	// ensure that the direct iterator sees it
	count = 0
	it2 := newReader.RangeIterator([]byte{0}, []byte{'x'})
	defer func() {
		err := it2.Close()
		if err != nil {
			t.Fatal(err)
		}
	}()
	for it2.Valid() {
		it2.Next()
		count++
	}
	if count != 2 {
		t.Errorf("expected iterator to see 2, saw %d", count)
	}

	// but that the isolated reader does not
	val, err = reader.Get([]byte("b"))
	if err != nil {
		t.Error(err)
	}
	if val != nil {
		t.Errorf("expected nil, got %v", val)
	}

	// and ensure that the iterator on the isolated reader also does not
	count = 0
	it3 := reader.RangeIterator([]byte{0}, []byte{'x'})
	defer func() {
		err := it3.Close()
		if err != nil {
			t.Fatal(err)
		}
	}()
	for it3.Valid() {
		it3.Next()
		count++
	}
	if count != 1 {
		t.Errorf("expected iterator to see 1, saw %d", count)
	}

}
