package plg_search_sqlitefts

import (
	"container/heap"
	"context"
	"path/filepath"
	"reflect"
	"sync"

	. "github.com/mickael-kerjean/filestash/server/common"
)

var SProc SearchProcess = SearchProcess{
	idx: make([]SearchIndexer, 0),
	n:   -1,
}

type SearchProcess struct {
	idx []SearchIndexer
	n   int
	mu  sync.RWMutex
}

func (this *SearchProcess) HintLs(app *App, path string) *SearchIndexer {
	id := GenerateID(app)

	// try to find the search indexer among the existing ones
	this.mu.RLock()
	for i := len(this.idx) - 1; i >= 0; i-- {
		if id == this.idx[i].Id {
			alreadyHasPath := false
			for j := 0; j < len(this.idx[i].FoldersUnknown); j++ {
				if this.idx[i].FoldersUnknown[j].Path == path {
					alreadyHasPath = true
					break
				}
			}
			if alreadyHasPath == false {
				heap.Push(&this.idx[i].FoldersUnknown, &Document{
					Type:        "directory",
					Path:        path,
					InitialPath: path,
					Name:        filepath.Base(path),
				})
			}
			ret := &this.idx[i]
			this.mu.RUnlock()
			return ret
		}
	}
	this.mu.RUnlock()

	// Having all indexers running in memory could be expensive => instead we're cycling a pool
	search_process_max := SEARCH_PROCESS_MAX()
	this.mu.Lock()
	lenIdx := len(this.idx)
	if lenIdx > 0 && search_process_max > 0 && lenIdx > (search_process_max-1) {
		toDel := this.idx[0 : lenIdx-(search_process_max-1)]
		for i := range toDel {
			toDel[i].DB.Close()
		}
		this.idx = this.idx[lenIdx-(search_process_max-1):]
	}
	// instantiate the new indexer
	s := NewSearchIndexer(id, app.Backend)
	v := reflect.ValueOf(app.Backend).Elem().FieldByName("Context")
	if v.IsValid() && v.CanSet() {
		// prevent context expiration which is often default as r.Context()
		// as we need to make queries outside the scope of a normal http request
		v.Set(reflect.ValueOf(context.Background()))
	}

	heap.Push(&s.FoldersUnknown, &Document{
		Type:        "directory",
		Path:        path,
		InitialPath: path,
		Name:        filepath.Base(path),
	})
	this.idx = append(this.idx, s)
	this.mu.Unlock()
	return &s
}

func (this *SearchProcess) HintRm(app *App, path string) {
	id := GenerateID(app)
	this.mu.RLock()
	for i := len(this.idx) - 1; i >= 0; i-- {
		if id == this.idx[i].Id {
			this.idx[i].DB.Exec("DELETE FROM file WHERE path >= ? AND path < ?", path, path+"~")
			break
		}
	}
	this.mu.RUnlock()
}

func (this *SearchProcess) HintFile(app *App, path string) {
	id := GenerateID(app)
	this.mu.RLock()
	for i := len(this.idx) - 1; i >= 0; i-- {
		if id == this.idx[i].Id {
			this.idx[i].DB.Exec("UPDATE file set indexTime = NULL WHERE path = ?", path)
			break
		}
	}
	this.mu.RUnlock()
}

func (this *SearchProcess) Peek() *SearchIndexer {
	if len(this.idx) == 0 {
		return nil
	}
	this.mu.Lock()
	if this.n >= len(this.idx)-1 || this.n < 0 {
		this.n = 0
	} else {
		this.n = this.n + 1
	}
	s := &this.idx[this.n]
	this.mu.Unlock()
	return s
}

func (this *SearchProcess) Reset() {
	this.mu.Lock()
	for i := range this.idx {
		this.idx[i].DB.Close()
	}
	this.idx = make([]SearchIndexer, 0)
	this.mu.Unlock()
	this.n = -1
}
