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

import (
	
	
)

// ErrorType of a constructor or group is updated when they fail to build.
type ErrorType int

const (
	noError ErrorType = iota
	rootCause
	transitiveFailure
)

// CtorID is a unique numeric identifier for constructors.
type CtorID uintptr

// Ctor encodes a constructor provided to the container for the DOT graph.
type Ctor struct {
	Name        string
	Package     string
	File        string
	Line        int
	ID          CtorID
	Params      []*Param
	GroupParams []*Group
	Results     []*Result
	ErrorType   ErrorType
}

// removeParam deletes the dependency on the provided result's nodeKey.
// This is used to prune links to results of deleted constructors.
func ( *Ctor) ( nodeKey) {
	var  []*Param
	for ,  := range .Params {
		if  != .nodeKey() {
			 = append(, )
		}
	}
	.Params = 
}

type nodeKey struct {
	t     reflect.Type
	name  string
	group string
}

// Node is a single node in a graph and is embedded into Params and Results.
type Node struct {
	Type  reflect.Type
	Name  string
	Group string
}

func ( *Node) () nodeKey {
	return nodeKey{t: .Type, name: .Name, group: .Group}
}

// Param is a parameter node in the graph. Parameters are the input to constructors.
type Param struct {
	*Node

	Optional bool
}

// Result is a result node in the graph. Results are the output of constructors.
type Result struct {
	*Node

	// GroupIndex is added to differentiate grouped values from one another.
	// Since grouped values have the same type and group, their Node / string
	// representations are the same so we need indices to uniquely identify
	// the values.
	GroupIndex int
}

// Group is a group node in the graph. Group represents an fx value group.
type Group struct {
	// Type is the type of values in the group.
	Type      reflect.Type
	Name      string
	Results   []*Result
	ErrorType ErrorType
}

func ( *Group) () nodeKey {
	return nodeKey{t: .Type, group: .Name}
}

// TODO(rhang): Avoid linear search to discover group results that should be pruned.
func ( *Group) ( *Result) {
	var  []*Result
	for ,  := range .Results {
		if .GroupIndex != .GroupIndex {
			 = append(, )
		}
	}
	.Results = 
}

// Graph is the DOT-format graph in a Container.
type Graph struct {
	Ctors   []*Ctor
	ctorMap map[CtorID]*Ctor

	Groups   []*Group
	groupMap map[nodeKey]*Group

	consumers map[nodeKey][]*Ctor

	Failed *FailedNodes
}

// FailedNodes is the nodes that failed in the graph.
type FailedNodes struct {
	// RootCauses is a list of the point of failures. They are the root causes
	// of failed invokes and can be either missing types (not provided) or
	// error types (error providing).
	RootCauses []*Result

	// TransitiveFailures is the list of nodes that failed to build due to
	// missing/failed dependencies.
	TransitiveFailures []*Result

	// ctors is a collection of failed constructors IDs that are populated as the graph is
	// traversed for errors.
	ctors map[CtorID]struct{}

	// Groups is a collection of failed groupKeys that is populated as the graph is traversed
	// for errors.
	groups map[nodeKey]struct{}
}

// NewGraph creates an empty graph.
func () *Graph {
	return &Graph{
		ctorMap:   make(map[CtorID]*Ctor),
		groupMap:  make(map[nodeKey]*Group),
		consumers: make(map[nodeKey][]*Ctor),
		Failed: &FailedNodes{
			ctors:  make(map[CtorID]struct{}),
			groups: make(map[nodeKey]struct{}),
		},
	}
}

// NewGroup creates a new group with information in the groupKey.
func ( nodeKey) *Group {
	return &Group{
		Type: .t,
		Name: .group,
	}
}

