// Copyright 2014 Google Inc. All rights reserved.
//
// Licensed 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 blueprint

import (
	"fmt"
	"path/filepath"
	"sync"
	"text/scanner"

	"github.com/google/blueprint/pathtools"
	"github.com/google/blueprint/proptools"
)

// A Module handles generating all of the Ninja build actions needed to build a
// single module based on properties defined in a Blueprints file.  Module
// objects are initially created during the parse phase of a Context using one
// of the registered module types (and the associated ModuleFactory function).
// The Module's properties struct is automatically filled in with the property
// values specified in the Blueprints file (see Context.RegisterModuleType for more
// information on this).
//
// A Module can be split into multiple Modules by a Mutator.  All existing
// properties set on the module will be duplicated to the new Module, and then
// modified as necessary by the Mutator.
//
// The Module implementation can access the build configuration as well as any
// modules on which on which it depends (as defined by the "deps" property
// specified in the Blueprints file, dynamically added by implementing the
// (deprecated) DynamicDependerModule interface, or dynamically added by a
// BottomUpMutator) using the ModuleContext passed to GenerateBuildActions.
// This ModuleContext is also used to create Ninja build actions and to report
// errors to the user.
//
// In addition to implementing the GenerateBuildActions method, a Module should
// implement methods that provide dependant modules and singletons information
// they need to generate their build actions.  These methods will only be called
// after GenerateBuildActions is called because the Context calls
// GenerateBuildActions in dependency-order (and singletons are invoked after
// all the Modules).  The set of methods a Module supports will determine how
// dependant Modules interact with it.
//
// For example, consider a Module that is responsible for generating a library
// that other modules can link against.  The library Module might implement the
// following interface:
//
//   type LibraryProducer interface {
//       LibraryFileName() string
//   }
//
//   func IsLibraryProducer(module blueprint.Module) {
//       _, ok := module.(LibraryProducer)
//       return ok
//   }
//
// A binary-producing Module that depends on the library Module could then do:
//
//   func (m *myBinaryModule) GenerateBuildActions(ctx blueprint.ModuleContext) {
//       ...
//       var libraryFiles []string
//       ctx.VisitDepsDepthFirstIf(IsLibraryProducer,
//           func(module blueprint.Module) {
//               libProducer := module.(LibraryProducer)
//               libraryFiles = append(libraryFiles, libProducer.LibraryFileName())
//           })
//       ...
//   }
//
// to build the list of library file names that should be included in its link
// command.
//
// GenerateBuildActions may be called from multiple threads.  It is guaranteed to
// be called after it has finished being called on all dependencies and on all
// variants of that appear earlier in the ModuleContext.VisitAllModuleVariants list.
// Any accesses to global variables or to Module objects that are not dependencies
// or variants of the current Module must be synchronized by the implementation of
// GenerateBuildActions.
type Module interface {
	// Name returns a string used to uniquely identify each module.  The return
	// value must be unique across all modules.  It is only called once, during
	// initial blueprint parsing.  To change the name later a mutator must call
	// MutatorContext.Rename
	//
	// In most cases, Name should return the contents of a "name:" property from
	// the blueprint file.  An embeddable SimpleName object can be used for this
	// case.
	Name() string

	// GenerateBuildActions is called by the Context that created the Module
	// during its generate phase.  This call should generate all Ninja build
	// actions (rules, pools, and build statements) needed to build the module.
	GenerateBuildActions(ModuleContext)
}

// A DynamicDependerModule is a Module that may add dependencies that do not
// appear in its "deps" property.  Any Module that implements this interface
// will have its DynamicDependencies method called by the Context that created
// it during generate phase.
//
// Deprecated, use a BottomUpMutator instead
type DynamicDependerModule interface {
	Module

	// DynamicDependencies is called by the Context that created the
	// DynamicDependerModule during its generate phase.  This call should return
	// the list of module names that the DynamicDependerModule depends on
	// dynamically.  Module names that already appear in the "deps" property may
	// but do not need to be included in the returned list.
	DynamicDependencies(DynamicDependerModuleContext) []string
}

