package walker

import (
	"fmt"
	"os"
	"plugin"
	"sync"
)

// RegisterFuzzFunc is for static linking (not plugin) model
func RegisterFuzzFunc(name string, fnc FuzzFunc) error {
	ffLock.Lock()
	defer ffLock.Unlock()
	if _, ok := fuzzfuncs[name]; ok {
		// maybe just output a warning and not bail here?
		return fmt.Errorf("RegisterFuzzFunc: %q is already registered", name)
	}
	fuzzfuncs[name] = fnc
	return nil
}

func FuncMap() map[string]FuzzFunc {
	return fuzzfuncs
}

var fuzzfuncs = map[string]FuzzFunc{}
var ffLock sync.Mutex

// LoadPlugin loads the file path and returns a Fuzzer.
//
// This is only for making a plugin.
func LoadPlugin(path string) (Fuzzer, error) {
	p, err := plugin.Open(path)
	if err != nil {
		return nil, err
	}
	sym, err := p.Lookup("NewFuzzer")
	if err != nil {
		return nil, fmt.Errorf("LoadPlugin: %q", err)
	}
	nf, ok := sym.(func() (Fuzzer, error))
	if !ok {
		return nil, fmt.Errorf("LoadPlugin: plugin at %q did not provide a NewFuzzer function", path)
	}
	return nf()
}

// NewFuzzer initializes a new Fuzzer.
//
// This is only for making a plugin.
type NewFuzzer func() (Fuzzer, error)

// Fuzzer is a struct that produces a FuzzFunc.
//
// This is only for making a plugin.
type Fuzzer interface {
	Name() string
	Func(path string, info os.FileInfo) error
}

// FuzzFunc is like filepath.WalkerFunc, but for poking at `path`
type FuzzFunc func(path string, info os.FileInfo) error
