package terraform

import "fmt"

// DeposedTransformer is a GraphTransformer that adds deposed resources
// to the graph.
type DeposedTransformer struct {
	// State is the global state. We'll automatically find the correct
	// ModuleState based on the Graph.Path that is being transformed.
	State *State

	// View, if non-empty, is the ModuleState.View used around the state
	// to find deposed resources.
	View string

	// The provider used by the resourced which were deposed
	ResolvedProvider string
}

func (t *DeposedTransformer) Transform(g *Graph) error {
	state := t.State.ModuleByPath(g.Path)
	if state == nil {
		// If there is no state for our module there can't be any deposed
		// resources, since they live in the state.
		return nil
	}

	// If we have a view, apply it now
	if t.View != "" {
		state = state.View(t.View)
	}

	// Go through all the resources in our state to look for deposed resources
	for k, rs := range state.Resources {
		// If we have no deposed resources, then move on
		if len(rs.Deposed) == 0 {
			continue
		}

		deposed := rs.Deposed

		for i, _ := range deposed {
			g.Add(&graphNodeDeposedResource{
				Index:            i,
				ResourceName:     k,
				ResourceType:     rs.Type,
				ProviderName:     rs.Provider,
				ResolvedProvider: t.ResolvedProvider,
			})
		}
	}

	return nil
}

// graphNodeDeposedResource is the graph vertex representing a deposed resource.
type graphNodeDeposedResource struct {
	Index            int
	ResourceName     string
	ResourceType     string
	ProviderName     string
	ResolvedProvider string
}

func (n *graphNodeDeposedResource) Name() string {
	return fmt.Sprintf("%s (deposed #%d)", n.ResourceName, n.Index)
}

func (n *graphNodeDeposedResource) ProvidedBy() string {
	return resourceProvider(n.ResourceName, n.ProviderName)
}

func (n *graphNodeDeposedResource) SetProvider(p string) {
	n.ResolvedProvider = p
}

// GraphNodeEvalable impl.
func (n *graphNodeDeposedResource) EvalTree() EvalNode {
	var provider ResourceProvider
	var state *InstanceState

	seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}

	// Build instance info
	info := &InstanceInfo{Id: n.Name(), Type: n.ResourceType}
	seq.Nodes = append(seq.Nodes, &EvalInstanceInfo{Info: info})

	// Refresh the resource
	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
		Ops: []walkOperation{walkRefresh},
		Node: &EvalSequence{
			Nodes: []EvalNode{
				&EvalGetProvider{
					Name:   n.ResolvedProvider,
					Output: &provider,
				},
				&EvalReadStateDeposed{
					Name:   n.ResourceName,
					Output: &state,
					Index:  n.Index,
				},
				&EvalRefresh{
					Info:     info,
					Provider: &provider,
					State:    &state,
					Output:   &state,
				},
				&EvalWriteStateDeposed{
					Name:         n.ResourceName,
					ResourceType: n.ResourceType,
					Provider:     n.ResolvedProvider,
					State:        &state,
					Index:        n.Index,
				},
			},
		},
	})

	// Apply
	var diff *InstanceDiff
	var err error
	seq.Nodes = append(seq.Nodes, &EvalOpFilter{
		Ops: []walkOperation{walkApply, walkDestroy},
		Node: &EvalSequence{
			Nodes: []EvalNode{
				&EvalGetProvider{
					Name:   n.ResolvedProvider,
					Output: &provider,
				},
				&EvalReadStateDeposed{
					Name:   n.ResourceName,
					Output: &state,
					Index:  n.Index,
				},
				&EvalDiffDestroy{
					Info:   info,
					State:  &state,
					Output: &diff,
				},
				// Call pre-apply hook
				&EvalApplyPre{
					Info:  info,
					State: &state,
					Diff:  &diff,
				},
				&EvalApply{
					Info:     info,
					State:    &state,
					Diff:     &diff,
					Provider: &provider,
					Output:   &state,
					Error:    &err,
				},
				// Always write the resource back to the state deposed... if it
				// was successfully destroyed it will be pruned. If it was not, it will
				// be caught on the next run.
				&EvalWriteStateDeposed{
					Name:         n.ResourceName,
					ResourceType: n.ResourceType,
					Provider:     n.ResolvedProvider,
					State:        &state,
					Index:        n.Index,
				},
				&EvalApplyPost{
					Info:  info,
					State: &state,
					Error: &err,
				},
				&EvalReturnError{
					Error: &err,
				},
				&EvalUpdateStateHook{},
			},
		},
	})

	return seq
}