type EarlyModuleContext interface {
	// Module returns the current module as a Module.  It should rarely be necessary, as the module already has a
	// reference to itself.
	Module() Module

	// ModuleName returns the name of the module.  This is generally the value that was returned by Module.Name() when
	// the module was created, but may have been modified by calls to BaseMutatorContext.Rename.
	ModuleName() string

	// ModuleDir returns the path to the directory that contains the defintion of the module.
	ModuleDir() string

	// ModuleType returns the name of the module type that was used to create the module, as specified in
	// RegisterModuleType.
	ModuleType() string

	// BlueprintFile returns the name of the blueprint file that contains the definition of this
	// module.
	BlueprintsFile() string

	// Config returns the config object that was passed to Context.PrepareBuildActions.
	Config() interface{}

	// ContainsProperty returns true if the specified property name was set in the module definition.
	ContainsProperty(name string) bool

	// Errorf reports an error at the specified position of the module definition file.
	Errorf(pos scanner.Position, fmt string, args ...interface{})

	// ModuleErrorf reports an error at the line number of the module type in the module definition.
	ModuleErrorf(fmt string, args ...interface{})

	// PropertyErrorf reports an error at the line number of a property in the module definition.
	PropertyErrorf(property, fmt string, args ...interface{})

	// Failed returns true if any errors have been reported.  In most cases the module can continue with generating
	// build rules after an error, allowing it to report additional errors in a single run, but in cases where the error
	// has prevented the module from creating necessary data it can return early when Failed returns true.
	Failed() bool

	// GlobWithDeps returns a list of files and directories that match the
	// specified pattern but do not match any of the patterns in excludes.
	// Any directories will have a '/' suffix.  It also adds efficient
	// dependencies to rerun the primary builder whenever a file matching
	// the pattern as added or removed, without rerunning if a file that
	// does not match the pattern is added to a searched directory.
	GlobWithDeps(pattern string, excludes []string) ([]string, error)

	// Fs returns a pathtools.Filesystem that can be used to interact with files.  Using the Filesystem interface allows
	// the module to be used in build system tests that run against a mock filesystem.
	Fs() pathtools.FileSystem

	// AddNinjaFileDeps adds dependencies on the specified files to the rule that creates the ninja manifest.  The
	// primary builder will be rerun whenever the specified files are modified.
	AddNinjaFileDeps(deps ...string)

	moduleInfo() *moduleInfo
	error(err error)

	// Namespace returns the Namespace object provided by the NameInterface set by Context.SetNameInterface, or the
	// default SimpleNameInterface if Context.SetNameInterface was not called.
	Namespace() Namespace

	// ModuleFactories returns a map of all of the global ModuleFactories by name.
	ModuleFactories() map[string]ModuleFactory
}

type BaseModuleContext interface {
	EarlyModuleContext

	// GetDirectDepWithTag returns the Module the direct dependency with the specified name, or nil if
	// none exists.  It panics if the dependency does not have the specified tag.
	GetDirectDepWithTag(name string, tag DependencyTag) Module

	// GetDirectDep returns the Module and DependencyTag for the  direct dependency with the specified
	// name, or nil if none exists.  If there are multiple dependencies on the same module it returns
	// the first DependencyTag.
	GetDirectDep(name string) (Module, DependencyTag)

	// VisitDirectDeps calls visit for each direct dependency.  If there are multiple direct dependencies on the same
	// module visit will be called multiple times on that module and OtherModuleDependencyTag will return a different
	// tag for each.
	//
	// The Module passed to the visit function should not be retained outside of the visit function, it may be
	// invalidated by future mutators.
	VisitDirectDeps(visit func(Module))

	// VisitDirectDepsIf calls pred for each direct dependency, and if pred returns true calls visit.  If there are
	// multiple direct dependencies on the same module pred and visit will be called multiple times on that module and
	// OtherModuleDependencyTag will return a different tag for each.
	//
	// The Module passed to the visit function should not be retained outside of the visit function, it may be
	// invalidated by future mutators.
	VisitDirectDepsIf(pred func(Module) bool, visit func(Module))

	// VisitDepsDepthFirst calls visit for each transitive dependency, traversing the dependency tree in depth first
	// order. visit will only be called once for any given module, even if there are multiple paths through the
	// dependency tree to the module or multiple direct dependencies with different tags.  OtherModuleDependencyTag will
	// return the tag for the first path found to the module.
	//
	// The Module passed to the visit function should not be retained outside of the visit function, it may be
	// invalidated by future mutators.
	VisitDepsDepthFirst(visit func(Module))

	// VisitDepsDepthFirst calls pred for each transitive dependency, and if pred returns true calls visit, traversing
	// the dependency tree in depth first order.  visit will only be called once for any given module, even if there are
	// multiple paths through the dependency tree to the module or multiple direct dependencies with different tags.
	// OtherModuleDependencyTag will return the tag for the first path found to the module.  The return value of pred
	// does not affect which branches of the tree are traversed.
	//
	// The Module passed to the visit function should not be retained outside of the visit function, it may be
	// invalidated by future mutators.
	VisitDepsDepthFirstIf(pred func(Module) bool, visit func(Module))

	// WalkDeps calls visit for each transitive dependency, traversing the dependency tree in top down order.  visit may
	// be called multiple times for the same (child, parent) pair if there are multiple direct dependencies between the
	// child and parent with different tags.  OtherModuleDependencyTag will return the tag for the currently visited
	// (child, parent) pair.  If visit returns false WalkDeps will not continue recursing down to child.
	//
	// The Modules passed to the visit function should not be retained outside of the visit function, they may be
	// invalidated by future mutators.
	WalkDeps(visit func(Module, Module) bool)

	// OtherModuleName returns the name of another Module.  See BaseModuleContext.ModuleName for more information.
	// It is intended for use inside the visit functions of Visit* and WalkDeps.
	OtherModuleName(m Module) string

	// OtherModuleDir returns the directory of another Module.  See BaseModuleContext.ModuleDir for more information.
	// It is intended for use inside the visit functions of Visit* and WalkDeps.
	OtherModuleDir(m Module) string

	// OtherModuleSubDir returns the unique subdirectory name of another Module.  See ModuleContext.ModuleSubDir for
	// more information.
	// It is intended for use inside the visit functions of Visit* and WalkDeps.
	OtherModuleSubDir(m Module) string

	// OtherModuleType returns the type of another Module.  See BaseModuleContext.ModuleType for more information.
	// It is intended for use inside the visit functions of Visit* and WalkDeps.
	OtherModuleType(m Module) string

	// OtherModuleErrorf reports an error on another Module.  See BaseModuleContext.ModuleErrorf for more information.
	// It is intended for use inside the visit functions of Visit* and WalkDeps.
	OtherModuleErrorf(m Module, fmt string, args ...interface{})

	// OtherModuleDependencyTag returns the dependency tag used to depend on a module, or nil if there is no dependency
	// on the module.  When called inside a Visit* method with current module being visited, and there are multiple
	// dependencies on the module being visited, it returns the dependency tag used for the current dependency.
	OtherModuleDependencyTag(m Module) DependencyTag

	// OtherModuleExists returns true if a module with the specified name exists, as determined by the NameInterface
	// passed to Context.SetNameInterface, or SimpleNameInterface if it was not called.
	OtherModuleExists(name string) bool
}

