package terraform

import (
	"fmt"

	"github.com/hashicorp/terraform/config"
	"github.com/hashicorp/terraform/dag"
)

// DisableProviderTransformer "disables" any providers that are only
// depended on by modules.
//
// NOTE: "old" = used by old graph builders, will be removed one day
type DisableProviderTransformerOld struct{}

func (t *DisableProviderTransformerOld) Transform(g *Graph) error {
	// Since we're comparing against edges, we need to make sure we connect
	g.ConnectDependents()

	for _, v := range g.Vertices() {
		// We only care about providers
		pn, ok := v.(GraphNodeProvider)
		if !ok || pn.ProviderName() == "" {
			continue
		}

		// Go through all the up-edges (things that depend on this
		// provider) and if any is not a module, then ignore this node.
		nonModule := false
		for _, sourceRaw := range g.UpEdges(v).List() {
			source := sourceRaw.(dag.Vertex)
			cn, ok := source.(graphNodeConfig)
			if !ok {
				nonModule = true
				break
			}

			if cn.ConfigType() != GraphNodeConfigTypeModule {
				nonModule = true
				break
			}
		}
		if nonModule {
			// We found something that depends on this provider that
			// isn't a module, so skip it.
			continue
		}

		// Disable the provider by replacing it with a "disabled" provider
		disabled := &graphNodeDisabledProvider{GraphNodeProvider: pn}
		if !g.Replace(v, disabled) {
			panic(fmt.Sprintf(
				"vertex disappeared from under us: %s",
				dag.VertexName(v)))
		}
	}

	return nil
}

type graphNodeDisabledProvider struct {
	GraphNodeProvider
}

// GraphNodeEvalable impl.
func (n *graphNodeDisabledProvider) EvalTree() EvalNode {
	var resourceConfig *ResourceConfig

	return &EvalOpFilter{
		Ops: []walkOperation{walkInput, walkValidate, walkRefresh, walkPlan, walkApply, walkDestroy},
		Node: &EvalSequence{
			Nodes: []EvalNode{
				&EvalInterpolate{
					Config: n.ProviderConfig(),
					Output: &resourceConfig,
				},
				&EvalBuildProviderConfig{
					Provider: n.ProviderName(),
					Config:   &resourceConfig,
					Output:   &resourceConfig,
				},
				&EvalSetProviderConfig{
					Provider: n.ProviderName(),
					Config:   &resourceConfig,
				},
			},
		},
	}
}

// GraphNodeFlattenable impl.
func (n *graphNodeDisabledProvider) Flatten(p []string) (dag.Vertex, error) {
	return &graphNodeDisabledProviderFlat{
		graphNodeDisabledProvider: n,
		PathValue:                 p,
	}, nil
}

func (n *graphNodeDisabledProvider) Name() string {
	return fmt.Sprintf("%s (disabled)", dag.VertexName(n.GraphNodeProvider))
}

// GraphNodeDotter impl.
func (n *graphNodeDisabledProvider) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
	return &dag.DotNode{
		Name: name,
		Attrs: map[string]string{
			"label": n.Name(),
			"shape": "diamond",
		},
	}
}

// GraphNodeDotterOrigin impl.
func (n *graphNodeDisabledProvider) DotOrigin() bool {
	return true
}

// GraphNodeDependable impl.
func (n *graphNodeDisabledProvider) DependableName() []string {
	return []string{"provider." + n.ProviderName()}
}

// GraphNodeProvider impl.
func (n *graphNodeDisabledProvider) ProviderName() string {
	return n.GraphNodeProvider.ProviderName()
}

// GraphNodeProvider impl.
func (n *graphNodeDisabledProvider) ProviderConfig() *config.RawConfig {
	return n.GraphNodeProvider.ProviderConfig()
}

// Same as graphNodeDisabledProvider, but for flattening
type graphNodeDisabledProviderFlat struct {
	*graphNodeDisabledProvider

	PathValue []string
}

func (n *graphNodeDisabledProviderFlat) Name() string {
	return fmt.Sprintf(
		"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeDisabledProvider.Name())
}

func (n *graphNodeDisabledProviderFlat) Path() []string {
	return n.PathValue
}

func (n *graphNodeDisabledProviderFlat) ProviderName() string {
	return fmt.Sprintf(
		"%s.%s", modulePrefixStr(n.PathValue),
		n.graphNodeDisabledProvider.ProviderName())
}

// GraphNodeDependable impl.
func (n *graphNodeDisabledProviderFlat) DependableName() []string {
	return modulePrefixList(
		n.graphNodeDisabledProvider.DependableName(),
		modulePrefixStr(n.PathValue))
}

func (n *graphNodeDisabledProviderFlat) DependentOn() []string {
	var result []string

	// If we're in a module, then depend on our parent's provider
	if len(n.PathValue) > 1 {
		prefix := modulePrefixStr(n.PathValue[:len(n.PathValue)-1])
		result = modulePrefixList(
			n.graphNodeDisabledProvider.DependableName(), prefix)
	}

	return result
}