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

	
	
)

// The result interface represents a result produced by a constructor.
//
// The following implementations exist:
//   resultList    All values returned by the constructor.
//   resultSingle  A single value produced by a constructor.
//   resultObject  dig.Out struct where each field in the struct can be
//                 another result.
//   resultGrouped A value produced by a constructor that is part of a value
//                 group.

type result interface {
	// Extracts the values for this result from the provided value and
	// stores them into the provided containerWriter.
	//
	// This MAY panic if the result does not consume a single value.
	Extract(containerWriter, bool, reflect.Value)

	// DotResult returns a slice of dot.Result(s).
	DotResult() []*dot.Result
}

var (
	_ result = resultSingle{}
	_ result = resultObject{}
	_ result = resultList{}
	_ result = resultGrouped{}
)

type resultOptions struct {
	// If set, this is the name of the associated result value.
	//
	// For Result Objects, name:".." tags on fields override this.
	Name  string
	Group string
	As    []interface{}
}

// newResult builds a result from the given type.
func newResult( reflect.Type,  resultOptions) (result, error) {
	switch {
	case IsIn() || (.Kind() == reflect.Ptr && IsIn(.Elem())) || embedsType(, _inPtrType):
		return nil, newErrInvalidInput(fmt.Sprintf(
			"cannot provide parameter objects: %v embeds a dig.In", ), nil)
	case isError():
		return nil, newErrInvalidInput("cannot return an error here, return it from the constructor instead", nil)
	case IsOut():
		return newResultObject(, )
	case embedsType(, _outPtrType):
		return nil, newErrInvalidInput(fmt.Sprintf(
			"cannot build a result object by embedding *dig.Out, embed dig.Out instead: %v embeds *dig.Out", ), nil)
	case .Kind() == reflect.Ptr && IsOut(.Elem()):
		return nil, newErrInvalidInput(fmt.Sprintf(
			"cannot return a pointer to a result object, use a value instead: %v is a pointer to a struct that embeds dig.Out", ), nil)
	case len(.Group) > 0:
		,  := parseGroupString(.Group)
		if  != nil {
			return nil, newErrInvalidInput(
				fmt.Sprintf("cannot parse group %q", .Group), )
		}
		 := resultGrouped{Type: , Group: .Name, Flatten: .Flatten}
		if len(.As) > 0 {
			var  []reflect.Type
			for ,  := range .As {
				 := reflect.TypeOf().Elem()
				if  ==  {
					continue
				}
				if !.Implements() {
					return nil, newErrInvalidInput(
						fmt.Sprintf("invalid dig.As: %v does not implement %v", , ), nil)
				}
				 = append(, )
			}
			if len() > 0 {
				.Type = [0]
				.As = [1:]
			}
		}
		if .Soft {
			return nil, newErrInvalidInput(fmt.Sprintf(
				"cannot use soft with result value groups: soft was used with group:%q", .Name), nil)
		}
		if .Flatten {
			if .Kind() != reflect.Slice {
				return nil, newErrInvalidInput(fmt.Sprintf(
					"flatten can be applied to slices only: %v is not a slice", ), nil)
			}
			.Type = .Type.Elem()
		}
		return , nil
	default:
		return newResultSingle(, )
	}
}

// resultVisitor visits every result in a result tree, allowing tracking state
// at each level.
type resultVisitor interface {
	// Visit is called on the result being visited.
	//
	// If Visit returns a non-nil resultVisitor, that resultVisitor visits all
	// the child results of this result.
	Visit(result) resultVisitor

	// AnnotateWithField is called on each field of a resultObject after
	// visiting it but before walking its descendants.
	//
	// The same resultVisitor is used for all fields: the one returned upon
	// visiting the resultObject.
	//
	// For each visited field, if AnnotateWithField returns a non-nil
	// resultVisitor, it will be used to walk the result of that field.
	AnnotateWithField(resultObjectField) resultVisitor

	// AnnotateWithPosition is called with the index of each result of a
	// resultList after vising it but before walking its descendants.
	//
	// The same resultVisitor is used for all results: the one returned upon
	// visiting the resultList.
	//
	// For each position, if AnnotateWithPosition returns a non-nil
	// resultVisitor, it will be used to walk the result at that index.
	AnnotateWithPosition(idx int) resultVisitor
}