type DynamicDependerModuleContext BottomUpMutatorContext

type ModuleContext interface {
	BaseModuleContext

	// ModuleSubDir returns a unique name for the current variant of a module that can be used as part of the path
	// to ensure that each variant of a module gets its own intermediates directory to write to.
	ModuleSubDir() string

	// Variable creates a new ninja variable scoped to the module.  It can be referenced by calls to Rule and Build
	// in the same module.
	Variable(pctx PackageContext, name, value string)

	// Rule creates a new ninja rule scoped to the module.  It can be referenced by calls to Build in the same module.
	Rule(pctx PackageContext, name string, params RuleParams, argNames ...string) Rule

	// Build creates a new ninja build statement.
	Build(pctx PackageContext, params BuildParams)

	// PrimaryModule returns the first variant of the current module.  Variants of a module are always visited in
	// order by mutators and GenerateBuildActions, so the data created by the current mutator can be read from the
	// Module returned by PrimaryModule without data races.  This can be used to perform singleton actions that are
	// only done once for all variants of a module.
	PrimaryModule() Module

	// FinalModule returns the last variant of the current module.  Variants of a module are always visited in
	// order by mutators and GenerateBuildActions, so the data created by the current mutator can be read from all
	// variants using VisitAllModuleVariants if the current module == FinalModule().  This can be used to perform
	// singleton actions that are only done once for all variants of a module.
	FinalModule() Module

	// VisitAllModuleVariants calls visit for each variant of the current module.  Variants of a module are always
	// visited in order by mutators and GenerateBuildActions, so the data created by the current mutator can be read
	// from all variants if the current module == FinalModule().  Otherwise, care must be taken to not access any
	// data modified by the current mutator.
	VisitAllModuleVariants(visit func(Module))

	// GetMissingDependencies returns the list of dependencies that were passed to AddDependencies or related methods,
	// but do not exist.  It can be used with Context.SetAllowMissingDependencies to allow the primary builder to
	// handle missing dependencies on its own instead of having Blueprint treat them as an error.
	GetMissingDependencies() []string
}

var _ BaseModuleContext = (*baseModuleContext)(nil)

type baseModuleContext struct {
	context        *Context
	config         interface{}
	module         *moduleInfo
	errs           []error
	visitingParent *moduleInfo
	visitingDep    depInfo
	ninjaFileDeps  []string
}

func (d *baseModuleContext) moduleInfo() *moduleInfo {
	return d.module
}

func (d *baseModuleContext) Module() Module {
	return d.module.logicModule
}

func (d *baseModuleContext) ModuleName() string {
	return d.module.Name()
}

func (d *baseModuleContext) ModuleType() string {
	return d.module.typeName
}

func (d *baseModuleContext) ContainsProperty(name string) bool {
	_, ok := d.module.propertyPos[name]
	return ok
}

func (d *baseModuleContext) ModuleDir() string {
	return filepath.Dir(d.module.relBlueprintsFile)
}

func (d *baseModuleContext) BlueprintsFile() string {
	return d.module.relBlueprintsFile
}

func (d *baseModuleContext) Config() interface{} {
	return d.config
}

func (d *baseModuleContext) error(err error) {
	if err != nil {
		d.errs = append(d.errs, err)
	}
}

