// Copyright 2017 Attic Labs, Inc. All rights reserved.
// Licensed under the Apache License, version 2.0:
// http://www.apache.org/licenses/LICENSE-2.0

package nbs

import (
	"bytes"
	"io"
	"io/ioutil"
	"os"
	"path/filepath"
	"sort"
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestFSTableCache(t *testing.T) {
	datas := [][]byte{[]byte("hello"), []byte("world"), []byte("goodbye")}
	sort.SliceStable(datas, func(i, j int) bool { return len(datas[i]) < len(datas[j]) })

	t.Run("ExpireLRU", func(t *testing.T) {
		t.Parallel()
		dir := makeTempDir(t)
		defer os.RemoveAll(dir)

		sum := 0
		for _, s := range datas[1:] {
			sum += len(s)
		}

		tc := newFSTableCache(dir, uint64(sum), len(datas))
		for _, d := range datas {
			tc.store(computeAddr(d), bytes.NewReader(d), uint64(len(d)))
		}

		expiredName := computeAddr(datas[0])
		assert.Nil(t, tc.checkout(expiredName))
		_, fserr := os.Stat(filepath.Join(dir, expiredName.String()))
		assert.True(t, os.IsNotExist(fserr))

		for _, d := range datas[1:] {
			name := computeAddr(d)
			r := tc.checkout(name)
			assert.NotNil(t, r)
			assertDataInReaderAt(t, d, r)
			_, fserr := os.Stat(filepath.Join(dir, name.String()))
			assert.False(t, os.IsNotExist(fserr))
		}
	})

	t.Run("Init", func(t *testing.T) {
		t.Run("Success", func(t *testing.T) {
			t.Parallel()
			dir := makeTempDir(t)
			defer os.RemoveAll(dir)
			assert := assert.New(t)

			names := []addr{}
			for i := byte(0); i < 4; i++ {
				name := computeAddr([]byte{i})
				assert.NoError(ioutil.WriteFile(filepath.Join(dir, name.String()), nil, 0666))
				names = append(names, name)
			}
			var ftc *fsTableCache
			assert.NotPanics(func() { ftc = newFSTableCache(dir, 1024, 4) })
			assert.NotNil(ftc)

			for _, name := range names {
				assert.NotNil(ftc.checkout(name))
			}
		})

		t.Run("BadFile", func(t *testing.T) {
			t.Parallel()
			dir := makeTempDir(t)
			defer os.RemoveAll(dir)

			assert.NoError(t, ioutil.WriteFile(filepath.Join(dir, "boo"), nil, 0666))
			assert.Panics(t, func() { newFSTableCache(dir, 1024, 4) })
		})

		t.Run("ClearTempFile", func(t *testing.T) {
			t.Parallel()
			dir := makeTempDir(t)
			defer os.RemoveAll(dir)

			tempFile := filepath.Join(dir, tempTablePrefix+"boo")
			assert.NoError(t, ioutil.WriteFile(tempFile, nil, 0666))
			assert.NotPanics(t, func() { newFSTableCache(dir, 1024, 4) })
			_, fserr := os.Stat(tempFile)
			assert.True(t, os.IsNotExist(fserr))
		})

		t.Run("Dir", func(t *testing.T) {
			t.Parallel()
			dir := makeTempDir(t)
			defer os.RemoveAll(dir)
			assert.NoError(t, os.Mkdir(filepath.Join(dir, "sub"), 0777))
			assert.Panics(t, func() { newFSTableCache(dir, 1024, 4) })
		})
	})
}

func assertDataInReaderAt(t *testing.T, data []byte, r io.ReaderAt) {
	p := make([]byte, len(data))
	n, err := r.ReadAt(p, 0)
	assert.NoError(t, err)
	assert.Equal(t, len(data), n)
	assert.Equal(t, data, p)
}