// AddCtor adds the constructor with paramList and resultList into the graph.
func ( *Graph) ( *Ctor,  []*Param,  []*Result) {
	var (
		      []*Param
		 []*Group
	)

	// Loop through the paramList to separate them into regular params and
	// grouped params. For grouped params, we use getGroup to find the actual
	// group.
	for ,  := range  {
		if .Group == "" {
			// Not a value group.
			 = append(, )
			continue
		}

		 := nodeKey{t: .Type.Elem(), group: .Group}
		 := .getGroup()
		 = append(, )
	}

	for ,  := range  {
		// If the result is a grouped value, we want to update its GroupIndex
		// and add it to the Group.
		if .Group != "" {
			.addToGroup(, .ID)
		}
	}

	.Params = 
	.GroupParams = 
	.Results = 

	// Track which constructors consume a parameter.
	for ,  := range  {
		 := .nodeKey()
		.consumers[] = append(.consumers[], )
	}

	.Ctors = append(.Ctors, )
	.ctorMap[.ID] = 
}

func ( *Graph) ( *Result,  bool) {
	if  {
		.addRootCause()
	} else {
		.addTransitiveFailure()
	}
}

// AddMissingNodes adds missing nodes to the list of failed Results in the graph.
func ( *Graph) ( []*Result) {
	// The failure(s) are root causes if there are no other failures.
	 := len(.Failed.RootCauses) == 0

	for ,  := range  {
		.failNode(, )
	}
}

// FailNodes adds results to the list of failed Results in the graph, and
// updates the state of the constructor with the given id accordingly.
func ( *Graph) ( []*Result,  CtorID) {
	// This failure is the root cause if there are no other failures.
	 := len(.Failed.RootCauses) == 0
	.Failed.ctors[] = struct{}{}

	for ,  := range  {
		.failNode(, )
	}

	if ,  := .ctorMap[];  {
		if  {
			.ErrorType = rootCause
		} else {
			.ErrorType = transitiveFailure
		}
	}
}

// FailGroupNodes finds and adds the failed grouped nodes to the list of failed
// Results in the graph, and updates the state of the group and constructor
// with the given id accordingly.
func ( *Graph) ( string,  reflect.Type,  CtorID) {
	// This failure is the root cause if there are no other failures.
	 := len(.Failed.RootCauses) == 0

	 := nodeKey{t: , group: }
	 := .getGroup()

	// If the ctor does not exist it cannot be failed.
	if ,  := .ctorMap[]; ! {
		return
	}

	// Track which constructors and groups have failed.
	.Failed.ctors[] = struct{}{}
	.Failed.groups[] = struct{}{}

	for ,  := range .ctorMap[].Results {
		if .Type ==  && .Group ==  {
			.failNode(, )
		}
	}

	if ,  := .ctorMap[];  {
		if  {
			.ErrorType = rootCause
			.ErrorType = rootCause
		} else {
			.ErrorType = transitiveFailure
			.ErrorType = transitiveFailure
		}
	}
}

// getGroup finds the group by nodeKey from the graph. If it is not available,
// a new group is created and returned.
func ( *Graph) ( nodeKey) *Group {
	,  := .groupMap[]
	if ! {
		 = NewGroup()
		.groupMap[] = 
		.Groups = append(.Groups, )
	}
	return 
}

// addToGroup adds a newly provided grouped result to the appropriate group.
func ( *Graph) ( *Result,  CtorID) {
	 := nodeKey{t: .Type, group: .Group}
	 := .getGroup()

	.GroupIndex = len(.Results)
	.Results = append(.Results, )
}

// PruneSuccess removes elements from the graph that do not have failed results.
// Removing elements that do not have failing results makes the graph easier to debug,
// since non-failing nodes and edges can clutter the graph and don't help the user debug.
func ( *Graph) () {
	.pruneCtors(.Failed.ctors)
	.pruneGroups(.Failed.groups)
}