func (d *baseModuleContext) Errorf(pos scanner.Position,
	format string, args ...interface{}) {

	d.error(&BlueprintError{
		Err: fmt.Errorf(format, args...),
		Pos: pos,
	})
}

func (d *baseModuleContext) ModuleErrorf(format string,
	args ...interface{}) {

	d.error(&ModuleError{
		BlueprintError: BlueprintError{
			Err: fmt.Errorf(format, args...),
			Pos: d.module.pos,
		},
		module: d.module,
	})
}

func (d *baseModuleContext) PropertyErrorf(property, format string,
	args ...interface{}) {

	pos := d.module.propertyPos[property]

	if !pos.IsValid() {
		pos = d.module.pos
	}

	d.error(&PropertyError{
		ModuleError: ModuleError{
			BlueprintError: BlueprintError{
				Err: fmt.Errorf(format, args...),
				Pos: pos,
			},
			module: d.module,
		},
		property: property,
	})
}

func (d *baseModuleContext) Failed() bool {
	return len(d.errs) > 0
}

func (d *baseModuleContext) GlobWithDeps(pattern string,
	excludes []string) ([]string, error) {
	return d.context.glob(pattern, excludes)
}

func (d *baseModuleContext) Fs() pathtools.FileSystem {
	return d.context.fs
}

func (d *baseModuleContext) Namespace() Namespace {
	return d.context.nameInterface.GetNamespace(newNamespaceContext(d.module))
}

var _ ModuleContext = (*moduleContext)(nil)

type moduleContext struct {
	baseModuleContext
	scope              *localScope
	actionDefs         localBuildActions
	handledMissingDeps bool
}

func (m *baseModuleContext) OtherModuleName(logicModule Module) string {
	module := m.context.moduleInfo[logicModule]
	return module.Name()
}

func (m *baseModuleContext) OtherModuleDir(logicModule Module) string {
	module := m.context.moduleInfo[logicModule]
	return filepath.Dir(module.relBlueprintsFile)
}

func (m *baseModuleContext) OtherModuleSubDir(logicModule Module) string {
	module := m.context.moduleInfo[logicModule]
	return module.variantName
}

func (m *baseModuleContext) OtherModuleType(logicModule Module) string {
	module := m.context.moduleInfo[logicModule]
	return module.typeName
}

func (m *baseModuleContext) OtherModuleErrorf(logicModule Module, format string,
	args ...interface{}) {

	module := m.context.moduleInfo[logicModule]
	m.errs = append(m.errs, &ModuleError{
		BlueprintError: BlueprintError{
			Err: fmt.Errorf(format, args...),
			Pos: module.pos,
		},
		module: module,
	})
}

func (m *baseModuleContext) OtherModuleDependencyTag(logicModule Module) DependencyTag {
	// fast path for calling OtherModuleDependencyTag from inside VisitDirectDeps
	if logicModule == m.visitingDep.module.logicModule {
		return m.visitingDep.tag
	}

	for _, dep := range m.visitingParent.directDeps {
		if dep.module.logicModule == logicModule {
			return dep.tag
		}
	}

	return nil
}

func (m *baseModuleContext) OtherModuleExists(name string) bool {
	_, exists := m.context.nameInterface.ModuleFromName(name, m.module.namespace())
	return exists
}

func (m *baseModuleContext) GetDirectDep(name string) (Module, DependencyTag) {
	for _, dep := range m.module.directDeps {
		if dep.module.Name() == name {
			return dep.module.logicModule, dep.tag
		}
	}

	return nil, nil
}

func (m *baseModuleContext) GetDirectDepWithTag(name string, tag DependencyTag) Module {
	var deps []depInfo
	for _, dep := range m.module.directDeps {
		if dep.module.Name() == name {
			if dep.tag == tag {
				return dep.module.logicModule
			}
			deps = append(deps, dep)
		}
	}

	if len(deps) != 0 {
		panic(fmt.Errorf("Unable to find dependency %q with requested tag %#v. Found: %#v", deps[0].module, tag, deps))
	}

	return nil
}

func (m *baseModuleContext) VisitDirectDeps(visit func(Module)) {
	defer func() {
		if r := recover(); r != nil {
			panic(newPanicErrorf(r, "VisitDirectDeps(%s, %s) for dependency %s",
				m.module, funcName(visit), m.visitingDep.module))
		}
	}()

	m.visitingParent = m.module

	for _, dep := range m.module.directDeps {
		m.visitingDep = dep
		visit(dep.module.logicModule)
	}

	m.visitingParent = nil
	m.visitingDep = depInfo{}
}

