package engine

import (
	"context"
	"sync"

	"github.com/deranjer/gvc/common/database"
	watcher "github.com/radovskyb/watcher"
	"github.com/rs/zerolog"
)

type key string
type Event struct {
	Name     string
	Progress int
	Total    int
}

// The watcher is responsible for not only seeing when a file changes,
// but also keeping track of
// * the file hash so that if it changes again any modifications can be handled
// * copying any versions and keeping them safe (even if temporary)
// * creating the diff of the file, in both directions if necessary
// * storing the details in the database
func NewWatcher(logger *zerolog.Logger, keyFolder, downloadFolder, syncFolder, thumbFolder, diffFolder string) (FileWatcher, error) {
	w := FileWatcher{
		watcher.New(),
		logger,
		true, //used to temporarily ignore events if necessary
		keyFolder, downloadFolder, syncFolder, thumbFolder, diffFolder,
	}
	return w, nil
}

func (fw *FileWatcher) Ignore() bool {
	fw.Enabled = false
	return fw.Enabled
}
func (fw *FileWatcher) Enable() bool {
	fw.Enabled = true
	return fw.Enabled
}
func (fw *FileWatcher) IsEnabled() bool {
	return fw.Enabled
}

// BeginWatcherRoutine kicks off the watcher. When the watcher noticies a file change,
// certain actions will be taken in case of event and error
// the routine will handle these whenever this occurs.
// If certain functions need to be called then this will
// need to be specified as part of the managers lambda functions
// TODO: Should return an error
func (fw *FileWatcher) BeginWatcherRoutine(ctx context.Context, wg *sync.WaitGroup, diffChannel chan database.DiffObject, onFileChanged func(string) (database.File, error)) {

	//seems a bit barking, but we can now cancel any diff that is occuring on a file when it fires again
	cancelFunctions := make(map[string]func())
	for {
		select {
		// we have filtered already on the [Op]erations we want to listen for so no need to check here
		case event := <-fw.Watcher.Event:
			if !fw.IsEnabled() {
				fw.Info().Msgf("ignoring event and reenabling the watcher %s\r\n", event)
				fw.Enable()
				continue
			}
			fw.Info().Msgf("event fired ", event)
			//this is currently slow as it does a db lookup on the path.
			//TODO: On load (or whenever a file is added to the watcher, the db information for files being watched, could be cached in memory. This would be much faster)
			fileInfo, err := onFileChanged(event.Path) //could return the 'Event' object here
			syncFilePath := fileInfo.BaseFilePath
			//uniqueName := fileInfo.Unique

			// fileID := fileInfo.ID
			//we need the hash of the current base, not the hash of the original file
			// fileHash := fileInfo.CurrentHash //hash needs to come from
			if err != nil {
				fw.Err(err).Msg("path was not returned to sync path")
				continue
			}
			//cancel the event if it indeed is running...
			if cancelFunctions[event.Path] != nil {
				cancelFunctions[event.Path]()
				delete(cancelFunctions, event.Path)
			}

			//context for the current event. Calling cancel will cancel the routines
			//to kill a context you must have access to the cancel function.
			// i could add the cancel function to a map of them
			// if you want to kill it you call on the correct cancel function
			// that will kill the context. Fine.
			// If however you want to kill it from another place....
			// then you need a cancel channel, which inturn calls the equivelent cancel function.... ok
			// sounds about best i can do right now...
			// kind of bonkers right....
			cancelContext, cancel := context.WithCancel(ctx)
			cancelFunctions[event.Path] = cancel
			// good idea to not use strings as keys directly as can conflict across namespaces
			// this needs to be sorted out -- too many things called an event....
			// TODO: its totally bananas
			e := Event{
				Name:     event.Path,
				Progress: 0,
				Total:    100,
			}
			eventContext := context.WithValue(cancelContext, key(event.Path), e)
			if err := manageFileDiffing(eventContext, event.Path, syncFilePath, fw.DiffFolder, diffChannel, wg); err != nil {
				// I don't think this can be reached...
				fw.Warn().Msgf("Error managing the diffing process %s", err)
			}
		case err := <-fw.Watcher.Error:
			fw.Err(err)
		case <-fw.Watcher.Closed:
			//fw.Notice("radovskyb closed")
			return
		}
	}
}
