// Copyright 2016-2018, Pulumi Corporation.  All rights reserved.

package graph

import (
	"github.com/pulumi/pulumi/pkg/v3/resource/deploy/providers"
	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
)

// DependencyGraph represents a dependency graph encoded within a resource snapshot.
type DependencyGraph struct {
	index     map[*resource.State]int // A mapping of resource pointers to indexes within the snapshot
	resources []*resource.State       // The list of resources, obtained from the snapshot
}

// DependingOn returns a slice containing all resources that directly or indirectly
// depend upon the given resource. The returned slice is guaranteed to be in topological
// order with respect to the snapshot dependency graph.
//
// The time complexity of DependingOn is linear with respect to the number of resources.
func (dg *DependencyGraph) DependingOn(res *resource.State, ignore map[resource.URN]bool) []*resource.State {
	// This implementation relies on the detail that snapshots are stored in a valid
	// topological order.
	var dependents []*resource.State
	dependentSet := make(map[resource.URN]bool)

	cursorIndex, ok := dg.index[res]
	contract.Assert(ok)
	dependentSet[res.URN] = true

	isDependent := func(candidate *resource.State) bool {
		if ignore[candidate.URN] {
			return false
		}
		if candidate.Provider != "" {
			ref, err := providers.ParseReference(candidate.Provider)
			contract.Assert(err == nil)
			if dependentSet[ref.URN()] {
				return true
			}
		}
		for _, dependency := range candidate.Dependencies {
			if dependentSet[dependency] {
				return true
			}
		}
		return false
	}

	// The dependency graph encoded directly within the snapshot is the reverse of
	// the graph that we actually want to operate upon. Edges in the snapshot graph
	// originate in a resource and go to that resource's dependencies.
	//
	// The `DependingOn` is simpler when operating on the reverse of the snapshot graph,
	// where edges originate in a resource and go to resources that depend on that resource.
	// In this graph, `DependingOn` for a resource is the set of resources that are reachable from the
	// given resource.
	//
	// To accomplish this without building up an entire graph data structure, we'll do a linear
	// scan of the resource list starting at the requested resource and ending at the end of
	// the list. All resources that depend directly or indirectly on `res` are prepended
	// onto `dependents`.
	for i := cursorIndex + 1; i < len(dg.resources); i++ {
		candidate := dg.resources[i]
		if isDependent(candidate) {
			dependents = append(dependents, candidate)
			dependentSet[candidate.URN] = true
		}
	}

	return dependents
}

// DependenciesOf returns a ResourceSet of resources upon which the given resource depends. The resource's parent is
// included in the returned set.
func (dg *DependencyGraph) DependenciesOf(res *resource.State) ResourceSet {
	set := make(ResourceSet)

	dependentUrns := make(map[resource.URN]bool)
	for _, dep := range res.Dependencies {
		dependentUrns[dep] = true
	}

	if res.Provider != "" {
		ref, err := providers.ParseReference(res.Provider)
		contract.Assert(err == nil)
		dependentUrns[ref.URN()] = true
	}

	cursorIndex, ok := dg.index[res]
	contract.Assert(ok)
	for i := cursorIndex - 1; i >= 0; i-- {
		candidate := dg.resources[i]
		if dependentUrns[candidate.URN] || candidate.URN == res.Parent {
			set[candidate] = true
		}
	}

	return set
}

// NewDependencyGraph creates a new DependencyGraph from a list of resources.
// The resources should be in topological order with respect to their dependencies.
func NewDependencyGraph(resources []*resource.State) *DependencyGraph {
	index := make(map[*resource.State]int)
	for idx, res := range resources {
		index[res] = idx
	}

	return &DependencyGraph{index, resources}
}