func (m *baseModuleContext) VisitDirectDepsIf(pred func(Module) bool, visit func(Module)) {
	defer func() {
		if r := recover(); r != nil {
			panic(newPanicErrorf(r, "VisitDirectDepsIf(%s, %s, %s) for dependency %s",
				m.module, funcName(pred), funcName(visit), m.visitingDep.module))
		}
	}()

	m.visitingParent = m.module

	for _, dep := range m.module.directDeps {
		m.visitingDep = dep
		if pred(dep.module.logicModule) {
			visit(dep.module.logicModule)
		}
	}

	m.visitingParent = nil
	m.visitingDep = depInfo{}
}

func (m *baseModuleContext) VisitDepsDepthFirst(visit func(Module)) {
	defer func() {
		if r := recover(); r != nil {
			panic(newPanicErrorf(r, "VisitDepsDepthFirst(%s, %s) for dependency %s",
				m.module, funcName(visit), m.visitingDep.module))
		}
	}()

	m.context.walkDeps(m.module, false, nil, func(dep depInfo, parent *moduleInfo) {
		m.visitingParent = parent
		m.visitingDep = dep
		visit(dep.module.logicModule)
	})

	m.visitingParent = nil
	m.visitingDep = depInfo{}
}

func (m *baseModuleContext) VisitDepsDepthFirstIf(pred func(Module) bool,
	visit func(Module)) {

	defer func() {
		if r := recover(); r != nil {
			panic(newPanicErrorf(r, "VisitDepsDepthFirstIf(%s, %s, %s) for dependency %s",
				m.module, funcName(pred), funcName(visit), m.visitingDep.module))
		}
	}()

	m.context.walkDeps(m.module, false, nil, func(dep depInfo, parent *moduleInfo) {
		if pred(dep.module.logicModule) {
			m.visitingParent = parent
			m.visitingDep = dep
			visit(dep.module.logicModule)
		}
	})

	m.visitingParent = nil
	m.visitingDep = depInfo{}
}

func (m *baseModuleContext) WalkDeps(visit func(child, parent Module) bool) {
	m.context.walkDeps(m.module, true, func(dep depInfo, parent *moduleInfo) bool {
		m.visitingParent = parent
		m.visitingDep = dep
		return visit(dep.module.logicModule, parent.logicModule)
	}, nil)

	m.visitingParent = nil
	m.visitingDep = depInfo{}
}

func (m *baseModuleContext) AddNinjaFileDeps(deps ...string) {
	m.ninjaFileDeps = append(m.ninjaFileDeps, deps...)
}

func (m *baseModuleContext) ModuleFactories() map[string]ModuleFactory {
	ret := make(map[string]ModuleFactory)
	for k, v := range m.context.moduleFactories {
		ret[k] = v
	}
	return ret
}

func (m *moduleContext) ModuleSubDir() string {
	return m.module.variantName
}

func (m *moduleContext) Variable(pctx PackageContext, name, value string) {
	m.scope.ReparentTo(pctx)

	v, err := m.scope.AddLocalVariable(name, value)
	if err != nil {
		panic(err)
	}

	m.actionDefs.variables = append(m.actionDefs.variables, v)
}

func (m *moduleContext) Rule(pctx PackageContext, name string,
	params RuleParams, argNames ...string) Rule {

	m.scope.ReparentTo(pctx)

	r, err := m.scope.AddLocalRule(name, &params, argNames...)
	if err != nil {
		panic(err)
	}

	m.actionDefs.rules = append(m.actionDefs.rules, r)

	return r
}

func (m *moduleContext) Build(pctx PackageContext, params BuildParams) {
	m.scope.ReparentTo(pctx)

	def, err := parseBuildParams(m.scope, &params)
	if err != nil {
		panic(err)
	}

	m.actionDefs.buildDefs = append(m.actionDefs.buildDefs, def)
}

func (m *moduleContext) PrimaryModule() Module {
	return m.module.group.modules[0].logicModule
}

func (m *moduleContext) FinalModule() Module {
	return m.module.group.modules[len(m.module.group.modules)-1].logicModule
}

func (m *moduleContext) VisitAllModuleVariants(visit func(Module)) {
	m.context.visitAllModuleVariants(m.module, visit)
}

func (m *moduleContext) GetMissingDependencies() []string {
	m.handledMissingDeps = true
	return m.module.missingDeps
}

//
// MutatorContext
//

type mutatorContext struct {
	baseModuleContext
	name             string
	reverseDeps      []reverseDep
	rename           []rename
	replace          []replace
	newVariations    []*moduleInfo // new variants of existing modules
	newModules       []*moduleInfo // brand new modules
	defaultVariation *string
}

type BaseMutatorContext interface {
	BaseModuleContext

	// Rename all variants of a module.  The new name is not visible to calls to ModuleName,
	// AddDependency or OtherModuleName until after this mutator pass is complete.
	Rename(name string)

	// MutatorName returns the name that this mutator was registered with.
	MutatorName() string
}

