// Package middleware implements the TrackerLogic interface by executing
// a series of middleware hooks.
package middleware

import (
	"errors"
	"sync"

	yaml "gopkg.in/yaml.v2"
)

var (
	driversM sync.RWMutex
	drivers  = make(map[string]Driver)

	// ErrDriverDoesNotExist is the error returned by NewMiddleware when a
	// middleware driver with that name does not exist.
	ErrDriverDoesNotExist = errors.New("middleware driver with that name does not exist")
)

// Driver is the interface used to initialize a new type of middleware.
//
// The options parameter is YAML encoded bytes that should be unmarshalled into
// the hook's custom configuration.
type Driver interface {
	NewHook(options []byte) (Hook, error)
}

// RegisterDriver makes a Driver available by the provided name.
//
// If called twice with the same name, the name is blank, or if the provided
// Driver is nil, this function panics.
func RegisterDriver(name string, d Driver) {
	if name == "" {
		panic("middleware: could not register a Driver with an empty name")
	}
	if d == nil {
		panic("middleware: could not register a nil Driver")
	}

	driversM.Lock()
	defer driversM.Unlock()

	if _, dup := drivers[name]; dup {
		panic("middleware: RegisterDriver called twice for " + name)
	}

	drivers[name] = d
}

// New attempts to initialize a new middleware instance from the
// list of registered Drivers.
//
// If a driver does not exist, returns ErrDriverDoesNotExist.
func New(name string, optionBytes []byte) (Hook, error) {
	driversM.RLock()
	defer driversM.RUnlock()

	var d Driver
	d, ok := drivers[name]
	if !ok {
		return nil, ErrDriverDoesNotExist
	}

	return d.NewHook(optionBytes)
}

// HookConfig is the generic configuration format used for all registered Hooks.
type HookConfig struct {
	Name    string                 `yaml:"name"`
	Options map[string]interface{} `yaml:"options"`
}

// HooksFromHookConfigs is a utility function for initializing Hooks in bulk.
func HooksFromHookConfigs(cfgs []HookConfig) (hooks []Hook, err error) {
	for _, cfg := range cfgs {
		// Marshal the options back into bytes.
		var optionBytes []byte
		optionBytes, err = yaml.Marshal(cfg.Options)
		if err != nil {
			return
		}

		var h Hook
		h, err = New(cfg.Name, optionBytes)
		if err != nil {
			return
		}

		hooks = append(hooks, h)
	}

	return
}