package leveldb

import (
	"fmt"
	"path"

	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/errors"
	"github.com/syndtr/goleveldb/leveldb/iterator"
	"github.com/syndtr/goleveldb/leveldb/opt"
	"github.com/syndtr/goleveldb/leveldb/util"

	. "github.com/tendermint/tmlibs/common"
)

func init() {
	dbCreator := func(name string, dir string) (DB, error) {
		return NewGoLevelDB(name, dir)
	}
	registerDBCreator(LevelDBBackendStr, dbCreator, false)
	registerDBCreator(GoLevelDBBackendStr, dbCreator, false)
}

type GoLevelDB struct {
	db *leveldb.DB
}

func NewGoLevelDB(name string, dir string) (*GoLevelDB, error) {
	dbPath := path.Join(dir, name+".db")
	db, err := leveldb.OpenFile(dbPath, nil)
	if err != nil {
		return nil, err
	}
	database := &GoLevelDB{db: db}
	return database, nil
}

func (db *GoLevelDB) Get(key []byte) []byte {
	res, err := db.db.Get(key, nil)
	if err != nil {
		if err == errors.ErrNotFound {
			return nil
		} else {
			PanicCrisis(err)
		}
	}
	return res
}

func (db *GoLevelDB) Set(key []byte, value []byte) {
	err := db.db.Put(key, value, nil)
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) SetSync(key []byte, value []byte) {
	err := db.db.Put(key, value, &opt.WriteOptions{Sync: true})
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) Delete(key []byte) {
	err := db.db.Delete(key, nil)
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) DeleteSync(key []byte) {
	err := db.db.Delete(key, &opt.WriteOptions{Sync: true})
	if err != nil {
		PanicCrisis(err)
	}
}

func (db *GoLevelDB) DB() *leveldb.DB {
	return db.db
}

func (db *GoLevelDB) Close() {
	db.db.Close()
}

func (db *GoLevelDB) Print() {
	str, _ := db.db.GetProperty("leveldb.stats")
	fmt.Printf("%v\n", str)

	iter := db.db.NewIterator(nil, nil)
	for iter.Next() {
		key := iter.Key()
		value := iter.Value()
		fmt.Printf("[%X]:\t[%X]\n", key, value)
	}
}

func (db *GoLevelDB) Stats() map[string]string {
	keys := []string{
		"leveldb.num-files-at-level{n}",
		"leveldb.stats",
		"leveldb.sstables",
		"leveldb.blockpool",
		"leveldb.cachedblock",
		"leveldb.openedtables",
		"leveldb.alivesnaps",
		"leveldb.aliveiters",
	}

	stats := make(map[string]string)
	for _, key := range keys {
		str, err := db.db.GetProperty(key)
		if err == nil {
			stats[key] = str
		}
	}
	return stats
}

type goLevelDBIterator struct {
	source    iterator.Iterator
	start     []byte
	isReverse bool
}

func newGoLevelDBIterator(source iterator.Iterator, start []byte, isReverse bool) *goLevelDBIterator {
	if start != nil {
		valid := source.Seek(start)
		if !valid && isReverse {
			source.Last()
			source.Next()
		}
	} else if isReverse {
		source.Last()
		source.Next()
	}

	return &goLevelDBIterator{
		source:    source,
		start:     start,
		isReverse: isReverse,
	}
}

// Key returns a copy of the current key.
func (it *goLevelDBIterator) Key() []byte {
	key := it.source.Key()
	k := make([]byte, len(key))
	copy(k, key)

	return k
}

// Value returns a copy of the current value.
func (it *goLevelDBIterator) Value() []byte {
	val := it.source.Value()
	v := make([]byte, len(val))
	copy(v, val)

	return v
}

func (it *goLevelDBIterator) Seek(point []byte) bool {
	return it.source.Seek(point)
}

func (it *goLevelDBIterator) Error() error {
	return it.source.Error()
}

func (it *goLevelDBIterator) Next() bool {
	it.assertNoError()
	if it.isReverse {
		return it.source.Prev()
	}
	return it.source.Next()
}

func (it *goLevelDBIterator) Release() {
	it.source.Release()
}

func (it *goLevelDBIterator) assertNoError() {
	if err := it.source.Error(); err != nil {
		panic(err)
	}
}

func (db *GoLevelDB) Iterator() Iterator {
	return &goLevelDBIterator{source: db.db.NewIterator(nil, nil)}
}

func (db *GoLevelDB) IteratorPrefix(prefix []byte) Iterator {
	return &goLevelDBIterator{source: db.db.NewIterator(util.BytesPrefix(prefix), nil)}
}

func (db *GoLevelDB) IteratorPrefixWithStart(Prefix, start []byte, isReverse bool) Iterator {
	itr := db.db.NewIterator(util.BytesPrefix(Prefix), nil)
	return newGoLevelDBIterator(itr, start, isReverse)
}

func (db *GoLevelDB) NewBatch() Batch {
	batch := new(leveldb.Batch)
	return &goLevelDBBatch{db, batch}
}

//--------------------------------------------------------------------------------

type goLevelDBBatch struct {
	db    *GoLevelDB
	batch *leveldb.Batch
}

func (mBatch *goLevelDBBatch) Set(key, value []byte) {
	mBatch.batch.Put(key, value)
}

func (mBatch *goLevelDBBatch) Delete(key []byte) {
	mBatch.batch.Delete(key)
}

func (mBatch *goLevelDBBatch) Write() {
	err := mBatch.db.db.Write(mBatch.batch, nil)
	if err != nil {
		PanicCrisis(err)
	}
}