type EarlyMutatorContext interface {
	BaseMutatorContext

	// CreateVariations splits  a module into mulitple variants, one for each name in the variationNames
	// parameter.  It returns a list of new modules in the same order as the variationNames
	// list.
	//
	// If any of the dependencies of the module being operated on were already split
	// by calling CreateVariations with the same name, the dependency will automatically
	// be updated to point the matching variant.
	//
	// If a module is split, and then a module depending on the first module is not split
	// when the Mutator is later called on it, the dependency of the depending module will
	// automatically be updated to point to the first variant.
	CreateVariations(...string) []Module

	// CreateLocationVariations splits a module into mulitple variants, one for each name in the variantNames
	// parameter.  It returns a list of new modules in the same order as the variantNames
	// list.
	//
	// Local variations do not affect automatic dependency resolution - dependencies added
	// to the split module via deps or DynamicDependerModule must exactly match a variant
	// that contains all the non-local variations.
	CreateLocalVariations(...string) []Module
}

type TopDownMutatorContext interface {
	BaseMutatorContext

	// CreateModule creates a new module by calling the factory method for the specified moduleType, and applies
	// the specified property structs to it as if the properties were set in a blueprint file.
	CreateModule(ModuleFactory, ...interface{}) Module
}

type BottomUpMutatorContext interface {
	BaseMutatorContext

	// AddDependency adds a dependency to the given module.
	// Does not affect the ordering of the current mutator pass, but will be ordered
	// correctly for all future mutator passes.
	AddDependency(module Module, tag DependencyTag, name ...string)

	// AddReverseDependency adds a dependency from the destination to the given module.
	// Does not affect the ordering of the current mutator pass, but will be ordered
	// correctly for all future mutator passes.  All reverse dependencies for a destination module are
	// collected until the end of the mutator pass, sorted by name, and then appended to the destination
	// module's dependency list.
	AddReverseDependency(module Module, tag DependencyTag, name string)

	// CreateVariations splits  a module into mulitple variants, one for each name in the variationNames
	// parameter.  It returns a list of new modules in the same order as the variationNames
	// list.
	//
	// If any of the dependencies of the module being operated on were already split
	// by calling CreateVariations with the same name, the dependency will automatically
	// be updated to point the matching variant.
	//
	// If a module is split, and then a module depending on the first module is not split
	// when the Mutator is later called on it, the dependency of the depending module will
	// automatically be updated to point to the first variant.
	CreateVariations(...string) []Module

	// CreateLocationVariations splits a module into mulitple variants, one for each name in the variantNames
	// parameter.  It returns a list of new modules in the same order as the variantNames
	// list.
	//
	// Local variations do not affect automatic dependency resolution - dependencies added
	// to the split module via deps or DynamicDependerModule must exactly match a variant
	// that contains all the non-local variations.
	CreateLocalVariations(...string) []Module

	// SetDependencyVariation sets all dangling dependencies on the current module to point to the variation
	// with given name. This function ignores the default variation set by SetDefaultDependencyVariation.
	SetDependencyVariation(string)

	// SetDefaultDependencyVariation sets the default variation when a dangling reference is detected
	// during the subsequent calls on Create*Variations* functions. To reset, set it to nil.
	SetDefaultDependencyVariation(*string)

	// AddVariationDependencies adds deps as dependencies of the current module, but uses the variations
	// argument to select which variant of the dependency to use.  A variant of the dependency must
	// exist that matches the all of the non-local variations of the current module, plus the variations
	// argument.
	AddVariationDependencies([]Variation, DependencyTag, ...string)

	// AddFarVariationDependencies adds deps as dependencies of the current module, but uses the
	// variations argument to select which variant of the dependency to use.  A variant of the
	// dependency must exist that matches the variations argument, but may also have other variations.
	// For any unspecified variation the first variant will be used.
	//
	// Unlike AddVariationDependencies, the variations of the current module are ignored - the
	// dependency only needs to match the supplied variations.
	AddFarVariationDependencies([]Variation, DependencyTag, ...string)

	// AddInterVariantDependency adds a dependency between two variants of the same module.  Variants are always
	// ordered in the same orderas they were listed in CreateVariations, and AddInterVariantDependency does not change
	// that ordering, but it associates a DependencyTag with the dependency and makes it visible to VisitDirectDeps,
	// WalkDeps, etc.
	AddInterVariantDependency(tag DependencyTag, from, to Module)

	// ReplaceDependencies replaces all dependencies on the identical variant of the module with the
	// specified name with the current variant of this module.  Replacements don't take effect until
	// after the mutator pass is finished.
	ReplaceDependencies(string)

	// AliasVariation takes a variationName that was passed to CreateVariations for this module, and creates an
	// alias from the current variant to the new variant.  The alias will be valid until the next time a mutator
	// calls CreateVariations or CreateLocalVariations on this module without also calling AliasVariation.  The
	// alias can be used to add dependencies on the newly created variant using the variant map from before
	// CreateVariations was run.
	AliasVariation(variationName string)
}

