package plg_search_sqlitefts

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

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

/*
 * We're listening to what the user is doing to hint the crawler over
 * what needs to be updated in priority, what file got updated and would need
 * to be reindexed, what should disappear from the index, ....
 * This way we can fine tune how full text search is behaving
 */

type FileHook struct{}

func (this FileHook) Ls(ctx *App, path string) error {
	if this.record(ctx) {
		go DaemonState.HintLs(ctx, path)
	}
	return nil
}

func (this FileHook) Cat(ctx *App, path string) error {
	if this.record(ctx) {
		go DaemonState.HintLs(ctx, filepath.Dir(path)+"/")
	}
	return nil
}

func (this FileHook) Mkdir(ctx *App, path string) error {
	if this.record(ctx) {
		go func() {
			DaemonState.HintLs(ctx, filepath.Dir(path)+"/")
			DaemonState.HintLs(ctx, path)
		}()
	}
	return nil
}

func (this FileHook) Rm(ctx *App, path string) error {
	if this.record(ctx) {
		go DaemonState.HintRm(ctx, path)
	}
	return nil
}

func (this FileHook) Mv(ctx *App, from string, to string) error {
	if this.record(ctx) {
		go func() {
			DaemonState.HintRm(ctx, filepath.Dir(from)+"/")
			DaemonState.HintLs(ctx, to+"/")
			DaemonState.HintLs(ctx, filepath.Dir(to)+"/")
		}()
	}
	return nil
}

func (this FileHook) Save(ctx *App, path string) error {
	if this.record(ctx) {
		go func() {
			DaemonState.HintLs(ctx, filepath.Dir(path)+"/")
			DaemonState.HintFile(ctx, path)
		}()
	}
	return nil
}

func (this FileHook) Touch(ctx *App, path string) error {
	if this.record(ctx) {
		go func() {
			DaemonState.HintLs(ctx, filepath.Dir(path)+"/")
			DaemonState.HintFile(ctx, path)
		}()
	}
	return nil
}

func (this FileHook) record(ctx *App) bool {
	if ctx.Context.Value("AUDIT") == false {
		return false
	}
	return true
}

func (this *daemonState) HintLs(app *App, path string) {
	id := GenerateID(app.Session)
	this.mu.Lock()
	defer this.mu.Unlock()

	// try to find the search indexer among the existing ones
	for i := len(this.idx) - 1; i >= 0; i-- {
		if id != this.idx[i].Id {
			continue
		}
		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),
			})
		}
		return
	}

	// Having all indexers running in memory could be expensive => instead we're cycling a pool
	search_process_max := SEARCH_PROCESS_MAX()
	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].Close()
		}
		this.idx = this.idx[lenIdx-(search_process_max-1):]
	}
	// instantiate the new indexer
	app.Context = context.Background()
	crawlerBackend, err := app.Backend.Init(app.Session, app)
	if err != nil {
		Log.Warning("plg_search_sqlitefs::init message=cannot_create_crawler err=%s", err.Error())
		return
	}
	s, err := NewCrawler(id, crawlerBackend)
	if err != nil {
		Log.Warning("plg_search_sqlitefs::init message=cannot_create_crawler err=%s", err.Error())
		return
	}
	defer func() {
		// recover from panic if one occurred. Set err to nil otherwise.
		if r := recover(); r != nil {
			name := "na"
			for _, el := range crawlerBackend.LoginForm().Elmnts {
				if el.Name == "type" {
					name = el.Value.(string)
				}
			}
			Log.Error("plg_search_sqlitefs::panic backend=\"%s\" recover=\"%s\"", name, r)
		}
	}()
	heap.Push(&s.FoldersUnknown, &Document{
		Type:        "directory",
		Path:        path,
		InitialPath: path,
		Name:        filepath.Base(path),
	})
	this.idx = append(this.idx, s)
}

func (this *daemonState) HintRm(app *App, path string) {
	id := GenerateID(app.Session)
	this.mu.RLock()
	for i := len(this.idx) - 1; i >= 0; i-- {
		if id != this.idx[i].Id {
			continue
		}
		if op, err := this.idx[i].State.Change(); err == nil {
			op.RemoveAll(path)
			op.Commit()
		}
		break
	}
	this.mu.RUnlock()
}

func (this *daemonState) HintFile(app *App, path string) {
	id := GenerateID(app.Session)
	this.mu.RLock()
	for i := len(this.idx) - 1; i >= 0; i-- {
		if id != this.idx[i].Id {
			continue
		}
		if op, err := this.idx[i].State.Change(); err == nil {
			op.IndexTimeClear(path)
			op.Commit()
		}
		break
	}
	this.mu.RUnlock()
}

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