package storage

import (
	"path"
	"strconv"
	"strings"

	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/filter"
	"github.com/syndtr/goleveldb/leveldb/iterator"
	"github.com/syndtr/goleveldb/leveldb/opt"
	"github.com/syndtr/goleveldb/leveldb/util"
)

type LevelDB struct {
	db *leveldb.DB
}

func NewLevelDB(path string) (*LevelDB, error) {
	db, err := leveldb.OpenFile(path, &opt.Options{
		Filter: filter.NewBloomFilter(32),
	})
	if err != nil {
		db, err = leveldb.RecoverFile(path, &opt.Options{
			Filter: filter.NewBloomFilter(32),
		})
		if err == nil && db.Close() == nil {
			db, err = leveldb.OpenFile(path, &opt.Options{
				Filter: filter.NewBloomFilter(32),
			})
		}
	}
	return &LevelDB{
		db: db,
	}, err
}

func (ldb *LevelDB) Get(key string, ns ...string) ([]byte, error) {
	namespace := resolveNamespace(ns)
	snapshot, err := ldb.db.GetSnapshot()
	if err != nil {
		return nil, err
	}
	defer snapshot.Release()

	v, err := snapshot.Get([]byte(path.Join(namespace, key)), nil)
	if err == leveldb.ErrNotFound {
		err = ErrNotFound
	} else if err != nil {
		return nil, err
	}

	return v, err
}

func (ldb *LevelDB) Set(key string, value []byte, ns ...string) error {
	namespace := resolveNamespace(ns)
	key = path.Join(namespace, key)
	batch := &leveldb.Batch{}
	if value != nil {
		batch.Put([]byte(key), value)
	} else {
		batch.Delete([]byte(key))
	}
	return ldb.db.Write(batch, nil)
}

func (ldb *LevelDB) Close() error {
	return ldb.db.Close()
}

func (ldb *LevelDB) List(ns []string, limits ...string) ([]string, error) {
	namespace := path.Join(resolveNamespace(ns))
	limits = resolveLimits(limits)
	it, next := ldb.getIterator(namespace, limits)
	defer it.Release()
	keys := ldb.useIterator(it, next, namespace, limits)
	err := it.Error()
	return keys, err
}

func (ldb *LevelDB) getIterator(namespace string, limits []string) (iterator.Iterator, func() bool) {
	limits[0] = path.Join(namespace, limits[0])
	limits[1] = path.Join(namespace, limits[1])
	asc := limits[3] == "true"

	r := util.BytesPrefix([]byte(namespace))
	it := ldb.db.NewIterator(r, nil)
	next := it.Next
	it.First()
	if !asc {
		next = it.Prev
		it.Last()
	}
	return it, next
}

func (ldb *LevelDB) useIterator(it iterator.Iterator, next func() bool, namespace string, limits []string) []string {
	limits = resolveLimits(limits)
	n, _ := strconv.Atoi(limits[2])
	m := 0

	for it.Error() == nil && !ldb.inRange(string(it.Key()), limits[0], limits[1]) && next() {
	}

	keys := []string{}
	for it.Error() == nil && ldb.inRange(string(it.Key()), limits[0], limits[1]) {
		k := string(it.Key())
		k = strings.TrimPrefix(k, namespace+"/")
		keys = append(keys, k)
		m += 1
		if n > 0 && m >= n {
			break
		}
		if !next() {
			break
		}
	}
	return keys
}

func (ldb *LevelDB) inRange(k, start, stop string) bool {
	return k != "" && k >= start && k <= stop
}