// A Mutator function is called for each Module, and can use
// MutatorContext.CreateVariations to split a Module into multiple Modules,
// modifying properties on the new modules to differentiate them.  It is called
// after parsing all Blueprint files, but before generating any build rules,
// and is always called on dependencies before being called on the depending module.
//
// The Mutator function should only modify members of properties structs, and not
// members of the module struct itself, to ensure the modified values are copied
// if a second Mutator chooses to split the module a second time.
type TopDownMutator func(mctx TopDownMutatorContext)
type BottomUpMutator func(mctx BottomUpMutatorContext)
type EarlyMutator func(mctx EarlyMutatorContext)

// DependencyTag is an interface to an arbitrary object that embeds BaseDependencyTag.  It can be
// used to transfer information on a dependency between the mutator that called AddDependency
// and the GenerateBuildActions method.  Variants created by CreateVariations have a copy of the
// interface (pointing to the same concrete object) from their original module.
type DependencyTag interface {
	dependencyTag(DependencyTag)
}

type BaseDependencyTag struct {
}

func (BaseDependencyTag) dependencyTag(DependencyTag) {
}

var _ DependencyTag = BaseDependencyTag{}

func (mctx *mutatorContext) MutatorName() string {
	return mctx.name
}

func (mctx *mutatorContext) CreateVariations(variationNames ...string) []Module {
	return mctx.createVariations(variationNames, false)
}

func (mctx *mutatorContext) CreateLocalVariations(variationNames ...string) []Module {
	return mctx.createVariations(variationNames, true)
}

func (mctx *mutatorContext) createVariations(variationNames []string, local bool) []Module {
	ret := []Module{}
	modules, errs := mctx.context.createVariations(mctx.module, mctx.name, mctx.defaultVariation, variationNames)
	if len(errs) > 0 {
		mctx.errs = append(mctx.errs, errs...)
	}

	for i, module := range modules {
		ret = append(ret, module.logicModule)
		if !local {
			if module.dependencyVariant == nil {
				module.dependencyVariant = make(variationMap)
			}
			module.dependencyVariant[mctx.name] = variationNames[i]
		}
	}

	if mctx.newVariations != nil {
		panic("module already has variations from this mutator")
	}
	mctx.newVariations = modules

	if len(ret) != len(variationNames) {
		panic("oops!")
	}

	return ret
}

func (mctx *mutatorContext) AliasVariation(variationName string) {
	if mctx.module.aliasTarget != nil {
		panic(fmt.Errorf("AliasVariation already called"))
	}

	for _, variant := range mctx.newVariations {
		if variant.variant[mctx.name] == variationName {
			mctx.module.aliasTarget = variant
			return
		}
	}

	var foundVariations []string
	for _, variant := range mctx.newVariations {
		foundVariations = append(foundVariations, variant.variant[mctx.name])
	}
	panic(fmt.Errorf("no %q variation in module variations %q", variationName, foundVariations))
}

func (mctx *mutatorContext) SetDependencyVariation(variationName string) {
	mctx.context.convertDepsToVariation(mctx.module, mctx.name, variationName, nil)
}

func (mctx *mutatorContext) SetDefaultDependencyVariation(variationName *string) {
	mctx.defaultVariation = variationName
}

func (mctx *mutatorContext) Module() Module {
	return mctx.module.logicModule
}

func (mctx *mutatorContext) AddDependency(module Module, tag DependencyTag, deps ...string) {
	for _, dep := range deps {
		modInfo := mctx.context.moduleInfo[module]
		errs := mctx.context.addDependency(modInfo, tag, dep)
		if len(errs) > 0 {
			mctx.errs = append(mctx.errs, errs...)
		}
	}
}

func (mctx *mutatorContext) AddReverseDependency(module Module, tag DependencyTag, destName string) {
	if _, ok := tag.(BaseDependencyTag); ok {
		panic("BaseDependencyTag is not allowed to be used directly!")
	}

	destModule, errs := mctx.context.findReverseDependency(mctx.context.moduleInfo[module], destName)
	if len(errs) > 0 {
		mctx.errs = append(mctx.errs, errs...)
		return
	}

	mctx.reverseDeps = append(mctx.reverseDeps, reverseDep{
		destModule,
		depInfo{mctx.context.moduleInfo[module], tag},
	})
}

func (mctx *mutatorContext) AddVariationDependencies(variations []Variation, tag DependencyTag,
	deps ...string) {

	for _, dep := range deps {
		errs := mctx.context.addVariationDependency(mctx.module, variations, tag, dep, false)
		if len(errs) > 0 {
			mctx.errs = append(mctx.errs, errs...)
		}
	}
}

func (mctx *mutatorContext) AddFarVariationDependencies(variations []Variation, tag DependencyTag,
	deps ...string) {

	for _, dep := range deps {
		errs := mctx.context.addVariationDependency(mctx.module, variations, tag, dep, true)
		if len(errs) > 0 {
			mctx.errs = append(mctx.errs, errs...)
		}
	}
}

