package rpl

import (
	"io/ioutil"
	"os"
	"path"
	"testing"
	"time"

	"github.com/siddontang/go/log"
)

func TestFileTable(t *testing.T) {
	testFileTable(t, true)
	testFileTable(t, false)
}

func testFileTable(t *testing.T, useMmap bool) {
	log.SetLevel(log.LevelInfo)

	base, err := ioutil.TempDir("", "test_table")
	if err != nil {
		t.Fatal(err)
	}

	os.MkdirAll(base, 0755)

	defer os.RemoveAll(base)

	l := new(Log)
	l.Compression = 0
	l.Data = make([]byte, 4096)

	w := newTableWriter(base, 1, 1024*1024, useMmap)
	defer w.Close()

	for i := 0; i < 10; i++ {
		l.ID = uint64(i + 1)
		l.CreateTime = uint32(time.Now().Unix())

		l.Data[0] = byte(i + 1)

		if err := w.StoreLog(l); err != nil {
			t.Fatal(err)
		}
	}

	if w.first != 1 {
		t.Fatal(w.first)
	} else if w.last != 10 {
		t.Fatal(w.last)
	}

	l.ID = 10
	if err := w.StoreLog(l); err == nil {
		t.Fatal("must err")
	}

	var ll Log

	for i := 0; i < 10; i++ {
		if err := w.GetLog(uint64(i+1), &ll); err != nil {
			t.Fatal(err)
		} else if len(ll.Data) != 4096 {
			t.Fatal(len(ll.Data))
		} else if ll.Data[0] != byte(i+1) {
			t.Fatal(ll.Data[0])
		}
	}

	if err := w.GetLog(12, &ll); err == nil {
		t.Fatal("must nil")
	}

	var r *tableReader

	name := fmtTableDataName(w.base, w.index)

	if r, err = w.Flush(); err != nil {
		t.Fatal(err)
	}

	for i := 10; i < 20; i++ {
		l.ID = uint64(i + 1)
		l.CreateTime = uint32(time.Now().Unix())

		l.Data[0] = byte(i + 1)

		if err := w.StoreLog(l); err != nil {
			t.Fatal(err)
		}
	}

	if w.first != 11 {
		t.Fatal(w.first)
	} else if w.last != 20 {
		t.Fatal(w.last)
	}

	defer r.Close()

	for i := 0; i < 10; i++ {
		if err := r.GetLog(uint64(i+1), &ll); err != nil {
			t.Fatal(err)
		} else if len(ll.Data) != 4096 {
			t.Fatal(len(ll.Data))
		} else if ll.Data[0] != byte(i+1) {
			t.Fatal(ll.Data[0])
		}
	}

	if err := r.GetLog(12, &ll); err == nil {
		t.Fatal("must nil")
	}

	r.Close()

	if r, err = newTableReader(base, 1, useMmap); err != nil {
		t.Fatal(err)
	}
	defer r.Close()

	for i := 0; i < 10; i++ {
		if err := r.GetLog(uint64(i+1), &ll); err != nil {
			t.Fatal(err)
		} else if len(ll.Data) != 4096 {
			t.Fatal(len(ll.Data))
		} else if ll.Data[0] != byte(i+1) {
			t.Fatal(ll.Data[0])
		}
	}

	if err := r.GetLog(12, &ll); err == nil {
		t.Fatal("must nil")
	}

	s := int64(r.data.Size())

	r.Close()

	log.SetLevel(log.LevelFatal)

	testRepair(t, name, 1, s, 11, useMmap)
	testRepair(t, name, 1, s, 20, useMmap)

	if err := os.Truncate(name, s-21); err != nil {
		t.Fatal(err)
	}

	if r, err := w.Flush(); err != nil {
		t.Fatal(err)
	} else {
		r.Close()
	}

	if r, err = newTableReader(base, 2, useMmap); err != nil {
		t.Fatal(err)
	}
	r.Close()
}

func testRepair(t *testing.T, name string, index int64, s int64, cutSize int64, useMmap bool) {
	var r *tableReader
	var err error

	if err := os.Truncate(name, s-cutSize); err != nil {
		t.Fatal(err)
	}

	if r, err = newTableReader(path.Dir(name), index, useMmap); err != nil {
		t.Fatal(err)
	}
	defer r.Close()

	var ll Log
	for i := 0; i < 10; i++ {
		if err := r.GetLog(uint64(i+1), &ll); err != nil {
			t.Fatal(err, i)
		} else if len(ll.Data) != 4096 {
			t.Fatal(len(ll.Data))
		} else if ll.Data[0] != byte(i+1) {
			t.Fatal(ll.Data[0])
		}
	}

	if err := r.GetLog(12, &ll); err == nil {
		t.Fatal("must nil")
	}

	if s != int64(r.data.Size()) {
		t.Fatalf("repair error size %d != %d", s, r.data.Size())
	}

}
