package experimental

import (
	

	
	
)

// StackIterator allows iterating on each function of the call stack, starting
// from the top. At least one call to Next() is required to start the iteration.
//
// Note: The iterator provides a view of the call stack at the time of
// iteration. As a result, parameter values may be different than the ones their
// function was called with.
type StackIterator interface {
	// Next moves the iterator to the next function in the stack. Returns
	// false if it reached the bottom of the stack.
	Next() bool
	// Function describes the function called by the current frame.
	Function() InternalFunction
	// ProgramCounter returns the program counter associated with the
	// function call.
	ProgramCounter() ProgramCounter
}

// WithFunctionListenerFactory registers a FunctionListenerFactory
// with the context.
func ( context.Context,  FunctionListenerFactory) context.Context {
	return context.WithValue(, expctxkeys.FunctionListenerFactoryKey{}, )
}

// FunctionListenerFactory returns FunctionListeners to be notified when a
// function is called.
type FunctionListenerFactory interface {
	// NewFunctionListener returns a FunctionListener for a defined function.
	// If nil is returned, no listener will be notified.
	NewFunctionListener(api.FunctionDefinition) FunctionListener
	// ^^ A single instance can be returned to avoid instantiating a listener
	// per function, especially as they may be thousands of functions. Shared
	// listeners use their FunctionDefinition parameter to clarify.
}

// FunctionListener can be registered for any function via
// FunctionListenerFactory to be notified when the function is called.
type FunctionListener interface {
	// Before is invoked before a function is called.
	//
	// There is always one corresponding call to After or Abort for each call to
	// Before. This guarantee allows the listener to maintain an internal stack
	// to perform correlations between the entry and exit of functions.
	//
	// # Params
	//
	//   - ctx: the context of the caller function which must be the same
	//	   instance or parent of the result.
	//   - mod: the calling module.
	//   - def: the function definition.
	//   - params:  api.ValueType encoded parameters.
	//   - stackIterator: iterator on the call stack. At least one entry is
	//     guaranteed (the called function), whose Args() will be equal to
	//     params. The iterator will be reused between calls to Before.
	//
	// Note: api.Memory is meant for inspection, not modification.
	// mod can be cast to InternalModule to read non-exported globals.
	Before(ctx context.Context, mod api.Module, def api.FunctionDefinition, params []uint64, stackIterator StackIterator)

	// After is invoked after a function is called.
	//
	// # Params
	//
	//   - ctx: the context of the caller function.
	//   - mod: the calling module.
	//   - def: the function definition.
	//   - results: api.ValueType encoded results.
	//
	// # Notes
	//
	//   - api.Memory is meant for inspection, not modification.
	//   - This is not called when a host function panics, or a guest function traps.
	//      See Abort for more details.
	After(ctx context.Context, mod api.Module, def api.FunctionDefinition, results []uint64)

	// Abort is invoked when a function does not return due to a trap or panic.
	//
	// # Params
	//
	//   - ctx: the context of the caller function.
	//   - mod: the calling module.
	//   - def: the function definition.
	//   - err: the error value representing the reason why the function aborted.
	//
	// # Notes
	//
	//   - api.Memory is meant for inspection, not modification.
	Abort(ctx context.Context, mod api.Module, def api.FunctionDefinition, err error)
}

// FunctionListenerFunc is a function type implementing the FunctionListener
// interface, making it possible to use regular functions and methods as
// listeners of function invocation.
//
// The FunctionListener interface declares two methods (Before and After),
// but this type invokes its value only when Before is called. It is best
// suites for cases where the host does not need to perform correlation
// between the start and end of the function call.
type FunctionListenerFunc func(context.Context, api.Module, api.FunctionDefinition, []uint64, StackIterator)

// Before satisfies the FunctionListener interface, calls f.
func ( FunctionListenerFunc) ( context.Context,  api.Module,  api.FunctionDefinition,  []uint64,  StackIterator) {
	(, , , , )
}

// After is declared to satisfy the FunctionListener interface, but it does
// nothing.
func ( FunctionListenerFunc) (context.Context, api.Module, api.FunctionDefinition, []uint64) {
}

// Abort is declared to satisfy the FunctionListener interface, but it does
// nothing.
func ( FunctionListenerFunc) (context.Context, api.Module, api.FunctionDefinition, error) {
}

// FunctionListenerFactoryFunc is a function type implementing the
// FunctionListenerFactory interface, making it possible to use regular
// functions and methods as factory of function listeners.
type FunctionListenerFactoryFunc func(api.FunctionDefinition) FunctionListener

// NewFunctionListener satisfies the FunctionListenerFactory interface, calls f.
func ( FunctionListenerFactoryFunc) ( api.FunctionDefinition) FunctionListener {
	return ()
}