func (mctx *mutatorContext) AddInterVariantDependency(tag DependencyTag, from, to Module) {
	mctx.context.addInterVariantDependency(mctx.module, tag, from, to)
}

func (mctx *mutatorContext) ReplaceDependencies(name string) {
	target := mctx.context.moduleMatchingVariant(mctx.module, name)

	if target == nil {
		panic(fmt.Errorf("ReplaceDependencies could not find identical variant %q for module %q",
			mctx.module.variantName, name))
	}

	mctx.replace = append(mctx.replace, replace{target, mctx.module})
}

func (mctx *mutatorContext) Rename(name string) {
	mctx.rename = append(mctx.rename, rename{mctx.module.group, name})
}

func (mctx *mutatorContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
	module := mctx.context.newModule(factory)

	module.relBlueprintsFile = mctx.module.relBlueprintsFile
	module.pos = mctx.module.pos
	module.propertyPos = mctx.module.propertyPos
	module.createdBy = mctx.module

	for _, p := range props {
		err := proptools.AppendMatchingProperties(module.properties, p, nil)
		if err != nil {
			panic(err)
		}
	}

	mctx.newModules = append(mctx.newModules, module)

	return module.logicModule
}

// SimpleName is an embeddable object to implement the ModuleContext.Name method using a property
// called "name".  Modules that embed it must also add SimpleName.Properties to their property
// structure list.
type SimpleName struct {
	Properties struct {
		Name string
	}
}

func (s *SimpleName) Name() string {
	return s.Properties.Name
}

// Load Hooks

type LoadHookContext interface {
	EarlyModuleContext

	// CreateModule creates a new module by calling the factory method for the specified moduleType, and applies
	// the specified property structs to it as if the properties were set in a blueprint file.
	CreateModule(ModuleFactory, ...interface{}) Module

	// RegisterScopedModuleType creates a new module type that is scoped to the current Blueprints
	// file.
	RegisterScopedModuleType(name string, factory ModuleFactory)
}

func (l *loadHookContext) CreateModule(factory ModuleFactory, props ...interface{}) Module {
	module := l.context.newModule(factory)

	module.relBlueprintsFile = l.module.relBlueprintsFile
	module.pos = l.module.pos
	module.propertyPos = l.module.propertyPos
	module.createdBy = l.module

	for _, p := range props {
		err := proptools.AppendMatchingProperties(module.properties, p, nil)
		if err != nil {
			panic(err)
		}
	}

	l.newModules = append(l.newModules, module)

	return module.logicModule
}

func (l *loadHookContext) RegisterScopedModuleType(name string, factory ModuleFactory) {
	if _, exists := l.context.moduleFactories[name]; exists {
		panic(fmt.Errorf("A global module type named %q already exists", name))
	}

	if _, exists := (*l.scopedModuleFactories)[name]; exists {
		panic(fmt.Errorf("A module type named %q already exists in this scope", name))
	}

	if *l.scopedModuleFactories == nil {
		(*l.scopedModuleFactories) = make(map[string]ModuleFactory)
	}

	(*l.scopedModuleFactories)[name] = factory
}

type loadHookContext struct {
	baseModuleContext
	newModules            []*moduleInfo
	scopedModuleFactories *map[string]ModuleFactory
}

type LoadHook func(ctx LoadHookContext)

// Load hooks need to be added by module factories, which don't have any parameter to get to the
// Context, and only produce a Module interface with no base implementation, so the load hooks
// must be stored in a global map.  The key is a pointer allocated by the module factory, so there
// is no chance of collisions even if tests are running in parallel with multiple contexts.  The
// contents should be short-lived, they are added during a module factory and removed immediately
// after the module factory returns.
var pendingHooks sync.Map

func AddLoadHook(module Module, hook LoadHook) {
	// Only one goroutine can be processing a given module, so no additional locking is required
	// for the slice stored in the sync.Map.
	v, exists := pendingHooks.Load(module)
	if !exists {
		v, _ = pendingHooks.LoadOrStore(module, new([]LoadHook))
	}
	hooks := v.(*[]LoadHook)
	*hooks = append(*hooks, hook)
}

func runAndRemoveLoadHooks(ctx *Context, config interface{}, module *moduleInfo,
	scopedModuleFactories *map[string]ModuleFactory) (newModules []*moduleInfo, errs []error) {

	if v, exists := pendingHooks.Load(module.logicModule); exists {
		hooks := v.(*[]LoadHook)
		mctx := &loadHookContext{
			baseModuleContext: baseModuleContext{
				context: ctx,
				config:  config,
				module:  module,
			},
			scopedModuleFactories: scopedModuleFactories,
		}

		for _, hook := range *hooks {
			hook(mctx)
			newModules = append(newModules, mctx.newModules...)
			errs = append(errs, mctx.errs...)
		}
		pendingHooks.Delete(module.logicModule)

		return newModules, errs
	}

	return nil, nil
}
