// Copyright (c) 2021 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 (
	
	

	
	
	
)

// constructorNode is a node in the dependency graph that represents
// a constructor provided by the user.
//
// constructorNodes can produce zero or more values that they store into the container.
// For the Provide path, we verify that constructorNodes produce at least one value,
// otherwise the function will never be called.
type constructorNode struct {
	ctor  interface{}
	ctype reflect.Type

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

	// id uniquely identifies the constructor that produces a node.
	id dot.CtorID

	// Whether the constructor owned by this node was already called.
	called bool

	// Type information about constructor parameters.
	paramList paramList

	// Type information about constructor results.
	resultList resultList

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

	// Scope this node is part of.
	s *Scope

	// Scope this node was originally provided to.
	// This is different from s if and only if the constructor was Provided with ExportOption.
	origS *Scope

	// Callback for this provided function, if there is one.
	callback Callback

	// BeforeCallback for this provided function, if there is one.
	beforeCallback BeforeCallback
}

type constructorOptions struct {
	// If specified, all values produced by this constructor have the provided name
	// belong to the specified value group or implement any of the interfaces.
	ResultName     string
	ResultGroup    string
	ResultAs       []interface{}
	Location       *digreflect.Func
	Callback       Callback
	BeforeCallback BeforeCallback
}

func newConstructorNode( interface{},  *Scope,  *Scope,  constructorOptions) (*constructorNode, error) {
	 := reflect.ValueOf()
	 := .Type()
	 := .Pointer()

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

	,  := newResultList(
		,
		resultOptions{
			Name:  .ResultName,
			Group: .ResultGroup,
			As:    .ResultAs,
		},
	)
	if  != nil {
		return nil, 
	}

	 := .Location
	if  == nil {
		 = digreflect.InspectFunc()
	}

	 := &constructorNode{
		ctor:           ,
		ctype:          ,
		location:       ,
		id:             dot.CtorID(),
		paramList:      ,
		resultList:     ,
		orders:         make(map[*Scope]int),
		s:              ,
		origS:          ,
		callback:       .Callback,
		beforeCallback: .BeforeCallback,
	}
	.newGraphNode(, .orders)
	return , nil
}

func ( *constructorNode) () *digreflect.Func { return .location }
func ( *constructorNode) () paramList       { return .paramList }
func ( *constructorNode) () resultList     { return .resultList }
func ( *constructorNode) () dot.CtorID             { return .id }
func ( *constructorNode) () reflect.Type        { return .ctype }
func ( *constructorNode) ( *Scope) int         { return .orders[] }
func ( *constructorNode) () *Scope          { return .origS }

// CopyOrder copies the order for the given parent scope to the given child scope.
func ( *constructorNode) (,  *Scope) {
	.orders[] = .orders[]
}

func ( *constructorNode) () string {
	return fmt.Sprintf("deps: %v, ctor: %v", .paramList, .ctype)
}

// Call calls this constructor if it hasn't already been called and
// injects any values produced by it into the provided container.
func ( *constructorNode) ( containerStore) ( error) {
	if .called {
		return nil
	}

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

	,  := .paramList.BuildList()
	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: ,
				}
			}
		}()
	}

	 := newStagingContainerWriter()
	 := .invoker()(reflect.ValueOf(.ctor), )
	if  = .resultList.ExtractList(, false /* decorating */, );  != nil {
		return errConstructorFailed{Func: .location, Reason: }
	}

	// Commit the result to the original container that this constructor
	// was supplied to. The provided constructor is only used for a view of
	// the rest of the graph to instantiate the dependencies of this
	// container.
	.Commit(.s)
	.called = true
	return nil
}

// stagingContainerWriter is a containerWriter that records the changes that
// would be made to a containerWriter and defers them until Commit is called.
type stagingContainerWriter struct {
	values map[key]reflect.Value
	groups map[key][]reflect.Value
}

var _ containerWriter = (*stagingContainerWriter)(nil)

func newStagingContainerWriter() *stagingContainerWriter {
	return &stagingContainerWriter{
		values: make(map[key]reflect.Value),
		groups: make(map[key][]reflect.Value),
	}
}

func ( *stagingContainerWriter) ( string,  reflect.Type,  reflect.Value) {
	.values[key{t: , name: }] = 
}

func ( *stagingContainerWriter) ( string,  reflect.Type,  reflect.Value) {
	digerror.BugPanicf("stagingContainerWriter.setDecoratedValue must never be called")
}

func ( *stagingContainerWriter) ( string,  reflect.Type,  reflect.Value) {
	 := key{t: , group: }
	.groups[] = append(.groups[], )
}

func ( *stagingContainerWriter) ( string,  reflect.Type,  reflect.Value) {
	digerror.BugPanicf("stagingContainerWriter.submitDecoratedGroupedValue must never be called")
}

// Commit commits the received results to the provided containerWriter.
func ( *stagingContainerWriter) ( containerWriter) {
	for ,  := range .values {
		.setValue(.name, .t, )
	}

	for ,  := range .groups {
		for ,  := range  {
			.submitGroupedValue(.group, .t, )
		}
	}
}