// pruneCtors removes constructors from the graph that do not have failing Results.
func ( *Graph) ( map[CtorID]struct{}) {
	var  []*Ctor
	for ,  := range .Ctors {
		if ,  := [.ID];  {
			 = append(, )
			continue
		}
		// If a constructor is deleted, the constructor's stale result references need to
		// be removed from that result's Group and/or consuming constructor.
		.pruneCtorParams(, .consumers)
		.pruneGroupResults(, .groupMap)
		delete(.ctorMap, .ID)
	}

	.Ctors = 
}

// pruneGroups removes groups from the graph that do not have failing results.
func ( *Graph) ( map[nodeKey]struct{}) {
	var  []*Group
	for ,  := range .Groups {
		 := .nodeKey()
		if ,  := [];  {
			 = append(, )
			continue
		}
		delete(.groupMap, )
	}
	.Groups = 

	.pruneCtorGroupParams(.groupMap)
}

// pruneCtorParams removes results of the constructor argument that are still referenced in the
// Params of constructors that consume those results. If the results in the constructor are found
// in the params of a consuming constructor that result should be removed.
func ( *Graph) ( *Ctor,  map[nodeKey][]*Ctor) {
	for ,  := range .Results {
		for ,  := range [.nodeKey()] {
			.removeParam(.nodeKey())
		}
	}
}

// pruneCtorGroupParams removes constructor results that are still referenced in the GroupParams of
// constructors that consume those results.
func ( *Graph) ( map[nodeKey]*Group) {
	for ,  := range .Ctors {
		var  []*Group
		for ,  := range .GroupParams {
			 := .nodeKey()
			if ,  := [];  {
				 = append(, )
			}
		}
		.GroupParams = 
	}
}

// pruneGroupResults removes results of the constructor argument that are still referenced in
// the Group object that contains that result. If a group no longer exists references to that
// should should be removed.
func ( *Graph) ( *Ctor,  map[nodeKey]*Group) {
	for ,  := range .Results {
		 := .nodeKey()
		if .group == "" {
			continue
		}

		,  := []
		if  {
			.removeResult()
		}
	}
}

// String implements fmt.Stringer for Param.
func ( *Param) () string {
	if .Name != "" {
		return fmt.Sprintf("%v[name=%v]", .Type.String(), .Name)
	}
	return .Type.String()
}

// String implements fmt.Stringer for Result.
func ( *Result) () string {
	switch {
	case .Name != "":
		return fmt.Sprintf("%v[name=%v]", .Type.String(), .Name)
	case .Group != "":
		return fmt.Sprintf("%v[group=%v]%v", .Type.String(), .Group, .GroupIndex)
	default:
		return .Type.String()
	}
}

// String implements fmt.Stringer for Group.
func ( *Group) () string {
	return fmt.Sprintf("[type=%v group=%v]", .Type.String(), .Name)
}

// Attributes composes and returns a string of the Result node's attributes.
func ( *Result) () string {
	switch {
	case .Name != "":
		return fmt.Sprintf(`label=<%v<BR /><FONT POINT-SIZE="10">Name: %v</FONT>>`, .Type, .Name)
	case .Group != "":
		return fmt.Sprintf(`label=<%v<BR /><FONT POINT-SIZE="10">Group: %v</FONT>>`, .Type, .Group)
	default:
		return fmt.Sprintf(`label=<%v>`, .Type)
	}
}

// Attributes composes and returns a string of the Group node's attributes.
func ( *Group) () string {
	 := fmt.Sprintf(`shape=diamond label=<%v<BR /><FONT POINT-SIZE="10">Group: %v</FONT>>`, .Type, .Name)
	if .ErrorType != noError {
		 += " color=" + .ErrorType.Color()
	}
	return 
}

// Color returns the color representation of each ErrorType.
func ( ErrorType) () string {
	switch  {
	case rootCause:
		return "red"
	case transitiveFailure:
		return "orange"
	default:
		return "black"
	}
}

func ( *Graph) ( *Result) {
	.Failed.RootCauses = append(.Failed.RootCauses, )
}

func ( *Graph) ( *Result) {
	.Failed.TransitiveFailures = append(.Failed.TransitiveFailures, )
}