// walkResult walks the result tree for the given result with the provided
// visitor.
//
// resultVisitor.Visit will be called on the provided result and if a non-nil
// resultVisitor is received, it will be used to walk its descendants. If a
// resultObject or resultList was visited, AnnotateWithField and
// AnnotateWithPosition respectively will be called before visiting the
// descendants of that resultObject/resultList.
//
// This is very similar to how go/ast.Walk works.
func walkResult( result,  resultVisitor) {
	 = .Visit()
	if  == nil {
		return
	}

	switch res := .(type) {
	case resultSingle, resultGrouped:
		// No sub-results
	case resultObject:
		 := 
		for ,  := range .Fields {
			if  := .AnnotateWithField();  != nil {
				(.Result, )
			}
		}
	case resultList:
		 := 
		for ,  := range .Results {
			if  := .AnnotateWithPosition();  != nil {
				(, )
			}
		}
	default:
		digerror.BugPanicf("received unknown result type %T", )
	}
}

// resultList holds all values returned by the constructor as results.
type resultList struct {
	ctype reflect.Type

	Results []result

	// For each item at index i returned by the constructor, resultIndexes[i]
	// is the index in .Results for the corresponding result object.
	// resultIndexes[i] is -1 for errors returned by constructors.
	resultIndexes []int
}

func ( resultList) () []*dot.Result {
	var  []*dot.Result
	for ,  := range .Results {
		 = append(, .DotResult()...)
	}
	return 
}

func newResultList( reflect.Type,  resultOptions) (resultList, error) {
	 := .NumOut()
	 := resultList{
		ctype:         ,
		Results:       make([]result, 0, ),
		resultIndexes: make([]int, ),
	}

	 := 0
	for  := 0;  < ; ++ {
		 := .Out()
		if isError() {
			.resultIndexes[] = -1
			continue
		}

		,  := newResult(, )
		if  != nil {
			return , newErrInvalidInput(fmt.Sprintf("bad result %d", +1), )
		}

		.Results = append(.Results, )
		.resultIndexes[] = 
		++
	}

	return , nil
}

func (resultList) (containerWriter, bool, reflect.Value) {
	digerror.BugPanicf("resultList.Extract() must never be called")
}

func ( resultList) ( containerWriter,  bool,  []reflect.Value) error {
	for ,  := range  {
		if  := .resultIndexes[];  >= 0 {
			.Results[].Extract(, , )
			continue
		}

		if ,  := .Interface().(error);  != nil {
			return 
		}
	}

	return nil
}

// resultSingle is an explicit value produced by a constructor, optionally
// with a name.
//
// This object will be added to the graph as-is.
type resultSingle struct {
	Name string
	Type reflect.Type

	// If specified, this is a list of types which the value will be made
	// available as, in addition to its own type.
	As []reflect.Type
}

func newResultSingle( reflect.Type,  resultOptions) (resultSingle, error) {
	 := resultSingle{
		Type: ,
		Name: .Name,
	}

	var  []reflect.Type

	for ,  := range .As {
		 := reflect.TypeOf().Elem()
		if  ==  {
			// Special case:
			//   c.Provide(func() io.Reader, As(new(io.Reader)))
			// Ignore instead of erroring out.
			continue
		}
		if !.Implements() {
			return , newErrInvalidInput(
				fmt.Sprintf("invalid dig.As: %v does not implement %v", , ), nil)
		}
		 = append(, )
	}

	if len() == 0 {
		return , nil
	}

	return resultSingle{
		Type: [0],
		Name: .Name,
		As:   [1:],
	}, nil
}

func ( resultSingle) () []*dot.Result {
	 := make([]*dot.Result, 0, len(.As)+1)
	 = append(, &dot.Result{
		Node: &dot.Node{
			Type: .Type,
			Name: .Name,
		},
	})

	for ,  := range .As {
		 = append(, &dot.Result{
			Node: &dot.Node{Type: , Name: .Name},
		})
	}

	return 
}

func ( resultSingle) ( containerWriter,  bool,  reflect.Value) {
	if  {
		.setDecoratedValue(.Name, .Type, )
		return
	}
	.setValue(.Name, .Type, )

	for ,  := range .As {
		.setValue(.Name, , )
	}
}

// resultObject is a dig.Out struct where each field is another result.
//
// This object is not added to the graph. Its fields are interpreted as
// results and added to the graph if needed.
type resultObject struct {
	Type   reflect.Type
	Fields []resultObjectField
}

func ( resultObject) () []*dot.Result {
	var  []*dot.Result
	for ,  := range .Fields {
		 = append(, .DotResult()...)
	}
	return 
}

