/**
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package resolve

import (
	"fmt"
	"mynewt.apache.org/newt/newt/cfgv"
	"sort"
	"strings"

	log "github.com/sirupsen/logrus"

	"mynewt.apache.org/newt/newt/extcmd"
	"mynewt.apache.org/newt/newt/flashmap"
	"mynewt.apache.org/newt/newt/logcfg"
	"mynewt.apache.org/newt/newt/newtutil"
	"mynewt.apache.org/newt/newt/parse"
	"mynewt.apache.org/newt/newt/pkg"
	"mynewt.apache.org/newt/newt/project"
	"mynewt.apache.org/newt/newt/syscfg"
	"mynewt.apache.org/newt/newt/sysdown"
	"mynewt.apache.org/newt/newt/sysinit"
	"mynewt.apache.org/newt/util"
)

// Represents a supplied API.
type resolveApi struct {
	// The package which supplies the API.
	rpkg *ResolvePackage

	// The expression which enabled this API.
	expr *parse.Node
}

// Represents a required API.
type resolveReqApi struct {
	// Whether the API requirement has been satisfied by a hard dependency.
	satisfied bool

	// The set of expressions which enabled this API requirement.  If any
	// expressions are true, the API requirement is enabled.
	exprs parse.ExprSet
}

type Resolver struct {
	apis             map[string]resolveApi
	pkgMap           map[*pkg.LocalPackage]*ResolvePackage
	seedPkgs         []*pkg.LocalPackage
	injectedSettings *cfgv.Settings
	flashMap         flashmap.FlashMap
	cfg              syscfg.Cfg
	lcfg             logcfg.LCfg
	sysinitCfg       sysinit.SysinitCfg
	sysdownCfg       sysdown.SysdownCfg
	preBuildCmdCfg   extcmd.ExtCmdCfg
	preLinkCmdCfg    extcmd.ExtCmdCfg
	postLinkCmdCfg   extcmd.ExtCmdCfg

	// [api-name][api-supplier]
	apiConflicts map[string]map[*ResolvePackage]struct{}

	parseWarnings map[*ResolvePackage][]string
}

type ResolveDep struct {
	// Package being depended on.
	Rpkg *ResolvePackage

	// Set of syscfg expressions that generated this dependency.
	Exprs parse.ExprSet

	// Represents the set of API requirements that this dependency satisfies.
	// The map key is the API name.
	ApiExprMap parse.ExprMap
}

type ResolvePackage struct {
	Lpkg *pkg.LocalPackage
	Deps map[*ResolvePackage]*ResolveDep

	Apis parse.ExprMap

	// Keeps track of API requirements and whether they are satisfied.
	reqApiMap map[string]resolveReqApi

	depsResolved bool

	// Tracks this package's dependents (things that depend on us).  If this
	// map becomes empty, this package can be deleted from the resolver.
	revDeps map[*ResolvePackage]struct{}
}

type ResolveSet struct {
	// Parent resolution.  Contains this ResolveSet.
	Res *Resolution

	// All seed packages and their dependencies.
	Rpkgs []*ResolvePackage
}

type ApiConflict struct {
	Api  string
	Pkgs []*ResolvePackage
}

// The result of resolving a target's configuration, APIs, and dependencies.
type Resolution struct {
	Cfg             syscfg.Cfg
	LCfg            logcfg.LCfg
	SysinitCfg      sysinit.SysinitCfg
	SysdownCfg      sysdown.SysdownCfg
	PreBuildCmdCfg  extcmd.ExtCmdCfg
	PreLinkCmdCfg   extcmd.ExtCmdCfg
	PostLinkCmdCfg  extcmd.ExtCmdCfg
	ApiMap          map[string]*ResolvePackage
	UnsatisfiedApis map[string][]*ResolvePackage
	ApiConflicts    []ApiConflict
	ParseWarnings   []string

	LpkgRpkgMap map[*pkg.LocalPackage]*ResolvePackage

	// Contains all dependencies; union of loader and app.
	MasterSet *ResolveSet

	LoaderSet *ResolveSet
	AppSet    *ResolveSet
}

func newResolver(
	seedPkgs []*pkg.LocalPackage,
	injectedSettings *cfgv.Settings,
	flashMap flashmap.FlashMap) *Resolver {

	r := &Resolver{
		apis:             map[string]resolveApi{},
		pkgMap:           map[*pkg.LocalPackage]*ResolvePackage{},
		seedPkgs:         seedPkgs,
		injectedSettings: injectedSettings,
		flashMap:         flashMap,
		cfg:              syscfg.NewCfg(),
		apiConflicts:     map[string]map[*ResolvePackage]struct{}{},
		parseWarnings:    map[*ResolvePackage][]string{},
	}

	if injectedSettings == nil {
		r.injectedSettings = cfgv.NewSettings(nil)
	}

	for _, lpkg := range seedPkgs {
		r.addPkg(lpkg)
	}

	return r
}

func newResolution() *Resolution {
	r := &Resolution{
		ApiMap:          map[string]*ResolvePackage{},
		UnsatisfiedApis: map[string][]*ResolvePackage{},
	}

	r.MasterSet = &ResolveSet{Res: r}
	r.LoaderSet = &ResolveSet{Res: r}
	r.AppSet = &ResolveSet{Res: r}

	return r
}

func NewResolvePkg(lpkg *pkg.LocalPackage) *ResolvePackage {
	return &ResolvePackage{
		Lpkg:      lpkg,
		reqApiMap: map[string]resolveReqApi{},
		Deps:      map[*ResolvePackage]*ResolveDep{},
		revDeps:   map[*ResolvePackage]struct{}{},
	}
}

// useMasterPkgs replaces a resolve set's packages with their equivalents from
// the master set.  This function is necessary to ensure only a single copy of
// each package exists among all resolve sets in a split build.
func (rs *ResolveSet) useMasterPkgs() error {
	for i, rdst := range rs.Rpkgs {
		rsrc := rs.Res.LpkgRpkgMap[rdst.Lpkg]
		if rsrc == nil {
			return util.FmtNewtError(
				"cannot use master packages in resolve set; "+
					"package \"%s\" missing",
				rdst.Lpkg.FullName())
		}

		rs.Rpkgs[i] = rsrc
	}

	return nil
}

func (r *Resolver) resolveDep(dep *pkg.Dependency,
	depender string) (*pkg.LocalPackage, error) {

	proj := project.GetProject()

	if proj.ResolveDependency(dep) == nil {
		return nil, util.FmtNewtError("Could not resolve package dependency: "+
			"%s; depender: %s", dep.String(), depender)
	}
	lpkg := proj.ResolveDependency(dep).(*pkg.LocalPackage)

	return lpkg, nil
}

// @return                      true if the package's dependency list was
//                                  modified.
func (rpkg *ResolvePackage) AddDep(
	depPkg *ResolvePackage, expr *parse.Node) bool {

	exprString := expr.String()

	var changed bool
	var dep *ResolveDep
	if dep = rpkg.Deps[depPkg]; dep != nil {
		// This package already depends on dep.  If the conditional expression
		// is new, then the existing dependency needs to be updated with the
		// new information.  Otherwise, ignore the duplicate.

		if _, ok := dep.Exprs[exprString]; !ok {
			changed = true
		}
	} else {
		// New dependency.
		dep = &ResolveDep{
			Rpkg: depPkg,
		}

		rpkg.Deps[depPkg] = dep
		depPkg.revDeps[rpkg] = struct{}{}
		changed = true
	}

	if dep.Exprs == nil {
		dep.Exprs = parse.ExprSet{}
	}
	dep.Exprs[exprString] = expr

	return changed
}

func (rpkg *ResolvePackage) AddApiDep(
	depPkg *ResolvePackage, api string, exprs []*parse.Node) {

	// Satisfy the API dependency.
	rpkg.reqApiMap[api] = resolveReqApi{
		satisfied: true,
		exprs:     parse.NewExprSet(exprs),
	}

	// Add a reverse dependency to the API-supplier.
	dep := rpkg.Deps[depPkg]
	if dep == nil {
		dep = &ResolveDep{
			Rpkg: depPkg,
		}
		rpkg.Deps[depPkg] = dep
	}
	if dep.ApiExprMap == nil {
		dep.ApiExprMap = parse.ExprMap{}
	}
	dep.ApiExprMap.Add(api, exprs)
}

func (r *Resolver) rpkgSlice() []*ResolvePackage {
	rpkgs := make([]*ResolvePackage, len(r.pkgMap))

	i := 0
	for _, rpkg := range r.pkgMap {
		rpkgs[i] = rpkg
		i++
	}

	return rpkgs
}

func (r *Resolver) apiSlice() []string {
	apis := make([]string, len(r.apis))

	i := 0
	for api, _ := range r.apis {
		apis[i] = api
		i++
	}

	return apis
}

// @return ResolvePackage		The rpkg corresponding to the specified lpkg.
//                                  This is a new package if a package was
//                                  added; old if it was already present.
//         bool					true if this is a new package.
func (r *Resolver) addPkg(lpkg *pkg.LocalPackage) (*ResolvePackage, bool) {
	if rpkg := r.pkgMap[lpkg]; rpkg != nil {
		return rpkg, false
	}

	rpkg := NewResolvePkg(lpkg)
	r.pkgMap[lpkg] = rpkg
	return rpkg, true
}

func (r *Resolver) sortedRpkgs() []*ResolvePackage {
	rpkgs := make([]*ResolvePackage, 0, len(r.pkgMap))
	for _, rpkg := range r.pkgMap {
		rpkgs = append(rpkgs, rpkg)
	}

	SortResolvePkgs(rpkgs)
	return rpkgs
}

func (r *Resolver) fillApisFor(rpkg *ResolvePackage) error {
	settings := r.cfg.AllSettingsForLpkg(rpkg.Lpkg)

	em, warn, err := readExprMap(rpkg.Lpkg.PkgY, "pkg.apis", settings)
	if err != nil {
		return err
	}
	if warn != "" {
		r.parseWarnings[rpkg] = append(r.parseWarnings[rpkg], warn)
	}

	rpkg.Apis = em
	return nil
}

// Selects the final API suppliers among all packages implementing APIs.  The
// result gets written to the resolver's `apis` map.  If more than one package
// implements the same API, an API conflict error is recorded.
func (r *Resolver) selectApiSuppliers() {
	apiMap := map[string][]resolveApi{}

	// Fill each package's list of supplied APIs.
	for _, rpkg := range r.sortedRpkgs() {
		r.fillApisFor(rpkg)
		for apiName, exprSet := range rpkg.Apis {
			apiMap[apiName] = append(apiMap[apiName], resolveApi{
				rpkg: rpkg,
				expr: exprSet.Disjunction(),
			})
		}
	}

	// Detect API conflicts and determine which packages supply which APIs.
	apiNames := make([]string, 0, len(apiMap))
	for name, _ := range apiMap {
		apiNames = append(apiNames, name)
	}
	sort.Strings(apiNames)

	for _, name := range apiNames {
		apis := apiMap[name]
		for _, api := range apis {
			old := r.apis[name]
			if old.rpkg != nil {
				if r.apiConflicts[name] == nil {
					r.apiConflicts[name] = map[*ResolvePackage]struct{}{}
				}
				r.apiConflicts[name][api.rpkg] = struct{}{}
				r.apiConflicts[name][old.rpkg] = struct{}{}
			} else {
				r.apis[name] = api
			}
		}
	}
}

// Populates the specified package's set of API requirements.
func (r *Resolver) calcApiReqsFor(rpkg *ResolvePackage) error {
	settings := r.cfg.AllSettingsForLpkg(rpkg.Lpkg)

	em, warn, err := readExprMap(rpkg.Lpkg.PkgY, "pkg.req_apis", settings)
	if err != nil {
		return err
	}
	if warn != "" {
		r.parseWarnings[rpkg] = append(r.parseWarnings[rpkg], warn)
	}

	for api, es := range em {
		rpkg.reqApiMap[api] = resolveReqApi{
			satisfied: false,
			exprs:     es,
		}
	}

	return nil
}

// Populates all packages' API requirements sets.
func (r *Resolver) calcApiReqs() error {
	for _, rpkg := range r.pkgMap {
		if err := r.calcApiReqsFor(rpkg); err != nil {
			return err
		}
	}

	return nil
}

// Completely removes a package from the resolver.  This is used to prune
// packages when newly-discovered syscfg values nullify dependencies.
func (r *Resolver) deletePkg(rpkg *ResolvePackage) error {
	i := 0
	for i < len(r.seedPkgs) {
		lpkg := r.seedPkgs[i]
		if lpkg == rpkg.Lpkg {
			fmt.Printf("DELETING SEED: %s (%p)\n", lpkg.FullName(), lpkg)
			r.seedPkgs = append(r.seedPkgs[:i], r.seedPkgs[i+1:]...)
		} else {
			i++
		}
	}
	delete(r.pkgMap, rpkg.Lpkg)

	// Delete the package from syscfg.
	r.cfg.DeletePkg(rpkg.Lpkg)

	// Remove all dependencies on the deleted package.
	for revdep, _ := range rpkg.revDeps {
		delete(revdep.Deps, rpkg)
	}

	// Remove all reverse dependencies pointing to the deleted package.  If the
	// deleted package is the only depender for any other packages (i.e., if
	// any of its dependencies have only one reverse dependency),
	// delete them as well.
	for dep, _ := range rpkg.Deps {
		if len(dep.revDeps) == 0 {
			return util.FmtNewtError(
				"package %s unexpectedly has 0 reverse dependencies",
				dep.Lpkg.FullName())
		}
		delete(dep.revDeps, rpkg)
		if len(dep.revDeps) == 0 {
			if err := r.deletePkg(dep); err != nil {
				return err
			}
		}
	}

	return nil
}

// @return bool                 True if this this function changed the resolver
//                                  state; another full iteration is required
//                                  in this case.
//         error                non-nil on failure.
func (r *Resolver) loadDepsForPkg(rpkg *ResolvePackage) (bool, error) {
	// Clear warnings from previous run.
	if _, ok := r.parseWarnings[rpkg]; ok {
		delete(r.parseWarnings, rpkg)
	}

	settings := r.cfg.AllSettingsForLpkg(rpkg.Lpkg)

	changed := false

	var depEm map[*parse.Node][]string
	var warn string
	var err error

	if rpkg.Lpkg.Type() == pkg.PACKAGE_TYPE_TRANSIENT {
		depEm, warn, err = getExprMapStringSlice(rpkg.Lpkg.PkgY, "pkg.link", nil)
	} else {
		depEm, warn, err = getExprMapStringSlice(rpkg.Lpkg.PkgY, "pkg.deps",
			settings)
	}
	if err != nil {
		return false, err
	}
	if warn != "" {
		r.parseWarnings[rpkg] = append(r.parseWarnings[rpkg], warn)
	}

	depender := rpkg.Lpkg.Name()

	oldDeps := rpkg.Deps
	rpkg.Deps = make(map[*ResolvePackage]*ResolveDep, len(oldDeps))
	for expr, depNames := range depEm {
		for _, depName := range depNames {
			newDep, err := pkg.NewDependency(rpkg.Lpkg.Repo(), depName)
			if err != nil {
				return false, err
			}

			lpkg, err := r.resolveDep(newDep, depender)
			if err != nil {
				return false, err
			}

			if lpkg.Type() == pkg.PACKAGE_TYPE_CONFIG {
				if rpkg.Lpkg.Type() != pkg.PACKAGE_TYPE_TARGET &&
					rpkg.Lpkg.Type() != pkg.PACKAGE_TYPE_CONFIG {
					return false, util.NewNewtError(fmt.Sprintf("Config packages (%s) "+
						"can only be included by target or config packages (%s -> %s)",
						lpkg.FullName(), rpkg.Lpkg.FullName(),
						pkg.PackageTypeNames[rpkg.Lpkg.Type()]))
				}
			} else if rpkg.Lpkg.Type() == pkg.PACKAGE_TYPE_CONFIG {
				return false, util.NewNewtError(fmt.Sprintf("Config packages (%s) "+
					"can only include config package (%s -> %s)", rpkg.Lpkg.FullName(),
					lpkg.FullName(), pkg.PackageTypeNames[lpkg.Type()]))
			}

			depRpkg, _ := r.addPkg(lpkg)
			rpkg.AddDep(depRpkg, expr)
		}
	}

	// This iteration may have deleted some dependency relationships (e.g., if
	// a new syscfg setting was discovered which causes this package's
	// dependency list to be overwritten).  Detect and delete these
	// relationships.
	for rdep, _ := range oldDeps {
		if _, ok := rpkg.Deps[rdep]; !ok {
			delete(rdep.revDeps, rpkg)
			changed = true

			// If we just deleted the last reference to a package, remove the
			// package entirely from the resolver and syscfg.
			if len(rdep.revDeps) == 0 {
				if err := r.deletePkg(rdep); err != nil {
					return true, err
				}
			}
		}
	}

	for rdep, _ := range rpkg.Deps {
		if _, ok := oldDeps[rdep]; !ok {
			changed = true
		}
	}

	return changed, nil
}

// Attempts to resolve all of a build package's dependencies, APIs, and
// required APIs.  This function should be called repeatedly until the package
// is fully resolved.
//
// If a dependency is resolved by this function, the new dependency needs to be
// processed.  The caller should attempt to resolve all packages again.
//
// @return bool                 true if >=1 dependencies were resolved.
//         error                non-nil on failure.
func (r *Resolver) resolvePkg(rpkg *ResolvePackage) (bool, error) {
	var err error
	newDeps := false

	if !rpkg.depsResolved {
		newDeps, err = r.loadDepsForPkg(rpkg)
		if err != nil {
			return false, err
		}

		rpkg.depsResolved = !newDeps
	}

	return newDeps, nil
}

// @return                      changed,err
func (r *Resolver) reloadCfg() (bool, error) {
	lpkgs := RpkgSliceToLpkgSlice(r.rpkgSlice())
	apis := r.apiSlice()

	// Determine which settings have been detected so far.  The feature map is
	// required for reloading syscfg, as settings may unlock additional
	// settings.
	settings := r.cfg.SettingValues()
	cfg, err := syscfg.Read(lpkgs, apis, r.injectedSettings, settings,
		r.flashMap)
	if err != nil {
		return false, err
	}

	cfg.AddInjectedSettings()
	cfg.ResolveValueRefs()

	// Determine if any new settings have been added or if any existing
	// settings have changed.
	for k, v := range cfg.Settings {
		oldval, ok := r.cfg.Settings[k]
		if !ok || len(oldval.History) != len(v.History) {
			r.cfg = cfg
			return true, nil
		}
	}

	// Determine if any existing settings have been removed.
	for k, _ := range r.cfg.Settings {
		if _, ok := cfg.Settings[k]; !ok {
			r.cfg = cfg
			return true, nil
		}
	}

	r.cfg = cfg

	return false, nil
}

// traceToSeed determines if the specified package can be reached from a seed
// package via a traversal of the dependency graph.  The supplied settings are
// used to determine the validity of dependencies in the graph.
func (rpkg *ResolvePackage) traceToSeed(
	settings *cfgv.Settings) (bool, error) {

	seen := map[*ResolvePackage]struct{}{}

	// A nested function is used here so that the `seen` map can be used across
	// multiple invocations.
	var iter func(cur *ResolvePackage) (bool, error)
	iter = func(cur *ResolvePackage) (bool, error) {
		// Don't process the same package twice.
		if _, ok := seen[cur]; ok {
			return false, nil
		}
		seen[cur] = struct{}{}

		// A type greater than "library" is a seed package.
		if cur.Lpkg.Type() > pkg.PACKAGE_TYPE_LIB {
			return true, nil
		}

		// Repeat the trace recursively for each depending package.
		for depender, _ := range cur.revDeps {
			rdep := depender.Deps[cur]

			// Only process this reverse dependency if it is valid given the
			// specified syscfg.
			expr := rdep.Exprs.Disjunction()
			depValid, err := parse.Eval(expr, settings)
			if err != nil {
				return false, err
			}

			if depValid {
				traced, err := iter(depender)
				if err != nil {
					return false, err
				}
				if traced {
					// Depender can be traced to a seed package.
					return true, nil
				}
			}
		}

		// All dependencies processed without reaching a seed package.
		return false, nil
	}

	return iter(rpkg)
}

// detectImposter returns true if the package is an imposter.  A package is an
// imposter if it is in the dependency graph by virtue of its own syscfg
// defines and overrides.  For example, say we have a package `foo`:
//
//     pkg.name: foo
//     syscfg.defs:
//         FOO_SETTING:
//		       value: 1
//
// Then we have a BSP package:
//
//     pkg.name: my_bsp
//     pkg.deps.FOO_SETTING:
//         - foo
//
// If this is the only dependency on `foo`, then `foo` is an imposter.  It
// should be removed from the graph, and its syscfg defines and overrides
// should be deleted.
//
// Because the syscfg state changes as newt discovers new dependencies, it is
// possible for imposters to end up in the graph.
func (r *Resolver) detectImposter(rpkg *ResolvePackage) (bool, error) {
	// Calculate a new syscfg instance, pretending the specified package
	// doesn't exist.
	settings := make(map[string]syscfg.CfgEntry, len(r.cfg.Settings))
	for k, src := range r.cfg.Settings {
		// Copy the source entry in full, then check its history for the
		// potential imposter.
		dst := src
		dst.History = make([]syscfg.CfgPoint, len(src.History))
		copy(dst.History, src.History)

		for i, _ := range dst.History {
			if dst.History[i].Source == rpkg.Lpkg {
				if i == 0 {
					// This setting is defined by the package; remove it
					// entirely.
					dst.History = nil
				} else {
					// Remove the package's override.
					dst.History = append(dst.History[:i], dst.History[i+1:]...)
				}
				break
			}
		}

		// Retain the setting if it wasn't defined by the potential imposter.
		if len(dst.History) > 0 {
			settings[k] = dst
		}
	}

	// See if the package can still be traced to a seed package when the
	// modified settings are used.
	found, err := rpkg.traceToSeed(syscfg.SettingValues(settings))
	if err != nil {
		return false, err
	}

	return !found, nil
}

// detectImpostersWorker reads packages from a channel and determines if they
// are imposters.  It is meant to be run in parallel via multiple go routines.
//
// See detectImposter() for a definition of "imposter package".
func (r *Resolver) detectImpostersWorker(
	jobsCh <-chan *ResolvePackage,
	stopCh chan struct{},
	resultsCh chan<- *ResolvePackage,
	errCh chan<- error,
) {

	// Repeatedly process jobs until any of:
	// 1. Stop signal from another go routine.
	// 2. Error encountered.
	// 3. No more jobs.
	for {
		select {
		case <-stopCh:
			// Re-enqueue the stop signal for the other go routines.
			stopCh <- struct{}{}

			// Completed without error.
			errCh <- nil
			return

		case rpkg := <-jobsCh:
			isImposter, err := r.detectImposter(rpkg)
			if err != nil {
				stopCh <- struct{}{}
				errCh <- err
				return
			}

			if isImposter {
				// Signal that this package can be pruned.
				resultsCh <- rpkg
			}

		default:
			// No more jobs to process.
			errCh <- nil
			return
		}
	}
}

// pruneImposters identifies and deletes imposters contained by the resolver.
// This function should be called repeatedly until no more imposters are
// identified.  It returns true if any imposters were found and deleted.
func (r *Resolver) pruneImposters() (bool, error) {
	jobsCh := make(chan *ResolvePackage, len(r.pkgMap))
	defer close(jobsCh)

	stopCh := make(chan struct{}, newtutil.NewtNumJobs)
	defer close(stopCh)

	resultsCh := make(chan *ResolvePackage, len(r.pkgMap))
	defer close(resultsCh)

	errCh := make(chan error, newtutil.NewtNumJobs)
	defer close(errCh)

	seedMap := map[*pkg.LocalPackage]struct{}{}
	for _, lpkg := range r.seedPkgs {
		seedMap[lpkg] = struct{}{}
	}

	// Enqueue all packages to the jobs channel.
	for _, rpkg := range r.pkgMap {
		if _, ok := seedMap[rpkg.Lpkg]; !ok {
			jobsCh <- rpkg
		}
	}

	// Iterate through all packages with a collection of go routines.
	for i := 0; i < newtutil.NewtNumJobs; i++ {
		go r.detectImpostersWorker(jobsCh, stopCh, resultsCh, errCh)
	}

	// Collect errors from each routine.  Abort on first error.
	for i := 0; i < newtutil.NewtNumJobs; i++ {
		if err := <-errCh; err != nil {
			return false, err
		}
	}

	// Delete all imposter packages.
	anyPruned := false
	for {
		select {
		case rpkg := <-resultsCh:
			// This package may have already been deleted indirectly via a
			// prior delete.  If it has no more reverse dependencies, it is
			// already invalid.
			if len(rpkg.revDeps) > 0 {
				if err := r.deletePkg(rpkg); err != nil {
					return false, err
				}
				anyPruned = true
			}

		default:
			return anyPruned, nil
		}
	}
}

// @return bool                 True if any packages were pruned, false
//                                  otherwise.
// @return err                  Error
func (r *Resolver) pruneOrphans() (bool, error) {
	seenMap := map[*ResolvePackage]struct{}{}

	// This function traverses the specified package's dependency list,
	// recording each visited packges in `seenMap`.
	var visit func(rpkg *ResolvePackage)
	visit = func(rpkg *ResolvePackage) {
		if _, ok := seenMap[rpkg]; ok {
			return
		}

		seenMap[rpkg] = struct{}{}
		for dep, _ := range rpkg.Deps {
			visit(dep)
		}
	}

	// Starting from each seed package, recursively traverse the package's
	// dependency list, keeping track of which packages were visited.
	for _, lpkg := range r.seedPkgs {
		rpkg := r.pkgMap[lpkg]
		if rpkg == nil {
			panic(fmt.Sprintf("Resolver lacks mapping for seed package %s (%p)", lpkg.FullName(), lpkg))
		}

		visit(rpkg)
	}

	// Any non-visited packages in the resolver are orphans and can be removed.
	anyPruned := false
	for _, rpkg := range r.pkgMap {
		if _, ok := seenMap[rpkg]; !ok {
			anyPruned = true
			if err := r.deletePkg(rpkg); err != nil {
				return false, err
			}
		}
	}

	return anyPruned, nil
}

func (r *Resolver) resolveHardDepsOnce() (bool, error) {
	// Circularly resolve dependencies, APIs, and required APIs until no new
	// ones exist.
	reprocess := false
	for _, rpkg := range r.pkgMap {
		newDeps, err := r.resolvePkg(rpkg)
		if err != nil {
			return false, err
		}

		if newDeps {
			// The new dependencies need to be processed.  Iterate again
			// after this iteration completes.
			reprocess = true
		}
	}

	if reprocess {
		return true, nil
	}

	// Prune orphan packages.
	anyPruned, err := r.pruneOrphans()
	if err != nil {
		return false, err
	}
	if anyPruned {
		return true, nil
	}

	// Prune imposter packages.
	anyPruned, err = r.pruneImposters()
	if err != nil {
		return false, err
	}
	if anyPruned {
		return true, nil
	}

	return false, nil
}

func (r *Resolver) resolveConfigPriorities() {
	var q []*ResolvePackage

	// Config packages are only includes by target package so need to find
	// that one and use it as a start point. Also since this may not be (and
	// probably is not) our first pass, we need to reset priorities for all
	// config packages found.
	for _, rpkg := range r.pkgMap {
		if rpkg.Lpkg.Type() == pkg.PACKAGE_TYPE_CONFIG {
			rpkg.Lpkg.SetSubPriority(0)
		}
		if rpkg.Lpkg.Type() == pkg.PACKAGE_TYPE_TARGET {
			q = append(q, rpkg)
		}
	}

	for len(q) > 0 {
		var parentPrio int

		parent := q[0]
		q = q[1:]

		// Direct dependencies of target package will have priority=99,
		// subsequent levels of dependency will have priority reduced.
		if parent.Lpkg.Type() == pkg.PACKAGE_TYPE_TARGET {
			parentPrio = pkg.PACKAGE_SUBPRIO_NUM
		} else {
			parentPrio = parent.Lpkg.SubPriority()
		}

		for rpkg, _ := range parent.Deps {
			if rpkg.Lpkg.Type() == pkg.PACKAGE_TYPE_CONFIG && rpkg.Lpkg.SubPriority() == 0 {
				rpkg.Lpkg.SetSubPriority(parentPrio - 1)
				q = append(q, rpkg)
			}
		}
	}
}

// Fully resolves all hard dependencies (i.e., packages listed in `pkg.deps`;
// not API dependencies).
func (r *Resolver) resolveHardDeps() error {
	for {
		reprocess, err := r.resolveHardDepsOnce()
		if err != nil {
			return err
		}

		if !reprocess {
			break
		}
	}

	r.resolveConfigPriorities()

	return nil
}

// Given a fully calculated syscfg and API map, resolves package dependencies
// by populating the resolver's package map.  This function should only be
// called if the resolver's syscfg (`cfg`) member is assigned.  This only
// happens for split images when the individual loader and app components are
// resolved separately, after the master syscfg and API map have been
// calculated.
func (r *Resolver) resolveDeps() ([]*ResolvePackage, error) {
	if err := r.resolveHardDeps(); err != nil {
		return nil, err
	}

	// Now that the final set of packages is known, determine which ones
	// satisfy each required API.
	r.selectApiSuppliers()

	// Determine which packages have API requirements.
	if err := r.calcApiReqs(); err != nil {
		return nil, err
	}

	// Satisfy API requirements.
	if err := r.resolveApiDeps(); err != nil {
		return nil, err
	}

	rpkgs := r.rpkgSlice()
	return rpkgs, nil
}

// Performs a set of resolution actions:
// 1. Calculates the system configuration (syscfg).
// 2. Determines which packages satisfy which API requirements.
// 3. Resolves package dependencies by populating the resolver's package map.
func (r *Resolver) resolveDepsAndCfg() error {
	if err := r.resolveHardDeps(); err != nil {
		return err
	}

	for {
		cfgChanged, err := r.reloadCfg()
		if err != nil {
			return err
		}
		if cfgChanged {
			// A new supported feature was discovered.  It is impossible
			// to determine what new dependency and API requirements are
			// generated as a result.  All packages need to be
			// reprocessed.
			for _, rpkg := range r.pkgMap {
				rpkg.depsResolved = false
			}
		}

		if err := r.resolveHardDeps(); err != nil {
			return err
		}

		if !cfgChanged {
			break
		}
	}

	// Now that the final set of packages is known, determine which ones
	// satisfy each required API.
	r.selectApiSuppliers()

	// Determine which packages have API requirements.
	if err := r.calcApiReqs(); err != nil {
		return err
	}

	// Satisfy API requirements.
	if err := r.resolveApiDeps(); err != nil {
		return err
	}

	lpkgs := RpkgSliceToLpkgSlice(r.rpkgSlice())
	r.lcfg = logcfg.Read(lpkgs, &r.cfg)
	r.sysinitCfg = sysinit.Read(lpkgs, &r.cfg)
	r.sysdownCfg = sysdown.Read(lpkgs, &r.cfg)

	r.preBuildCmdCfg = extcmd.Read("pre_build_cmds", lpkgs, &r.cfg,
		func(lpkg *pkg.LocalPackage,
			settings *cfgv.Settings) map[string]string {
			return lpkg.PreBuildCmds(settings)
		})
	r.preLinkCmdCfg = extcmd.Read("pre_link_cmds", lpkgs, &r.cfg,
		func(lpkg *pkg.LocalPackage,
			settings *cfgv.Settings) map[string]string {
			return lpkg.PreLinkCmds(settings)
		})
	r.postLinkCmdCfg = extcmd.Read("post_link_cmds", lpkgs, &r.cfg,
		func(lpkg *pkg.LocalPackage,
			settings *cfgv.Settings) map[string]string {
			return lpkg.PostLinkCmds(settings)
		})

	// Log the final syscfg.
	r.cfg.Log()

	return nil
}

func joinExprs(expr1 string, expr2 string) string {
	if expr1 == "" {
		return expr2
	}
	if expr2 == "" {
		return expr1
	}

	return expr1 + "," + expr2
}

// Transforms each package's required APIs to hard dependencies.  That is, this
// function determines which package supplies each required API, and adds the
// corresponding dependecy to each package which requires the API.
func (r *Resolver) resolveApiDeps() error {
	for _, rpkg := range r.pkgMap {
		for apiString, reqApi := range rpkg.reqApiMap {
			// Determine which package satisfies this API requirement.
			api, ok := r.apis[apiString]

			// If there is a package that supports the requested API, add a
			// hard dependency to the package.  Otherwise, record an
			// unsatisfied API requirement with an empty API struct.
			if ok && api.rpkg != nil {
				rpkg.AddApiDep(api.rpkg, apiString, reqApi.exprs.Exprs())
			} else if !ok {
				r.apis[apiString] = resolveApi{}
			}
		}
	}

	return nil
}

func (r *Resolver) apiResolution() (
	map[string]*ResolvePackage,
	map[string][]*ResolvePackage) {

	apiMap := make(map[string]*ResolvePackage, len(r.apis))
	anyUnsatisfied := false
	for name, api := range r.apis {
		if api.rpkg == nil {
			anyUnsatisfied = true
		} else {
			apiMap[name] = api.rpkg
		}
	}

	unsatisfied := map[string][]*ResolvePackage{}
	if anyUnsatisfied {
		for _, rpkg := range r.pkgMap {
			for name, reqApi := range rpkg.reqApiMap {
				if !reqApi.satisfied {
					slice := unsatisfied[name]
					slice = append(slice, rpkg)
					unsatisfied[name] = slice
				}
			}
		}
	}

	for _, slice := range unsatisfied {
		SortResolvePkgs(slice)
	}

	return apiMap, unsatisfied
}

func ResolveFull(
	loaderSeeds []*pkg.LocalPackage,
	appSeeds []*pkg.LocalPackage,
	injectedSettings *cfgv.Settings,
	flashMap flashmap.FlashMap,
	detectErr bool) (*Resolution, error) {

	// First, calculate syscfg and determine which package provides each
	// required API.  Syscfg and APIs are project-wide; that is, they are
	// calculated across the aggregate of all app packages and loader packages
	// (if any).  The dependency graph for the entire set of packages gets
	// calculated here as a byproduct.

	allSeeds := append(loaderSeeds, appSeeds...)
	r := newResolver(allSeeds, injectedSettings, flashMap)

	if err := r.resolveDepsAndCfg(); err != nil {
		return nil, err
	}

	res := newResolution()
	res.Cfg = r.cfg
	res.LCfg = r.lcfg
	res.SysinitCfg = r.sysinitCfg
	res.SysdownCfg = r.sysdownCfg
	res.PreBuildCmdCfg = r.preBuildCmdCfg
	res.PreLinkCmdCfg = r.preLinkCmdCfg
	res.PostLinkCmdCfg = r.postLinkCmdCfg

	warnMap := map[string]struct{}{}
	for _, lines := range r.parseWarnings {
		for _, line := range lines {
			warnMap[line] = struct{}{}
		}
	}
	for w, _ := range warnMap {
		res.ParseWarnings = append(res.ParseWarnings, w)
	}
	sort.Strings(res.ParseWarnings)

	// Determine which package satisfies each API and which APIs are
	// unsatisfied.
	res.ApiMap, res.UnsatisfiedApis = r.apiResolution()

	for api, m := range r.apiConflicts {
		c := ApiConflict{
			Api: api,
		}
		for rpkg, _ := range m {
			c.Pkgs = append(c.Pkgs, rpkg)
		}

		res.ApiConflicts = append(res.ApiConflicts, c)
	}

	res.LpkgRpkgMap = r.pkgMap

	res.MasterSet.Rpkgs = r.rpkgSlice()

	// We have now resolved all packages.  Emit all warnings.
	for _, warn := range res.ParseWarnings {
		lines := strings.Split(warn, "\n")
		for _, line := range lines {
			util.OneTimeWarning("%s", line)
		}
	}
	for _, rpkg := range res.MasterSet.Rpkgs {
		LogTransientWarning(rpkg.Lpkg)
	}

	// If there is no loader, then the set of all packages is just the app
	// packages.  We already resolved the necessary dependency information when
	// syscfg was calculated above.
	if loaderSeeds == nil {
		res.AppSet.Rpkgs = r.rpkgSlice()
		res.LoaderSet = nil
		if detectErr == true {
			res.Cfg.DetectErrors(flashMap)
		}
		return res, nil
	}

	// Otherwise, we need to resolve dependencies separately for:
	// 1. The set of loader packages, and
	// 2. The set of app packages.
	//
	// These need to be resolved separately so that it is possible later to
	// determine which packages need to be shared between loader and app.

	// It is OK if the app requires an API that is supplied by the loader.
	// Ensure each set of packages has access to the API-providers.
	for _, rpkg := range res.ApiMap {
		loaderSeeds = append(loaderSeeds, rpkg.Lpkg)
		appSeeds = append(appSeeds, rpkg.Lpkg)
	}

	// Resolve loader dependencies.
	r = newResolver(loaderSeeds, injectedSettings, flashMap)
	r.cfg = res.Cfg

	var err error

	res.LoaderSet.Rpkgs, err = r.resolveDeps()
	if err != nil {
		return nil, err
	}
	if err := res.LoaderSet.useMasterPkgs(); err != nil {
		return nil, err
	}

	// Resolve app dependencies.  The app automtically gets all the packages
	// from the loader except for the loader-app-package.
	for _, rpkg := range res.LoaderSet.Rpkgs {
		if rpkg.Lpkg.Type() != pkg.PACKAGE_TYPE_APP {
			appSeeds = append(appSeeds, rpkg.Lpkg)
		}
	}

	r = newResolver(appSeeds, injectedSettings, flashMap)
	r.cfg = res.Cfg

	res.AppSet.Rpkgs, err = r.resolveDeps()
	if err != nil {
		return nil, err
	}
	if err := res.AppSet.useMasterPkgs(); err != nil {
		return nil, err
	}

	if detectErr == true {
		res.Cfg.DetectErrors(flashMap)
	}

	return res, nil
}

func (res *Resolution) ErrorText() string {
	str := ""

	if len(res.UnsatisfiedApis) > 0 {
		apiNames := make([]string, 0, len(res.UnsatisfiedApis))
		for api, _ := range res.UnsatisfiedApis {
			apiNames = append(apiNames, api)
		}
		sort.Strings(apiNames)

		str += "Unsatisfied APIs detected:\n"
		for _, api := range apiNames {
			str += fmt.Sprintf("    * %s, required by: ", api)

			rpkgs := res.UnsatisfiedApis[api]
			pkgNames := make([]string, len(rpkgs))
			for i, rpkg := range rpkgs {
				pkgNames[i] = rpkg.Lpkg.Name()
			}
			sort.Strings(pkgNames)

			str += strings.Join(pkgNames, ", ")
			str += "\n"
		}
	}

	str += res.Cfg.ErrorText()
	str += res.LCfg.ErrorText()
	str += res.SysinitCfg.ErrorText()
	str += res.SysdownCfg.ErrorText()
	str += res.PreBuildCmdCfg.ErrorText()
	str += res.PreLinkCmdCfg.ErrorText()
	str += res.PostLinkCmdCfg.ErrorText()

	str = strings.TrimSpace(str)
	if str != "" {
		str += "\n"
	}

	return str
}

func (res *Resolution) WarningText() string {
	text := ""

	for _, c := range res.ApiConflicts {
		text += fmt.Sprintf("Warning: API conflict: %s (", c.Api)
		for i, rpkg := range c.Pkgs {
			if i != 0 {
				text += " <-> "
			}
			text += rpkg.Lpkg.Name()
		}
		text += ")\n"
	}

	return text + res.Cfg.WarningText()
}

func (res *Resolution) DeprecatedWarning() []string {
	return res.Cfg.DeprecatedWarning()
}

func (res *Resolution) CfgExperimentalWarning() []string {
	return res.Cfg.ExperimentalWarning()
}

func (res *Resolution) PkgExperimentalWarning() []string {
	lines := []string{}

	for pkg := range res.LpkgRpkgMap {
		experimental, err := pkg.PkgY.GetValBool("pkg.experimental", nil)
		if err != nil {
			log.Errorf("Internal error; Could not read package %s yml file", pkg.Name())
		}
		if experimental {
			lines = append(lines,
				fmt.Sprintf("Use of experimental package %s", pkg.Name()))
		}
	}

	return lines
}

func LogTransientWarning(lpkg *pkg.LocalPackage) {
	if lpkg.Type() == pkg.PACKAGE_TYPE_TRANSIENT {
		log.Warnf("Transient package %s used, update configuration "+
			"to use linked package instead (%s)",
			lpkg.FullName(), lpkg.LinkedName())
	}
}
