package activator

import (
	"reflect"

	"github.com/kataras/iris/context"
)

type binder struct {
	values []interface{}
	fields []field

	// saves any middleware that may need to be passed to the router,
	// statically, to gain performance.
	middleware context.Handlers
}

// binder accepts a value of something
// and tries to find its equalivent type
// inside the controller and sets that to it,
// after that each new instance of the controller will have
// this value on the specific field, like persistence data control does.
//
// returns a nil binder if values are not valid bindable data to the controller type.
func newBinder(elemType reflect.Type, values []interface{}) *binder {
	if len(values) == 0 {
		return nil
	}

	b := &binder{values: values}
	b.fields = b.lookup(elemType)

	// if nothing valid found return nil, so the caller
	// can omit the binder.
	if len(b.fields) == 0 && len(b.middleware) == 0 {
		return nil
	}

	return b
}

func (b *binder) storeValueIfMiddleware(value reflect.Value) bool {
	if value.CanInterface() {
		if m, ok := value.Interface().(context.Handler); ok {
			b.middleware = append(b.middleware, m)
			return true
		}
		if m, ok := value.Interface().(func(context.Context)); ok {
			b.middleware = append(b.middleware, m)
			return true
		}
	}
	return false
}

func (b *binder) lookup(elem reflect.Type) (fields []field) {
	for _, v := range b.values {
		value := reflect.ValueOf(v)
		// handlers will be recognised as middleware, not struct fields.
		// End-Developer has the option to call any handler inside
		// the controller's `BeginRequest` and `EndRequest`, the
		// state is respected from the method handler already.
		if b.storeValueIfMiddleware(value) {
			// stored as middleware, continue to the next field, we don't have
			// to bind anything here.
			continue
		}

		matcher := func(elemField reflect.StructField) bool {
			return elemField.Type == value.Type()
		}

		handler := func(f *field) {
			f.Value = value
		}

		fields = append(fields, lookupFields(elem, matcher, handler)...)
	}
	return
}

func (b *binder) handle(c reflect.Value) {
	// we could make check for middlewares here but
	// these could easly be used outside of the controller
	// so we don't have to initialize a controller to call them
	// so they don't belong actually here, we will register them to the
	// router itself, before the controller's handler to gain performance,
	// look `activator.go#RegisterMethodHandlers` for more.

	elem := c.Elem() // controller should always be a pointer at this state
	for _, f := range b.fields {
		f.sendTo(elem)
	}
}
