// Copyright (c) 2014, Suryandaru Triandana <syndtr@gmail.com>
// All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package table

import (
	"encoding/binary"
	"fmt"

	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	"github.com/syndtr/goleveldb/leveldb/comparer"
	"github.com/syndtr/goleveldb/leveldb/iterator"
	"github.com/syndtr/goleveldb/leveldb/testutil"
	"github.com/syndtr/goleveldb/leveldb/util"
)

type blockTesting struct {
	tr *Reader
	b  *block
}

func (t *blockTesting) TestNewIterator(slice *util.Range) iterator.Iterator {
	return t.tr.newBlockIter(t.b, nil, slice, false)
}

var _ = testutil.Defer(func() {
	Describe("Block", func() {
		Build := func(kv *testutil.KeyValue, restartInterval int) *blockTesting {
			// Building the block.
			bw := &blockWriter{
				restartInterval: restartInterval,
				scratch:         make([]byte, 30),
			}
			kv.Iterate(func(i int, key, value []byte) {
				Expect(bw.append(key, value)).ShouldNot(HaveOccurred())
			})
			Expect(bw.finish()).ShouldNot(HaveOccurred())

			// Opening the block.
			data := bw.buf.Bytes()
			restartsLen := int(binary.LittleEndian.Uint32(data[len(data)-4:]))
			return &blockTesting{
				tr: &Reader{cmp: comparer.DefaultComparer},
				b: &block{
					data:           data,
					restartsLen:    restartsLen,
					restartsOffset: len(data) - (restartsLen+1)*4,
				},
			}
		}

		Describe("read test", func() {
			for restartInterval := 1; restartInterval <= 5; restartInterval++ {
				Describe(fmt.Sprintf("with restart interval of %d", restartInterval), func() {
					kv := &testutil.KeyValue{}
					Text := func() string {
						return fmt.Sprintf("and %d keys", kv.Len())
					}

					Test := func() {
						// Make block.
						br := Build(kv, restartInterval)
						// Do testing.
						testutil.KeyValueTesting(nil, kv.Clone(), br, nil, nil)
					}

					Describe(Text(), Test)

					kv.PutString("", "empty")
					Describe(Text(), Test)

					kv.PutString("a1", "foo")
					Describe(Text(), Test)

					kv.PutString("a2", "v")
					Describe(Text(), Test)

					kv.PutString("a3qqwrkks", "hello")
					Describe(Text(), Test)

					kv.PutString("a4", "bar")
					Describe(Text(), Test)

					kv.PutString("a5111111", "v5")
					kv.PutString("a6", "")
					kv.PutString("a7", "v7")
					kv.PutString("a8", "vvvvvvvvvvvvvvvvvvvvvv8")
					kv.PutString("b", "v9")
					kv.PutString("c9", "v9")
					kv.PutString("c91", "v9")
					kv.PutString("d0", "v9")
					Describe(Text(), Test)
				})
			}
		})

		Describe("out-of-bound slice test", func() {
			kv := &testutil.KeyValue{}
			kv.PutString("k1", "v1")
			kv.PutString("k2", "v2")
			kv.PutString("k3abcdefgg", "v3")
			kv.PutString("k4", "v4")
			kv.PutString("k5", "v5")
			for restartInterval := 1; restartInterval <= 5; restartInterval++ {
				Describe(fmt.Sprintf("with restart interval of %d", restartInterval), func() {
					// Make block.
					bt := Build(kv, restartInterval)

					Test := func(r *util.Range) func(done Done) {
						return func(done Done) {
							iter := bt.TestNewIterator(r)
							Expect(iter.Error()).ShouldNot(HaveOccurred())

							t := testutil.IteratorTesting{
								KeyValue: kv.Clone(),
								Iter:     iter,
							}

							testutil.DoIteratorTesting(&t)
							iter.Release()
							done <- true
						}
					}

					It("Should do iterations and seeks correctly #0",
						Test(&util.Range{Start: []byte("k0"), Limit: []byte("k6")}), 2.0)

					It("Should do iterations and seeks correctly #1",
						Test(&util.Range{Start: []byte(""), Limit: []byte("zzzzzzz")}), 2.0)
				})
			}
		})
	})
})