// MultiFunctionListenerFactory constructs a FunctionListenerFactory which
// combines the listeners created by each of the factories passed as arguments.
//
// This function is useful when multiple listeners need to be hooked to a module
// because the propagation mechanism based on installing a listener factory in
// the context.Context used when instantiating modules allows for a single
// listener to be installed.
//
// The stack iterator passed to the Before method is reset so that each listener
// can iterate the call stack independently without impacting the ability of
// other listeners to do so.
func ( ...FunctionListenerFactory) FunctionListenerFactory {
	 := make(multiFunctionListenerFactory, len())
	copy(, )
	return 
}

type multiFunctionListenerFactory []FunctionListenerFactory

func ( multiFunctionListenerFactory) ( api.FunctionDefinition) FunctionListener {
	var  []FunctionListener
	for ,  := range  {
		if  := .NewFunctionListener();  != nil {
			 = append(, )
		}
	}
	switch len() {
	case 0:
		return nil
	case 1:
		return [0]
	default:
		return &multiFunctionListener{lstns: }
	}
}

type multiFunctionListener struct {
	lstns []FunctionListener
	stack stackIterator
}

func ( *multiFunctionListener) ( context.Context,  api.Module,  api.FunctionDefinition,  []uint64,  StackIterator) {
	.stack.base = 
	for ,  := range .lstns {
		.stack.index = -1
		.Before(, , , , &.stack)
	}
}

func ( *multiFunctionListener) ( context.Context,  api.Module,  api.FunctionDefinition,  []uint64) {
	for ,  := range .lstns {
		.After(, , , )
	}
}

func ( *multiFunctionListener) ( context.Context,  api.Module,  api.FunctionDefinition,  error) {
	for ,  := range .lstns {
		.Abort(, , , )
	}
}

type stackIterator struct {
	base  StackIterator
	index int
	pcs   []uint64
	fns   []InternalFunction
}

func ( *stackIterator) () bool {
	if .base != nil {
		.pcs = .pcs[:0]
		.fns = .fns[:0]

		for .base.Next() {
			.pcs = append(.pcs, uint64(.base.ProgramCounter()))
			.fns = append(.fns, .base.Function())
		}

		.base = nil
	}
	.index++
	return .index < len(.pcs)
}

func ( *stackIterator) () ProgramCounter {
	return ProgramCounter(.pcs[.index])
}

func ( *stackIterator) () InternalFunction {
	return .fns[.index]
}

// StackFrame represents a frame on the call stack.
type StackFrame struct {
	Function     api.Function
	Params       []uint64
	Results      []uint64
	PC           uint64
	SourceOffset uint64
}

type internalFunction struct {
	definition   api.FunctionDefinition
	sourceOffset uint64
}

func ( internalFunction) () api.FunctionDefinition {
	return .definition
}

func ( internalFunction) ( ProgramCounter) uint64 {
	return .sourceOffset
}

// stackFrameIterator is an implementation of the experimental.stackFrameIterator
// interface.
type stackFrameIterator struct {
	index int
	stack []StackFrame
	fndef []api.FunctionDefinition
}

func ( *stackFrameIterator) () bool {
	.index++
	return .index < len(.stack)
}

func ( *stackFrameIterator) () InternalFunction {
	return internalFunction{
		definition:   .fndef[.index],
		sourceOffset: .stack[.index].SourceOffset,
	}
}

func ( *stackFrameIterator) () ProgramCounter {
	return ProgramCounter(.stack[.index].PC)
}

// NewStackIterator constructs a stack iterator from a list of stack frames.
// The top most frame is the last one.
func ( ...StackFrame) StackIterator {
	 := &stackFrameIterator{
		index: -1,
		stack: make([]StackFrame, len()),
		fndef: make([]api.FunctionDefinition, len()),
	}
	for  := range  {
		.stack[] = [len()-(+1)]
	}
	// The size of function definition is only one pointer which should allow
	// the compiler to optimize the conversion to api.FunctionDefinition; but
	// the presence of internal.WazeroOnlyType, despite being defined as an
	// empty struct, forces a heap allocation that we amortize by caching the
	// result.
	for ,  := range  {
		.fndef[] = .Function.Definition()
	}
	return 
}

// BenchmarkFunctionListener implements a benchmark for function listeners.
//
// The benchmark calls Before and After methods repeatedly using the provided
// module an stack frames to invoke the methods.
//
// The stack frame is a representation of the call stack that the Before method
// will be invoked with. The top of the stack is stored at index zero. The stack
// must contain at least one frame or the benchmark will fail.
func ( int,  api.Module,  []StackFrame,  FunctionListener) {
	if len() == 0 {
		panic("cannot benchmark function listener with an empty stack")
	}

	 := context.Background()
	 := [0].Function.Definition()
	 := [0].Params
	 := [0].Results
	 := &stackIterator{base: NewStackIterator(...)}

	for  := 0;  < ; ++ {
		.index = -1
		.Before(, , , , )
		.After(, , , )
	}
}

// TODO: the calls to Abort are not yet tested in internal/testing/enginetest,
// but they are validated indirectly in tests which exercise host logging,
// like Test_procExit in imports/wasi_snapshot_preview1. Eventually we should
// add dedicated tests to validate the behavior of the interpreter and compiler
// engines independently.