package registry

import (
	"path"
	"reflect"
	"runtime"
)

// Global
var (
	Global       = New()
	Register     = Global.Register
	Descriptions = Global.Descriptions
	GetEntry     = Global.Entry
	Add          = Global.Add
)

// M Alias for map[string]interface{}
type M = map[string]interface{}

// R the function registry
type R struct {
	data map[string]*Entry
}

// New creates a new registry
func New() *R {
	return &R{map[string]*Entry{}}
}

// Clone an existing registry
func (r *R) Clone() *R {
	newR := &R{map[string]*Entry{}}
	for k, v := range r.data {
		newR.data[k] = v
	}
	return newR
}

// Add unnamed function
func (r *R) Add(fns ...interface{}) Batch {

	b := Batch{}
	for _, fn := range fns {
		if reflect.TypeOf(fn).Kind() != reflect.Func {
			return nil
		}
		// Automatic naming
		name := runtime.FuncForPC(reflect.ValueOf(fn).Pointer()).Name()
		name = path.Ext(name)[1:]
		b = append(b, r.Register(name, fn))
	}

	return b
}

//Register should be a func only register
func (r *R) Register(name string, v interface{}) *Entry {
	e := NewEntry(r, v)
	if e.err != nil {
		return e
	}
	r.data[name] = e
	return e
}

// Get an entry
func (r *R) Get(name string, params ...interface{}) (interface{}, error) {
	e, ok := r.data[name]
	if !ok {
		return nil, ErrNotFound
	}
	v := e.fn
	// We already know this is a function
	// and that returns 1 or more values

	vtyp := reflect.TypeOf(v)
	if vtyp.Out(0).Kind() != reflect.Func {
		return v, nil
	}
	// Constructor
	fparam := make([]reflect.Value, len(params))
	for i := range params {
		fparam[i] = reflect.ValueOf(params[i])
	}
	// Call the func and return the thing
	v = reflect.ValueOf(v).Call(fparam)[0].Interface()

	return v, nil
}

// Entry fetches entries from the register
func (r *R) Entry(name string) (*Entry, error) {
	e, ok := r.data[name]
	if !ok {
		return nil, ErrNotFound
	}
	return e, nil
}

//Describe named fn

// Descriptions Description list
func (r *R) Descriptions() (map[string]Description, error) {
	ret := map[string]Description{}
	for k, e := range r.data {
		ret[k] = *e.Description
	}
	return ret, nil
}
