package eval

import (
	
	
	
	

	
	
	
)

var (
	// ErrArgs is thrown when a Go function gets erroneous arguments.
	//
	// TODO(xiaq): Replace this single error type with multiple types that carry
	// richer error information.
	ErrArgs = errors.New("args error")
	// ErrNoOptAccepted is thrown when a Go function that does not accept any
	// options gets passed options.
	ErrNoOptAccepted = errors.New("function does not accept any options")
)

type goFn struct {
	name string
	impl interface{}

	// Type information of impl.

	// If true, pass the frame as a *Frame argument.
	frame bool
	// If true, pass options as a RawOptions argument.
	rawOptions bool
	// If not nil, type of the parameter that gets options via RawOptions.Scan.
	options reflect.Type
	// If not nil, pass the inputs as an Input-typed last argument.
	inputs bool
	// Type of "normal" (non-frame, non-options, non-variadic) arguments.
	normalArgs []reflect.Type
	// If not nil, type of variadic arguments.
	variadicArg reflect.Type
}

// An interface to be implemented by pointers to structs that should hold
// scanned options.
type optionsPtr interface {
	SetDefaultOptions()
}

// Inputs is the type that the last parameter of a Go-native function can take.
// When that is the case, it is a callback to get inputs. See the doc of GoFn
// for details.
type Inputs func(func(interface{}))

var (
	frameType      = reflect.TypeOf((*Frame)(nil))
	rawOptionsType = reflect.TypeOf(RawOptions(nil))
	optionsPtrType = reflect.TypeOf((*optionsPtr)(nil)).Elem()
	inputsType     = reflect.TypeOf(Inputs(nil))
)

// NewGoFn wraps a Go function into an Elvish function using reflection.
//
// Parameters are passed following these rules:
//
// 1. If the first parameter of function has type *Frame, it gets the current
// call frame.
//
// 2. After the potential *Frame argument, the first parameter has type
// RawOptions, it gets a map of option names to their values.
//
// Alternatively, this parameter may be a (non-pointer) struct whose pointer
// type implements a SetDefaultOptions method that takes no arguments and has no
// return value. In this case, a new instance of the struct is constructed, the
// SetDefaultOptions method is called, and any option passed to the Elvish
// function is used to populate the fields of the struct. Field names are mapped
// to option names using strutil.CamelToDashed, unless they have a field tag
// "name", in which case the tag is preferred.
//
// If the function does not declare that it accepts options via either method
// described above, it accepts no options.
//
// 3. If the last parameter is non-variadic and has type Inputs, it represents
// an optional parameter that contains the input to this function. If the
// argument is not supplied, the input channel of the Frame will be used to
// supply the inputs.
//
// 4. Other parameters are converted using vals.ScanToGo.
//
// Return values go to the channel part of the stdout port, after being
// converted using vals.FromGo. If the last return value has type error and is
// not nil, it is turned into an exception and no outputting happens. If the
// last return value is a nil error, it is ignored.
func ( string,  interface{}) Callable {
	 := reflect.TypeOf()
	 := &goFn{name: , impl: }

	 := 0
	if  < .NumIn() && .In() == frameType {
		.frame = true
		++
	}
	if  < .NumIn() && .In() == rawOptionsType {
		.rawOptions = true
		++
	}
	if  < .NumIn() && reflect.PtrTo(.In()).Implements(optionsPtrType) {
		if .rawOptions {
			panic("Function declares both RawOptions and Options parameters")
		}
		.options = .In()
		++
	}
	for ;  < .NumIn(); ++ {
		 := .In()
		if  == .NumIn()-1 {
			if .IsVariadic() {
				.variadicArg = .Elem()
				break
			} else if  == inputsType {
				.inputs = true
				break
			}
		}
		.normalArgs = append(.normalArgs, )
	}
	return 
}

// Kind returns "fn".
func (*goFn) () string {
	return "fn"
}

// Equal compares identity.
func ( *goFn) ( interface{}) bool {
	return  == 
}

// Hash hashes the address.
func ( *goFn) () uint32 {
	return hash.Pointer(unsafe.Pointer())
}

// Repr returns an opaque representation "<builtin $name>".
func ( *goFn) (int) string {
	return "<builtin " + .name + ">"
}

// error(nil) is treated as nil by reflect.TypeOf, so we first get the type of
// *error and use Elem to obtain type of error.
var errorType = reflect.TypeOf((*error)(nil)).Elem()

var errNoOptions = errors.New("function does not accept any options")

// Call calls the implementation using reflection.
func ( *goFn) ( *Frame,  []interface{},  map[string]interface{}) error {
	if .variadicArg != nil {
		if len() < len(.normalArgs) {
			return errs.ArityMismatch{
				What:     "arguments here",
				ValidLow: len(.normalArgs), ValidHigh: -1, Actual: len()}
		}
	} else if .inputs {
		if len() != len(.normalArgs) && len() != len(.normalArgs)+1 {
			return errs.ArityMismatch{
				What:     "arguments here",
				ValidLow: len(.normalArgs), ValidHigh: len(.normalArgs) + 1, Actual: len()}
		}
	} else if len() != len(.normalArgs) {
		return errs.ArityMismatch{
			What:     "arguments here",
			ValidLow: len(.normalArgs), ValidHigh: len(.normalArgs), Actual: len()}
	}
	if !.rawOptions && .options == nil && len() > 0 {
		return ErrNoOptAccepted
	}

	var  []reflect.Value
	if .frame {
		 = append(, reflect.ValueOf())
	}
	if .rawOptions {
		 = append(, reflect.ValueOf())
	}
	if .options != nil {
		 := reflect.New(.options)
		 := .Interface()
		.(optionsPtr).SetDefaultOptions()
		 := scanOptions(, )
		if  != nil {
			return 
		}
		 = append(, .Elem())
	}
	for ,  := range  {
		var  reflect.Type
		if  < len(.normalArgs) {
			 = .normalArgs[]
		} else if .variadicArg != nil {
			 = .variadicArg
		} else if .inputs {
			break // Handled after the loop
		} else {
			panic("impossible")
		}
		 := reflect.New()
		 := vals.ScanToGo(, .Interface())
		if  != nil {
			return fmt.Errorf("wrong type of argument %d: %v", , )
		}
		 = append(, .Elem())
	}

	if .inputs {
		var  Inputs
		if len() == len(.normalArgs) {
			 = .IterateInputs
		} else {
			// Wrap an iterable argument in Inputs.
			 := [len()-1]
			if !vals.CanIterate() {
				return fmt.Errorf("%s cannot be iterated", vals.Kind())
			}
			 = func( func(interface{})) {
				// CanIterate(iterable) is true
				_ = vals.Iterate(, func( interface{}) bool {
					()
					return true
				})
			}
		}
		 = append(, reflect.ValueOf())
	}

	 := reflect.ValueOf(.impl).Call()

	if len() > 0 && [len()-1].Type() == errorType {
		 := [len()-1].Interface()
		if  != nil {
			return .(error)
		}
		 = [:len()-1]
	}

	for ,  := range  {
		.OutputChan() <- vals.FromGo(.Interface())
	}
	return nil
}