func newResultObject( reflect.Type,  resultOptions) (resultObject, error) {
	 := resultObject{Type: }
	if len(.Name) > 0 {
		return , newErrInvalidInput(fmt.Sprintf(
			"cannot specify a name for result objects: %v embeds dig.Out", ), nil)
	}

	if len(.Group) > 0 {
		return , newErrInvalidInput(fmt.Sprintf(
			"cannot specify a group for result objects: %v embeds dig.Out", ), nil)
	}

	for  := 0;  < .NumField(); ++ {
		 := .Field()
		if .Type == _outType {
			// Skip over the dig.Out embed.
			continue
		}

		,  := newResultObjectField(, , )
		if  != nil {
			return , newErrInvalidInput(fmt.Sprintf("bad field %q of %v", .Name, ), )
		}

		.Fields = append(.Fields, )
	}
	return , nil
}

func ( resultObject) ( containerWriter,  bool,  reflect.Value) {
	for ,  := range .Fields {
		.Result.Extract(, , .Field(.FieldIndex))
	}
}

// resultObjectField is a single field inside a dig.Out struct.
type resultObjectField struct {
	// Name of the field in the struct.
	FieldName string

	// Index of the field in the struct.
	//
	// We need to track this separately because not all fields of the struct
	// map to results.
	FieldIndex int

	// Result produced by this field.
	Result result
}

func ( resultObjectField) () []*dot.Result {
	return .Result.DotResult()
}

// newResultObjectField(i, f, opts) builds a resultObjectField from the field
// f at index i.
func newResultObjectField( int,  reflect.StructField,  resultOptions) (resultObjectField, error) {
	 := resultObjectField{
		FieldName:  .Name,
		FieldIndex: ,
	}

	var  result
	switch {
	case .PkgPath != "":
		return , newErrInvalidInput(
			fmt.Sprintf("unexported fields not allowed in dig.Out, did you mean to export %q (%v)?", .Name, .Type), nil)

	case .Tag.Get(_groupTag) != "":
		var  error
		,  = newResultGrouped()
		if  != nil {
			return , 
		}

	default:
		var  error
		if  := .Tag.Get(_nameTag); len() > 0 {
			// can modify in-place because options are passed-by-value.
			.Name = 
		}
		,  = newResult(.Type, )
		if  != nil {
			return , 
		}
	}

	.Result = 
	return , nil
}

// resultGrouped is a value produced by a constructor that is part of a result
// group.
//
// These will be produced as fields of a dig.Out struct.
type resultGrouped struct {
	// Name of the group as specified in the `group:".."` tag.
	Group string

	// Type of value produced.
	Type reflect.Type

	// Indicates elements of a value are to be injected individually, instead of
	// as a group. Requires the value's slice to be a group. If set, Type will be
	// the type of individual elements rather than the group.
	Flatten bool

	// If specified, this is a list of types which the value will be made
	// available as, in addition to its own type.
	As []reflect.Type
}

func ( resultGrouped) () []*dot.Result {
	 := make([]*dot.Result, 0, len(.As)+1)
	 = append(, &dot.Result{
		Node: &dot.Node{
			Type:  .Type,
			Group: .Group,
		},
	})

	for ,  := range .As {
		 = append(, &dot.Result{
			Node: &dot.Node{Type: , Group: .Group},
		})
	}
	return 
}

// newResultGrouped(f) builds a new resultGrouped from the provided field.
func newResultGrouped( reflect.StructField) (resultGrouped, error) {
	,  := parseGroupString(.Tag.Get(_groupTag))
	if  != nil {
		return resultGrouped{}, 
	}
	 := resultGrouped{
		Group:   .Name,
		Flatten: .Flatten,
		Type:    .Type,
	}
	 := .Tag.Get(_nameTag)
	,  := isFieldOptional()
	switch {
	case .Flatten && .Type.Kind() != reflect.Slice:
		return , newErrInvalidInput(fmt.Sprintf(
			"flatten can be applied to slices only: field %q (%v) is not a slice", .Name, .Type), nil)
	case .Soft:
		return , newErrInvalidInput(fmt.Sprintf(
			"cannot use soft with result value groups: soft was used with group %q", .Group), nil)
	case  != "":
		return , newErrInvalidInput(fmt.Sprintf(
			"cannot use named values with value groups: name:%q provided with group:%q", , .Group), nil)
	case :
		return , newErrInvalidInput("value groups cannot be optional", nil)
	}
	if .Flatten {
		.Type = .Type.Elem()
	}

	return , nil
}

func ( resultGrouped) ( containerWriter,  bool,  reflect.Value) {
	// Decorated values are always flattened.
	if ! && !.Flatten {
		.submitGroupedValue(.Group, .Type, )
		for ,  := range .As {
			.submitGroupedValue(.Group, , )
		}
		return
	}

	if  {
		.submitDecoratedGroupedValue(.Group, .Type, )
		return
	}
	for  := 0;  < .Len(); ++ {
		.submitGroupedValue(.Group, .Type, .Index())
	}
}