package sborepo

// pkgNode wraps a Package with dependency-graph state.
//
// Dependents are packages that depend on this node. InGraph marks whether the
// node is part of the current processing graph.
type pkgNode struct {
	*Package
	InGraph    bool
	dependents []*pkgNode // packages that depend on this package
	idep, ndep int32
}

// Dependents returns a slice of packages previously added by AddDependent.
// It can be used by functions which build a dependency graph.
func (pkg *pkgNode) Dependents() []*pkgNode { return pkg.dependents }

// AddDependent adds a package that depends on this package to the list of
// dependents. This is used to build a dependency graph.
func (pkg *pkgNode) AddDependent(dependent *pkgNode) {
	dependent.ndep++
	pkg.dependents = append(pkg.dependents, dependent)
}

// ProcessDependents iterates over the package, its dependents (previously set
// by AddDependent), and their dependents recursively. It applies f to each
// in-graph node and ensures that every node is visited once.
func (pkg *pkgNode) ProcessDependents(f func(*pkgNode)) {
	pkg.recUnordered(f, make(map[*pkgNode]bool))
}
func (p *pkgNode) recUnordered(f func(*pkgNode), visited map[*pkgNode]bool) {
	if visited[p] {
		return
	}
	visited[p] = true
	if p.InGraph {
		f(p)
	}
	for _, dependent := range p.Dependents() {
		dependent.recUnordered(f, visited)
	}
}

// resolveDep recursively resolves and adds dependencies of package parent. If
// the package has no dependencies, it is added to dg.root.
func (parent *pkgNode) resolveDep(repository RepoMap, dg DependencyGraph) {
	parent.InGraph = true
	dg.graphMap[parent.Name] = parent

	for _, pkgName := range parent.Dependencies() {
		switch pkgName {
		case "%README%", "":
			continue
		case parent.Name:
			panic("Circular dependency detected: " + parent.Name)
		}
		pkgPtr, ok := dg.graphMap[pkgName]
		if !ok {
			pkg, ok := repository.GetPkg(pkgName)
			if !ok {
				panic("Package " + pkgName + " is a dependency of " + parent.Name +
					" but is not found in the repository")
			}
			pkgPtr = &pkgNode{Package: pkg}
		}
		pkgPtr.AddDependent(parent)
		if !ok {
			pkgPtr.resolveDep(repository, dg)
		}
	}
	if parent.ndep == 0 {
		dg.root.AddDependent(parent)
	}
}
