// Copyright (c) 2022 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

package dig

import (
	
	

	
	
)

type decoratorState int

const (
	decoratorReady decoratorState = iota
	decoratorOnStack
	decoratorCalled
)

type decorator interface {
	Call(c containerStore) error
	ID() dot.CtorID
	State() decoratorState
}

type decoratorNode struct {
	dcor  interface{}
	dtype reflect.Type

	id dot.CtorID

	// Location where this function was defined.
	location *digreflect.Func

	// Current state of this decorator
	state decoratorState

	// Parameters of the decorator.
	params paramList

	// Results of the decorator.
	results resultList

	// Order of this node in each Scopes' graphHolders.
	orders map[*Scope]int

	// Scope this node was originally provided to.
	s *Scope

	// Callback for this decorator, if there is one.
	callback Callback

	// BeforeCallback for this decorator, if there is one
	beforeCallback BeforeCallback
}

func newDecoratorNode( interface{},  *Scope,  decorateOptions) (*decoratorNode, error) {
	 := reflect.ValueOf()
	 := .Type()
	 := .Pointer()

	,  := newParamList(, )
	if  != nil {
		return nil, 
	}

	,  := newResultList(, resultOptions{})
	if  != nil {
		return nil, 
	}

	 := &decoratorNode{
		dcor:           ,
		dtype:          ,
		id:             dot.CtorID(),
		location:       digreflect.InspectFunc(),
		orders:         make(map[*Scope]int),
		params:         ,
		results:        ,
		s:              ,
		callback:       .Callback,
		beforeCallback: .BeforeCallback,
	}
	return , nil
}

func ( *decoratorNode) ( containerStore) ( error) {
	if .state == decoratorCalled {
		return nil
	}

	.state = decoratorOnStack

	if  := shallowCheckDependencies(, .params);  != nil {
		return errMissingDependencies{
			Func:   .location,
			Reason: ,
		}
	}

	,  := .params.BuildList(.s)
	if  != nil {
		return errArgumentsFailed{
			Func:   .location,
			Reason: ,
		}
	}
	if .beforeCallback != nil {
		.beforeCallback(BeforeCallbackInfo{
			Name: fmt.Sprintf("%v.%v", .location.Package, .location.Name),
		})
	}

	if .callback != nil {
		 := .clock().Now()
		// Wrap in separate func to include PanicErrors
		defer func() {
			.callback(CallbackInfo{
				Name:    fmt.Sprintf("%v.%v", .location.Package, .location.Name),
				Error:   ,
				Runtime: .clock().Since(),
			})
		}()
	}

	if .s.recoverFromPanics {
		defer func() {
			if  := recover();  != nil {
				 = PanicError{
					fn:    .location,
					Panic: ,
				}
			}
		}()
	}

	 := .invoker()(reflect.ValueOf(.dcor), )
	if  = .results.ExtractList(.s, true /* decorated */, );  != nil {
		return 
	}
	.state = decoratorCalled
	return nil
}

func ( *decoratorNode) () dot.CtorID { return .id }

func ( *decoratorNode) () decoratorState { return .state }

// DecorateOption modifies the default behavior of Decorate.
type DecorateOption interface {
	apply(*decorateOptions)
}

type decorateOptions struct {
	Info           *DecorateInfo
	Callback       Callback
	BeforeCallback BeforeCallback
}

// FillDecorateInfo is a DecorateOption that writes info on what Dig was
// able to get out of the provided decorator into the provided DecorateInfo.
func ( *DecorateInfo) DecorateOption {
	return fillDecorateInfoOption{info: }
}

type fillDecorateInfoOption struct{ info *DecorateInfo }

func ( fillDecorateInfoOption) () string {
	return fmt.Sprintf("FillDecorateInfo(%p)", .info)
}

func ( fillDecorateInfoOption) ( *decorateOptions) {
	.Info = .info
}

// DecorateInfo provides information about the decorator's inputs and outputs
// types as strings, as well as the ID of the decorator supplied to the Container.
type DecorateInfo struct {
	ID      ID
	Inputs  []*Input
	Outputs []*Output
}

// Decorate provides a decorator for a type that has already been provided in the Container.
// Decorations at this level affect all scopes of the container.
// See Scope.Decorate for information on how to use this method.
func ( *Container) ( interface{},  ...DecorateOption) error {
	return .scope.Decorate(, ...)
}

// Decorate provides a decorator for a type that has already been provided in the Scope.
//
// Similar to Provide, Decorate takes in a function with zero or more dependencies and one
// or more results. Decorate can be used to modify a type that was already introduced to the
// Scope, or completely replace it with a new object.
//
// For example,
//
//	s.Decorate(func(log *zap.Logger) *zap.Logger {
//	  return log.Named("myapp")
//	})
//
// This takes in a value, augments it with a name, and returns a replacement for it. Functions
// in the Scope's dependency graph that use *zap.Logger will now use the *zap.Logger
// returned by this decorator.
//
// A decorator can also take in multiple parameters and replace one of them:
//
//	s.Decorate(func(log *zap.Logger, cfg *Config) *zap.Logger {
//	  return log.Named(cfg.Name)
//	})
//
// Or replace a subset of them:
//
//	s.Decorate(func(
//	  log *zap.Logger,
//	  cfg *Config,
//	  scope metrics.Scope
//	) (*zap.Logger, metrics.Scope) {
//	  log = log.Named(cfg.Name)
//	  scope = scope.With(metrics.Tag("service", cfg.Name))
//	  return log, scope
//	})
//
// Decorating a Scope affects all the child scopes of this Scope.
//
// Similar to a provider, the decorator function gets called *at most once*.
func ( *Scope) ( interface{},  ...DecorateOption) error {
	var  decorateOptions
	for ,  := range  {
		.apply(&)
	}

	,  := newDecoratorNode(, , )
	if  != nil {
		return 
	}

	,  := findResultKeys(.results)
	if  != nil {
		return 
	}
	for ,  := range  {
		if ,  := .decorators[];  {
			return newErrInvalidInput(
				fmt.Sprintf("cannot decorate using function %v: %s already decorated", .dtype, ), nil)
		}
		.decorators[] = 
	}

	if  := .Info;  != nil {
		 := .params.DotParam()
		 := .results.DotResult()
		.ID = (ID)(.id)
		.Inputs = make([]*Input, len())
		.Outputs = make([]*Output, len())

		for ,  := range  {
			.Inputs[] = &Input{
				t:        .Type,
				optional: .Optional,
				name:     .Name,
				group:    .Group,
			}
		}
		for ,  := range  {
			.Outputs[] = &Output{
				t:     .Type,
				name:  .Name,
				group: .Group,
			}
		}
	}
	return nil
}

func findResultKeys( resultList) ([]key, error) {
	// use BFS to search for all keys included in a resultList.
	var (
		    []result
		 []key
	)
	 = append(, )

	for len() > 0 {
		 := [0]
		 = [1:]

		switch innerResult := .(type) {
		case resultSingle:
			 = append(, key{t: .Type, name: .Name})
		case resultGrouped:
			if .Type.Kind() != reflect.Slice {
				return nil, newErrInvalidInput("decorating a value group requires decorating the entire value group, not a single value", nil)
			}
			 = append(, key{t: .Type.Elem(), group: .Group})
		case resultObject:
			for ,  := range .Fields {
				 = append(, .Result)
			}
		case resultList:
			 = append(, .Results...)
		}
	}
	return , nil
}