func (sk *SpaceKeeper) getIndexedWorkSpaces() map[int][]*WorkSpace {
	queueMap := make(map[int]*plotterQueue)
	for _, ws := range sk.workSpaceIndex[allState].Items() {
		bl := ws.id.bitLength
		qws := newQueuedWorkSpace(ws, false)
		if queue, exists := queueMap[bl]; exists {
			queue.Push(qws, qws.priority())
		} else {
			queueMap[bl] = newPlotterQueue()
			queueMap[bl].Push(qws, qws.priority())
		}
	}
	resultMap := make(map[int][]*WorkSpace)
	for bl, queue := range queueMap {
		resultMap[bl] = make([]*WorkSpace, queue.Size())
		for i := range resultMap[bl] {
			resultMap[bl][i] = queue.PopItem().ws
		}
	}
	return resultMap
}
getIndexedWorkSpaces get all indexed workSpace grouped by bitLength
slice of workSpace is sorted by same priority as in queuedWorkSpace
package capacity
import (
	"context"
	"os"
	"path/filepath"
	"sync"
	"sync/atomic"
	"github.com/massnetorg/mass-core/logging"
	"github.com/massnetorg/mass-core/massutil/service"
	"github.com/massnetorg/mass-core/poc"
	"github.com/massnetorg/mass-core/poc/pocutil"
	"github.com/massnetorg/mass-core/pocec"
	"github.com/panjf2000/ants/v2"
	"github.com/shirou/gopsutil/disk"
	"massnet.org/mass/poc/engine"
)
const (
	plotterMaxChanSize = 1024
	maxPoolWorker      = 32
	allState           = engine.LastState + 1  
)
type PoCWallet interface {
	GenerateNewPublicKey() (*pocec.PublicKey, uint32, error)
	GetPublicKeyOrdinal(*pocec.PublicKey) (uint32, bool)
	SignMessage(pubKey *pocec.PublicKey, hash []byte) (*pocec.Signature, error)
	Unlock(password []byte) error
	Lock()
	IsLocked() bool
}
type SpaceKeeper struct {
	*service.BaseService
	stateLock             sync.RWMutex
	wg                    sync.WaitGroup
	quit                  chan struct{}
	configuring           int32  
	configured            int32  
	allowGenerateNewSpace bool
	dbDirs                []string
	dbType                string
	wallet                PoCWallet
	workSpaceIndex        []*WorkSpaceMap
	workSpacePaths        map[string]*WorkSpacePath
	workSpaceList         []*WorkSpace
	queue                 *plotterQueue
	newQueuedWorkSpaceCh  chan *queuedWorkSpace
	workerPool            *ants.Pool
	generateInitialIndex  func() error
	fileWatcher           func()
}
func (sk *SpaceKeeper) OnStart() error {
	if sk.wallet.IsLocked() {
		logging.CPrint(logging.ERROR, "can not start spaceKeeper with locked poc wallet", logging.LogFormat{"err": ErrWalletIsLocked})
		return ErrWalletIsLocked
	}
	sk.quit = make(chan struct{})
	go sk.spacePlotter()
	go sk.fileWatcher()
	logging.CPrint(logging.INFO, "spaceKeeper started")
	return nil
}
func (sk *SpaceKeeper) OnStop() error {
	close(sk.quit)
	sk.wg.Wait()
	logging.CPrint(logging.INFO, "spaceKeeper stopped")
	return nil
}
func (sk *SpaceKeeper) Type() string {
	return sk.Name()
}
func (sk *SpaceKeeper) WorkSpaceIDs(flags engine.WorkSpaceStateFlags) ([]string, error) {
	sk.stateLock.RLock()
	defer sk.stateLock.RUnlock()
	if flags.Contains(engine.SFAll) {
		idList := sk.workSpaceList
		result := make([]string, len(idList))
		for i := range result {
			result[i] = idList[i].id.String()
		}
		return result, nil
	}
	items := getWsByFlags(sk.workSpaceList, flags)
	result := make([]string, 0, len(items))
	for _, ws := range items {
		result = append(result, ws.id.String())
	}
	return result, nil
}
func (sk *SpaceKeeper) WorkSpaceInfos(flags engine.WorkSpaceStateFlags) ([]engine.WorkSpaceInfo, error) {
	sk.stateLock.RLock()
	defer sk.stateLock.RUnlock()
	if flags.Contains(engine.SFAll) {
		wsList := sk.workSpaceList
		result := make([]engine.WorkSpaceInfo, len(wsList))
		for i := range result {
			result[i] = wsList[i].Info()
		}
		return result, nil
	}
	items := getWsByFlags(sk.workSpaceList, flags)
	result := make([]engine.WorkSpaceInfo, 0, len(items))
	for _, ws := range items {
		result = append(result, ws.Info())
	}
	return result, nil
}
func (sk *SpaceKeeper) GetProof(ctx context.Context, sid string, challenge pocutil.Hash, filter bool) (*engine.WorkSpaceProof, error) {
	if !sk.Started() {
		return nil, ErrSpaceKeeperIsNotRunning
	}
	if ws, ok := sk.workSpaceIndex[allState].Items()[sid]; ok && ws.using {
		return sk.getProof(ws, challenge, filter), nil
	}
	return nil, ErrWorkSpaceDoesNotExist
}
func (sk *SpaceKeeper) GetProofs(ctx context.Context, flags engine.WorkSpaceStateFlags, challenge pocutil.Hash, filter bool) ([]*engine.WorkSpaceProof, error) {
	if !sk.Started() {
		return nil, ErrSpaceKeeperIsNotRunning
	}
	items := make(map[string]*WorkSpace)
	for _, ws := range getWsByFlags(sk.workSpaceList, flags) {
		items[ws.id.String()] = ws
	}
	proofs := sk.getProofs(items, challenge, filter)
	result := make([]*engine.WorkSpaceProof, 0, len(proofs))
	for _, proof := range proofs {
		result = append(result, proof)
	}
	return result, nil
}
func (sk *SpaceKeeper) GetProofReader(ctx context.Context, sid string, challenge pocutil.Hash, filter bool) (engine.ProofReader, error) {
	if !sk.Started() {
		return nil, ErrSpaceKeeperIsNotRunning
	}
	if ws, ok := sk.workSpaceIndex[allState].Items()[sid]; ok && ws.using {
		prw := engine.NewProofRW(ctx, 1)
		go func() {
			if err := prw.Write(sk.getProof(ws, challenge, filter)); err != nil {
				logging.CPrint(logging.WARN, "fail to write WorkSpaceProof to ProofRW", logging.LogFormat{"err": err, "sid": sid, "challenge": challenge})
			}
			prw.Close()
		}()
		return prw, nil
	}
	return nil, ErrWorkSpaceDoesNotExist
}
func (sk *SpaceKeeper) GetProofsReader(ctx context.Context, flags engine.WorkSpaceStateFlags, challenge pocutil.Hash, filter bool) (engine.ProofReader, error) {
	if !sk.Started() {
		return nil, ErrSpaceKeeperIsNotRunning
	}
	items := make(map[string]*WorkSpace)
	for _, ws := range getWsByFlags(sk.workSpaceList, flags) {
		items[ws.id.String()] = ws
	}
	prw := engine.NewProofRW(ctx, len(items))
	go func() {
		proofs := sk.getProofs(items, challenge, filter)
		var err error
		for i, proof := range proofs {
			if err = prw.Write(proof); err != nil {
				logging.CPrint(logging.WARN, "fail to write WorkSpaceProofs to ProofRW", logging.LogFormat{
					"err":       err,
					"index":     i,
					"count":     len(proofs),
					"flags":     flags,
					"challenge": challenge,
				})
				break
			}
		}
		prw.Close()
	}()
	return prw, nil
}
func (sk *SpaceKeeper) ActOnWorkSpace(sid string, action engine.ActionType) (err error) {
	if !action.IsValid() {
		return engine.ErrInvalidAction
	}
	switch action {
	case engine.Plot:
		err = sk.PlotWS(sid)
	case engine.Mine:
		err = sk.MineWS(sid)
	case engine.Stop:
		err = sk.StopWS(sid)
	case engine.Remove:
		err = sk.RemoveWS(sid)
	case engine.Delete:
		err = sk.DeleteWS(sid)
	default:
		err = engine.ErrUnImplementedAction
	}
	return err
}
func (sk *SpaceKeeper) ActOnWorkSpaces(flags engine.WorkSpaceStateFlags, action engine.ActionType) (errs map[string]error, err error) {
	if !action.IsValid() {
		return nil, engine.ErrInvalidAction
	}
	switch action {
	case engine.Plot:
		errs = sk.PlotMultiWS(flags)
	case engine.Mine:
		errs = sk.MineMultiWS(flags)
	case engine.Stop:
		errs = sk.StopMultiWS(flags)
	case engine.Remove:
		errs = sk.RemoveMultiWS(flags)
	case engine.Delete:
		errs = sk.DeleteMultiWS(flags)
	default:
		return nil, engine.ErrUnImplementedAction
	}
	return errs, nil
}
func (sk *SpaceKeeper) SignHash(sid string, hash [32]byte) (*pocec.Signature, error) {
	if ws, ok := sk.workSpaceIndex[allState].Items()[sid]; ok {
		return sk.wallet.SignMessage(ws.id.PubKey(), hash[:])
	}
	return nil, ErrWorkSpaceDoesNotExist
}
func (sk *SpaceKeeper) PlotWS(sid string) error {
	sk.stateLock.RLock()
	defer sk.stateLock.RUnlock()
	if ws, ok := sk.workSpaceIndex[allState].Get(sid); !ok || !ws.using {
		return ErrWorkSpaceDoesNotExist
	}
	if ws, ok := sk.workSpaceIndex[engine.Registered].Get(sid); ok {
		sk.newQueuedWorkSpaceCh <- newQueuedWorkSpace(ws, false)
		return nil
	}
	if _, ok := sk.workSpaceIndex[engine.Plotting].Get(sid); ok {
		qws := sk.queue.PoppedItem()
		if qws.ws.id.String() != sid {
			return ErrWorkSpaceIsNotPlotting
		}
		qws.wouldMining = false
		return nil
	}
	return nil
}
func (sk *SpaceKeeper) MineWS(sid string) error {
	sk.stateLock.Lock()
	defer sk.stateLock.Unlock()
	if ws, ok := sk.workSpaceIndex[allState].Get(sid); !ok || !ws.using {
		return ErrWorkSpaceDoesNotExist
	}
	if ws, ok := sk.workSpaceIndex[engine.Registered].Get(sid); ok {
		sk.newQueuedWorkSpaceCh <- newQueuedWorkSpace(ws, true)
		return nil
	}
	if _, ok := sk.workSpaceIndex[engine.Plotting].Get(sid); ok {
		qws := sk.queue.PoppedItem()
		if qws.ws.id.String() != sid {
			return ErrWorkSpaceIsNotPlotting
		}
		qws.wouldMining = true
		return nil
	}
	if ws, ok := sk.workSpaceIndex[engine.Ready].Get(sid); ok {
		sk.workSpaceIndex[engine.Ready].Delete(sid)
		sk.workSpaceIndex[engine.Mining].Set(sid, ws)
		ws.state = engine.Mining
		return nil
	}
	return nil
}
func (sk *SpaceKeeper) StopWS(sid string) error {
	sk.stateLock.Lock()
	defer sk.stateLock.Unlock()
	if ws, ok := sk.workSpaceIndex[allState].Get(sid); !ok || !ws.using {
		return ErrWorkSpaceDoesNotExist
	}
	sk.queue.Delete(sid)
	if ws, ok := sk.workSpaceIndex[engine.Plotting].Get(sid); ok {
		qws := sk.queue.PoppedItem()
		if qws.ws.id.String() != sid {
			return ErrWorkSpaceIsNotPlotting
		}
		qws.wouldMining = false
		return ws.StopPlot()
	}
	if ws, ok := sk.workSpaceIndex[engine.Mining].Get(sid); ok {
		sk.workSpaceIndex[engine.Mining].Delete(sid)
		sk.workSpaceIndex[engine.Ready].Set(sid, ws)
		ws.state = engine.Ready
		return nil
	}
	return nil
}
func (sk *SpaceKeeper) RemoveWS(sid string) error {
	sk.stateLock.Lock()
	defer sk.stateLock.Unlock()
	var ok bool
	var ws *WorkSpace
	if ws, ok = sk.workSpaceIndex[allState].Get(sid); !ok || !ws.using {
		return ErrWorkSpaceDoesNotExist
	}
	sk.queue.Delete(sid)
	if ws, ok = sk.workSpaceIndex[engine.Registered].Get(sid); !ok {
		if ws, ok = sk.workSpaceIndex[engine.Ready].Get(sid); !ok {
			return ErrWorkSpaceIsNotStill
		}
	}
	sk.disuseWorkSpace(ws)
	return nil
}
func (sk *SpaceKeeper) DeleteWS(sid string) error {
	sk.stateLock.Lock()
	defer sk.stateLock.Unlock()
	var ok bool
	var ws *WorkSpace
	if ws, ok = sk.workSpaceIndex[allState].Get(sid); !ok || !ws.using {
		return ErrWorkSpaceDoesNotExist
	}
	sk.queue.Delete(sid)
	if ws, ok = sk.workSpaceIndex[engine.Registered].Get(sid); !ok {
		if ws, ok = sk.workSpaceIndex[engine.Ready].Get(sid); !ok {
			return ErrWorkSpaceIsNotStill
		}
	}
	sk.workSpaceIndex[ws.state].Delete(sid)
	sk.workSpaceIndex[allState].Delete(sid)
	sk.disuseWorkSpace(ws)
	return ws.Delete()
}
func (sk *SpaceKeeper) PlotMultiWS(flags engine.WorkSpaceStateFlags) map[string]error {
	result := make(map[string]error)
	for _, ws := range getWsByFlags(sk.workSpaceList, flags) {
		sid := ws.id.String()
		result[sid] = sk.PlotWS(sid)
	}
	return result
}
func (sk *SpaceKeeper) MineMultiWS(flags engine.WorkSpaceStateFlags) map[string]error {
	result := make(map[string]error)
	for _, ws := range getWsByFlags(sk.workSpaceList, flags) {
		sid := ws.id.String()
		result[sid] = sk.MineWS(sid)
	}
	return result
}
func (sk *SpaceKeeper) StopMultiWS(flags engine.WorkSpaceStateFlags) map[string]error {
	result := make(map[string]error)
	for _, ws := range getWsByFlags(sk.workSpaceList, flags) {
		sid := ws.id.String()
		result[sid] = sk.StopWS(sid)
	}
	return result
}
func (sk *SpaceKeeper) RemoveMultiWS(flags engine.WorkSpaceStateFlags) map[string]error {
	result := make(map[string]error)
	for _, ws := range getWsByFlags(sk.workSpaceList, flags) {
		sid := ws.id.String()
		result[sid] = sk.RemoveWS(sid)
	}
	return result
}
func (sk *SpaceKeeper) DeleteMultiWS(flags engine.WorkSpaceStateFlags) map[string]error {
	result := make(map[string]error)
	for _, ws := range getWsByFlags(sk.workSpaceList, flags) {
		sid := ws.id.String()
		result[sid] = sk.DeleteWS(sid)
	}
	return result
}
func (sk *SpaceKeeper) Configured() bool {
	return atomic.LoadInt32(&sk.configured) != 0
}
func (sk *SpaceKeeper) ResetDBDirs(dbDirs []string) error {
	if sk.Started() {
		return ErrSpaceKeeperIsRunning
	}
	absDirs := make([]string, len(dbDirs))
	for i := range dbDirs {
		absDir, err := filepath.Abs(dbDirs[i])
		if err != nil {
			return ErrInvalidDir
		}
		absDirs[i] = absDir
	}
	var strSliceEqual = func() bool {
		if len(absDirs) != len(sk.dbDirs) {
			return false
		}
		existsDir := make(map[string]struct{})
		for _, dir := range sk.dbDirs {
			existsDir[dir] = struct{}{}
		}
		for _, dir := range absDirs {
			if _, ok := existsDir[dir]; !ok {
				return false
			}
		}
		return true
	}
	if len(sk.dbDirs) == 0 {
		sk.dbDirs = absDirs
		if err := sk.generateInitialIndex(); err != nil {
			return err
		}
		return nil
	}
	if !strSliceEqual() {
		return ErrSpaceKeeperChangeDBDirs
	}
	return nil
}
func (sk *SpaceKeeper) AvailableDiskSize() uint64 {
	info, err := disk.Usage(sk.dbDirs[0])
	if err != nil {
		return 0
	}
	return info.Free
}
func (sk *SpaceKeeper) IsCapacityAvailable(path string, capacityBytes uint64) error {
	if path == "" {
		path = sk.dbDirs[0]
	}
	absPath, err := filepath.Abs(path)
	if err != nil {
		return err
	}
	info, err := disk.Usage(absPath)
	if err != nil {
		return err
	}
	freeBytes := info.Free
	var wsiList []engine.WorkSpaceInfo
	if p, ok := sk.workSpacePaths[absPath]; ok {
		for _, ws := range p.spaces {
			wsiList = append(wsiList, ws.Info())
		}
	} else if wsiList, err = peekMassDBInfosByDir(absPath, sk.dbType); err != nil {
		return err
	}
	var plottedBytes uint64
	for _, wsi := range wsiList {
		if wsi.State == engine.Ready || wsi.State == engine.Mining {
			plottedBytes += poc.ProofTypeDefault.PlotSize(wsi.BitLength)
		}
	}
	if freeBytes+plottedBytes < capacityBytes {
		return ErrOSDiskSizeNotEnough
	}
	return nil
}
func checkOSDiskSizeByPath(path string, requiredBytes int) error {
	if requiredBytes < 0 {
		return ErrInvalidRequiredBytes
	}
	info, err := disk.Usage(path)
	if err != nil {
		return err
	}
	if uint64(requiredBytes) >= info.Free {
		return ErrOSDiskSizeNotEnough
	}
	return nil
}
func (sk *SpaceKeeper) checkOSDiskSize(requiredBytes int) error {
	return checkOSDiskSizeByPath(sk.dbDirs[0], requiredBytes)
}
func usableBitLength() []int {
	return []int{24, 26, 28}
}