package nodb

import (
	"fmt"
	"sync"

	"gitea.com/lunny/nodb/store"
)

type ibucket interface {
	Get(key []byte) ([]byte, error)

	Put(key []byte, value []byte) error
	Delete(key []byte) error

	NewIterator() *store.Iterator

	NewWriteBatch() store.WriteBatch

	RangeIterator(min []byte, max []byte, rangeType uint8) *store.RangeLimitIterator
	RevRangeIterator(min []byte, max []byte, rangeType uint8) *store.RangeLimitIterator
	RangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *store.RangeLimitIterator
	RevRangeLimitIterator(min []byte, max []byte, rangeType uint8, offset int, count int) *store.RangeLimitIterator
}

type DB struct {
	l *Nodb

	sdb *store.DB

	bucket ibucket

	index uint8

	kvBatch   *batch
	listBatch *batch
	hashBatch *batch
	zsetBatch *batch
	binBatch  *batch
	setBatch  *batch

	status uint8
}

func (l *Nodb) newDB(index uint8) *DB {
	d := new(DB)

	d.l = l

	d.sdb = l.ldb

	d.bucket = d.sdb

	d.status = DBAutoCommit
	d.index = index

	d.kvBatch = d.newBatch()
	d.listBatch = d.newBatch()
	d.hashBatch = d.newBatch()
	d.zsetBatch = d.newBatch()
	d.binBatch = d.newBatch()
	d.setBatch = d.newBatch()

	return d
}

func (db *DB) newBatch() *batch {
	return db.l.newBatch(db.bucket.NewWriteBatch(), &dbBatchLocker{l: &sync.Mutex{}, wrLock: &db.l.wLock}, nil)
}

func (db *DB) Index() int {
	return int(db.index)
}

func (db *DB) IsAutoCommit() bool {
	return db.status == DBAutoCommit
}

func (db *DB) FlushAll() (drop int64, err error) {
	all := [...](func() (int64, error)){
		db.flush,
		db.lFlush,
		db.hFlush,
		db.zFlush,
		db.bFlush,
		db.sFlush}

	for _, flush := range all {
		if n, e := flush(); e != nil {
			err = e
			return
		} else {
			drop += n
		}
	}

	return
}

func (db *DB) newEliminator() *elimination {
	eliminator := newEliminator(db)

	eliminator.regRetireContext(KVType, db.kvBatch, db.delete)
	eliminator.regRetireContext(ListType, db.listBatch, db.lDelete)
	eliminator.regRetireContext(HashType, db.hashBatch, db.hDelete)
	eliminator.regRetireContext(ZSetType, db.zsetBatch, db.zDelete)
	eliminator.regRetireContext(BitType, db.binBatch, db.bDelete)
	eliminator.regRetireContext(SetType, db.setBatch, db.sDelete)

	return eliminator
}

func (db *DB) flushRegion(t *batch, minKey []byte, maxKey []byte) (drop int64, err error) {
	it := db.bucket.RangeIterator(minKey, maxKey, store.RangeROpen)
	for ; it.Valid(); it.Next() {
		t.Delete(it.RawKey())
		drop++
		if drop&1023 == 0 {
			if err = t.Commit(); err != nil {
				return
			}
		}
	}
	it.Close()
	return
}

func (db *DB) flushType(t *batch, dataType byte) (drop int64, err error) {
	var deleteFunc func(t *batch, key []byte) int64
	var metaDataType byte
	switch dataType {
	case KVType:
		deleteFunc = db.delete
		metaDataType = KVType
	case ListType:
		deleteFunc = db.lDelete
		metaDataType = LMetaType
	case HashType:
		deleteFunc = db.hDelete
		metaDataType = HSizeType
	case ZSetType:
		deleteFunc = db.zDelete
		metaDataType = ZSizeType
	case BitType:
		deleteFunc = db.bDelete
		metaDataType = BitMetaType
	case SetType:
		deleteFunc = db.sDelete
		metaDataType = SSizeType
	default:
		return 0, fmt.Errorf("invalid data type: %s", TypeName[dataType])
	}

	var keys [][]byte
	keys, err = db.scan(metaDataType, nil, 1024, false, "")
	for len(keys) != 0 || err != nil {
		for _, key := range keys {
			deleteFunc(t, key)
			db.rmExpire(t, dataType, key)

		}

		if err = t.Commit(); err != nil {
			return
		} else {
			drop += int64(len(keys))
		}
		keys, err = db.scan(metaDataType, nil, 1024, false, "")
	}
	return
}
