// 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 (
	
	
	
	

	
)

// A VisualizeOption modifies the default behavior of Visualize.
type VisualizeOption interface {
	applyVisualizeOption(*visualizeOptions)
}

type visualizeOptions struct {
	VisualizeError error
}

// VisualizeError includes a visualization of the given error in the output of
// Visualize if an error was returned by Invoke or Provide.
//
//	if err := c.Provide(...); err != nil {
//	  dig.Visualize(c, w, dig.VisualizeError(err))
//	}
//
// This option has no effect if the error was nil or if it didn't contain any
// information to visualize.
func ( error) VisualizeOption {
	return visualizeErrorOption{}
}

type visualizeErrorOption struct{ err error }

func ( visualizeErrorOption) () string {
	return fmt.Sprintf("VisualizeError(%v)", .err)
}

func ( visualizeErrorOption) ( *visualizeOptions) {
	.VisualizeError = .err
}

func updateGraph( *dot.Graph,  error) error {
	var  []errVisualizer
	// Unwrap error to find the root cause.
	for {
		if ,  := .(errVisualizer);  {
			 = append(, )
		}
		 := errors.Unwrap()
		if  == nil {
			break
		}
		 = 
	}

	// If there are no errVisualizers included, we do not modify the graph.
	if len() == 0 {
		return nil
	}

	// We iterate in reverse because the last element is the root cause.
	for  := len() - 1;  >= 0; -- {
		[].updateGraph()
	}

	// Remove non-error entries from the graph for readability.
	.PruneSuccess()

	return nil
}

// Visualize parses the graph in Container c into DOT format and writes it to
// io.Writer w.
func ( *Container,  io.Writer,  ...VisualizeOption) error {
	 := .createGraph()

	var  visualizeOptions
	for ,  := range  {
		.applyVisualizeOption(&)
	}

	if .VisualizeError != nil {
		if  := updateGraph(, .VisualizeError);  != nil {
			return 
		}
	}

	visualizeGraph(, )
	return nil
}

func visualizeGraph( io.Writer,  *dot.Graph) {
	.Write([]byte("digraph {\n\trankdir=RL;\n\tgraph [compound=true];\n"))
	for ,  := range .Groups {
		visualizeGroup(, )
	}
	for ,  := range .Ctors {
		visualizeCtor(, , )
	}
	for ,  := range .Failed.TransitiveFailures {
		fmt.Fprintf(, "\t%s [color=orange];\n", strconv.Quote(.String()))
	}
	for ,  := range .Failed.RootCauses {
		fmt.Fprintf(, "\t%s [color=red];\n", strconv.Quote(.String()))
	}
	.Write([]byte("}"))
}

func visualizeGroup( io.Writer,  *dot.Group) {
	fmt.Fprintf(, "\t%s [%s];\n", strconv.Quote(.String()), .Attributes())
	for ,  := range .Results {
		fmt.Fprintf(, "\t%s -> %s;\n", strconv.Quote(.String()), strconv.Quote(.String()))
	}
}

func visualizeCtor( io.Writer,  int,  *dot.Ctor) {
	fmt.Fprintf(, "\tsubgraph cluster_%d {\n", )
	if .Package != "" {
		fmt.Fprintf(, "\t\tlabel = %s;\n", strconv.Quote(.Package))
	}
	fmt.Fprintf(, "\t\tconstructor_%d [shape=plaintext label=%s];\n", , strconv.Quote(.Name))

	if .ErrorType != 0 {
		fmt.Fprintf(, "\t\tcolor=%s;\n", .ErrorType.Color())
	}
	for ,  := range .Results {
		fmt.Fprintf(, "\t\t%s [%s];\n", strconv.Quote(.String()), .Attributes())
	}
	fmt.Fprintf(, "\t}\n")
	for ,  := range .Params {
		var  string
		if .Optional {
			 = " style=dashed"
		}

		fmt.Fprintf(, "\tconstructor_%d -> %s [ltail=cluster_%d%s];\n", , strconv.Quote(.String()), , )
	}
	for ,  := range .GroupParams {
		fmt.Fprintf(, "\tconstructor_%d -> %s [ltail=cluster_%d];\n", , strconv.Quote(.String()), )
	}
}

// CanVisualizeError returns true if the error is an errVisualizer.
func ( error) bool {
	for {
		if ,  := .(errVisualizer);  {
			return true
		}
		 := errors.Unwrap()
		if  == nil {
			break
		}
		 = 
	}

	return false
}

func ( *Container) () *dot.Graph {
	return .scope.createGraph()
}

func ( *Scope) () *dot.Graph {
	 := dot.NewGraph()

	.addNodes()

	return 
}

func ( *Scope) ( *dot.Graph) {
	for ,  := range .nodes {
		.AddCtor(newDotCtor(), .paramList.DotParam(), .resultList.DotResult())
	}

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

func newDotCtor( *constructorNode) *dot.Ctor {
	return &dot.Ctor{
		ID:      .id,
		Name:    .location.Name,
		Package: .location.Package,
		File:    .location.File,
		Line:    .location.Line,
	}
}