// Copyright 2024 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 android

import "github.com/google/blueprint"

// TransitionMutator implements a top-down mechanism where a module tells its
// direct dependencies what variation they should be built in but the dependency
// has the final say.
//
// When implementing a transition mutator, one needs to implement four methods:
//   - Split() that tells what variations a module has by itself
//   - OutgoingTransition() where a module tells what it wants from its
//     dependency
//   - IncomingTransition() where a module has the final say about its own
//     variation
//   - Mutate() that changes the state of a module depending on its variation
//
// That the effective variation of module B when depended on by module A is the
// composition the outgoing transition of module A and the incoming transition
// of module B.
//
// the outgoing transition should not take the properties of the dependency into
// account, only those of the module that depends on it. For this reason, the
// dependency is not even passed into it as an argument. Likewise, the incoming
// transition should not take the properties of the depending module into
// account and is thus not informed about it. This makes for a nice
// decomposition of the decision logic.
//
// A given transition mutator only affects its own variation; other variations
// stay unchanged along the dependency edges.
//
// Soong makes sure that all modules are created in the desired variations and
// that dependency edges are set up correctly. This ensures that "missing
// variation" errors do not happen and allows for more flexible changes in the
// value of the variation among dependency edges (as oppposed to bottom-up
// mutators where if module A in variation X depends on module B and module B
// has that variation X, A must depend on variation X of B)
//
// The limited power of the context objects passed to individual mutators
// methods also makes it more difficult to shoot oneself in the foot. Complete
// safety is not guaranteed because no one prevents individual transition
// mutators from mutating modules in illegal ways and for e.g. Split() or
// Mutate() to run their own visitations of the transitive dependency of the
// module and both of these are bad ideas, but it's better than no guardrails at
// all.
//
// This model is pretty close to Bazel's configuration transitions. The mapping
// between concepts in Soong and Bazel is as follows:
//   - Module == configured target
//   - Variant == configuration
//   - Variation name == configuration flag
//   - Variation == configuration flag value
//   - Outgoing transition == attribute transition
//   - Incoming transition == rule transition
//
// The Split() method does not have a Bazel equivalent and Bazel split
// transitions do not have a Soong equivalent.
//
// Mutate() does not make sense in Bazel due to the different models of the
// two systems: when creating new variations, Soong clones the old module and
// thus some way is needed to change it state whereas Bazel creates each
// configuration of a given configured target anew.
type TransitionMutator[T blueprint.TransitionInfo] interface {
	// Split returns the set of variations that should be created for a module no
	// matter who depends on it. Used when Make depends on a particular variation
	// or when the module knows its variations just based on information given to
	// it in the Blueprint file. This method should not mutate the module it is
	// called on.
	Split(ctx BaseModuleContext) []T

	// OutgoingTransition is called on a module to determine which variation it wants
	// from its direct dependencies. The dependency itself can override this decision.
	// This method should not mutate the module itself.
	OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo T) T

	// IncomingTransition is called on a module to determine which variation it should
	// be in based on the variation modules that depend on it want. This gives the module
	// a final say about its own variations. This method should not mutate the module
	// itself.
	IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo T) T

	// Mutate is called after a module was split into multiple variations on each variation.
	// It should not split the module any further but adding new dependencies is
	// fine. Unlike all the other methods on TransitionMutator, this method is
	// allowed to mutate the module.
	Mutate(ctx BottomUpMutatorContext, transitionInfo T)

	// TransitionInfoFromVariation is called when adding dependencies with an explicit variation after the
	// TransitionMutator has already run.  It takes a variation name and returns a TransitionInfo for that
	// variation.  It may not be possible for some TransitionMutators to generate an appropriate TransitionInfo
	// if the variation does not contain all the information from the TransitionInfo, in which case the
	// TransitionMutator can panic in TransitionInfoFromVariation, and adding dependencies with explicit variations
	// for this TransitionMutator is not supported.
	TransitionInfoFromVariation(variation string) T
}

// androidTransitionMutator is a copy of blueprint.TransitionMutator with the context argument types changed
// from blueprint.BaseModuleContext to BaseModuleContext, etc.
type androidTransitionMutator interface {
	Split(ctx BaseModuleContext) []blueprint.TransitionInfo
	OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo
	IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo
	Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo)
	TransitionInfoFromVariation(variation string) blueprint.TransitionInfo
}

// VariationTransitionMutator is a simpler version of androidTransitionMutator that passes variation strings instead
// of a blueprint.TransitionInfo object.
type VariationTransitionMutator interface {
	Split(ctx BaseModuleContext) []string
	OutgoingTransition(ctx OutgoingTransitionContext, sourceVariation string) string
	IncomingTransition(ctx IncomingTransitionContext, incomingVariation string) string
	Mutate(ctx BottomUpMutatorContext, variation string)
}

type IncomingTransitionContext interface {
	ArchModuleContext
	ModuleProviderContext
	ModuleErrorContext

	// Module returns the target of the dependency edge for which the transition
	// is being computed
	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 BottomUpMutatorContext.Rename.
	ModuleName() string

	// DepTag() Returns the dependency tag through which this dependency is
	// reached
	DepTag() blueprint.DependencyTag

	// Config returns the configuration for the build.
	Config() Config

	DeviceConfig() DeviceConfig

	// IsAddingDependency returns true if the transition is being called while adding a dependency
	// after the transition mutator has already run, or false if it is being called when the transition
	// mutator is running.  This should be used sparingly, all uses will have to be removed in order
	// to support creating variants on demand.
	IsAddingDependency() bool

	// HasMutatorFinished returns true if the given mutator has finished running.
	// It will panic if given an invalid mutator name.
	HasMutatorFinished(mutatorName string) bool

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

type OutgoingTransitionContext interface {
	ArchModuleContext
	ModuleProviderContext

	// Module returns the target of the dependency edge for which the transition
	// is being computed
	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 BottomUpMutatorContext.Rename.
	ModuleName() string

	// DepTag() Returns the dependency tag through which this dependency is
	// reached
	DepTag() blueprint.DependencyTag

	// Config returns the configuration for the build.
	Config() Config

	DeviceConfig() DeviceConfig
}

// androidTransitionMutatorAdapter wraps an androidTransitionMutator to convert it to a blueprint.TransitionInfo
// by converting the blueprint.*Context objects into android.*Context objects.
type androidTransitionMutatorAdapter struct {
	finalPhase bool
	mutator    androidTransitionMutator
	name       string
}

func (a *androidTransitionMutatorAdapter) Split(ctx blueprint.BaseModuleContext) []blueprint.TransitionInfo {
	if a.finalPhase {
		panic("TransitionMutator not allowed in FinalDepsMutators")
	}
	m := ctx.Module().(Module)
	moduleContext := baseModuleContextPool.Get()
	defer baseModuleContextPool.Put(moduleContext)
	*moduleContext = m.base().baseModuleContextFactory(ctx)
	return a.mutator.Split(moduleContext)
}

func (a *androidTransitionMutatorAdapter) OutgoingTransition(bpctx blueprint.OutgoingTransitionContext,
	sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
	m := bpctx.Module().(Module)
	ctx := outgoingTransitionContextPool.Get()
	defer outgoingTransitionContextPool.Put(ctx)
	*ctx = outgoingTransitionContextImpl{
		archModuleContext: m.base().archModuleContextFactory(bpctx),
		bp:                bpctx,
	}
	return a.mutator.OutgoingTransition(ctx, sourceTransitionInfo)
}

func (a *androidTransitionMutatorAdapter) IncomingTransition(bpctx blueprint.IncomingTransitionContext,
	incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
	m := bpctx.Module().(Module)
	ctx := incomingTransitionContextPool.Get()
	defer incomingTransitionContextPool.Put(ctx)
	*ctx = incomingTransitionContextImpl{
		archModuleContext: m.base().archModuleContextFactory(bpctx),
		bp:                bpctx,
	}
	return a.mutator.IncomingTransition(ctx, incomingTransitionInfo)
}

func (a *androidTransitionMutatorAdapter) Mutate(ctx blueprint.BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
	am := ctx.Module().(Module)
	variation := transitionInfo.Variation()
	if variation != "" {
		// TODO: this should really be checking whether the TransitionMutator affected this module, not
		//  the empty variant, but TransitionMutator has no concept of skipping a module.
		base := am.base()
		base.commonProperties.DebugMutators = append(base.commonProperties.DebugMutators, a.name)
		base.commonProperties.DebugVariations = append(base.commonProperties.DebugVariations, variation)
	}
	if config := ctx.Config().(Config); config.captureBuild {
		config.modulesForTests.Insert(ctx.ModuleName(), am)
	}

	mctx := bottomUpMutatorContextFactory(ctx, am, a.finalPhase)
	defer bottomUpMutatorContextPool.Put(mctx)
	a.mutator.Mutate(mctx, transitionInfo)
}

func (a *androidTransitionMutatorAdapter) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
	return a.mutator.TransitionInfoFromVariation(variation)
}

// variationTransitionMutatorAdapter wraps a VariationTransitionMutator to convert it to an androidTransitionMutator
// by wrapping the string info object used by VariationTransitionMutator with variationTransitionInfo to convert it into
// blueprint.TransitionInfo.
type variationTransitionMutatorAdapter struct {
	m VariationTransitionMutator
}

func (v variationTransitionMutatorAdapter) Split(ctx BaseModuleContext) []blueprint.TransitionInfo {
	variations := v.m.Split(ctx)
	transitionInfos := make([]blueprint.TransitionInfo, 0, len(variations))
	for _, variation := range variations {
		transitionInfos = append(transitionInfos, variationTransitionInfo{variation})
	}
	return transitionInfos
}

func (v variationTransitionMutatorAdapter) OutgoingTransition(ctx OutgoingTransitionContext,
	sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {

	sourceVariationTransitionInfo, _ := sourceTransitionInfo.(variationTransitionInfo)
	outgoingVariation := v.m.OutgoingTransition(ctx, sourceVariationTransitionInfo.variation)
	return variationTransitionInfo{outgoingVariation}
}

func (v variationTransitionMutatorAdapter) IncomingTransition(ctx IncomingTransitionContext,
	incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {

	incomingVariationTransitionInfo, _ := incomingTransitionInfo.(variationTransitionInfo)
	variation := v.m.IncomingTransition(ctx, incomingVariationTransitionInfo.variation)
	return variationTransitionInfo{variation}
}

func (v variationTransitionMutatorAdapter) Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
	variationTransitionInfo, _ := transitionInfo.(variationTransitionInfo)
	v.m.Mutate(ctx, variationTransitionInfo.variation)
}

func (v variationTransitionMutatorAdapter) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
	return variationTransitionInfo{variation}
}

// variationTransitionInfo is a blueprint.TransitionInfo that contains a single variation string.
type variationTransitionInfo struct {
	variation string
}

func (v variationTransitionInfo) Variation() string {
	return v.variation
}

// genericTransitionMutatorAdapter wraps a TransitionMutator to convert it to an androidTransitionMutator
type genericTransitionMutatorAdapter[T blueprint.TransitionInfo] struct {
	m TransitionMutator[T]
}

// NewGenericTransitionMutatorAdapter is used to convert a generic TransitionMutator[T] into an androidTransitionMutator
// that can be passed to RegisterMutatorsContext.InfoBasedTransition.
func NewGenericTransitionMutatorAdapter[T blueprint.TransitionInfo](m TransitionMutator[T]) androidTransitionMutator {
	return &genericTransitionMutatorAdapter[T]{m}
}

func (g *genericTransitionMutatorAdapter[T]) convertTransitionInfoToT(transitionInfo blueprint.TransitionInfo) T {
	if transitionInfo == nil {
		var zero T
		return zero
	}
	return transitionInfo.(T)
}

func (g *genericTransitionMutatorAdapter[T]) Split(ctx BaseModuleContext) []blueprint.TransitionInfo {
	transitionInfos := g.m.Split(ctx)
	bpTransitionInfos := make([]blueprint.TransitionInfo, 0, len(transitionInfos))
	for _, transitionInfo := range transitionInfos {
		bpTransitionInfos = append(bpTransitionInfos, transitionInfo)
	}
	return bpTransitionInfos
}

func (g *genericTransitionMutatorAdapter[T]) OutgoingTransition(ctx OutgoingTransitionContext, sourceTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
	sourceTransitionInfoT := g.convertTransitionInfoToT(sourceTransitionInfo)
	return g.m.OutgoingTransition(ctx, sourceTransitionInfoT)
}

func (g *genericTransitionMutatorAdapter[T]) IncomingTransition(ctx IncomingTransitionContext, incomingTransitionInfo blueprint.TransitionInfo) blueprint.TransitionInfo {
	incomingTransitionInfoT := g.convertTransitionInfoToT(incomingTransitionInfo)
	return g.m.IncomingTransition(ctx, incomingTransitionInfoT)
}

func (g *genericTransitionMutatorAdapter[T]) Mutate(ctx BottomUpMutatorContext, transitionInfo blueprint.TransitionInfo) {
	transitionInfoT := g.convertTransitionInfoToT(transitionInfo)
	g.m.Mutate(ctx, transitionInfoT)
}

func (g *genericTransitionMutatorAdapter[T]) TransitionInfoFromVariation(variation string) blueprint.TransitionInfo {
	return g.m.TransitionInfoFromVariation(variation)
}

// incomingTransitionContextImpl wraps a blueprint.IncomingTransitionContext to convert it to an
// IncomingTransitionContext.
type incomingTransitionContextImpl struct {
	archModuleContext
	bp blueprint.IncomingTransitionContext
}

func (c *incomingTransitionContextImpl) Module() Module {
	return c.bp.Module().(Module)
}

func (c *incomingTransitionContextImpl) ModuleName() string {
	return c.bp.ModuleName()
}

func (c *incomingTransitionContextImpl) DepTag() blueprint.DependencyTag {
	return c.bp.DepTag()
}

func (c *incomingTransitionContextImpl) Config() Config {
	return c.bp.Config().(Config)
}

func (c *incomingTransitionContextImpl) DeviceConfig() DeviceConfig {
	return DeviceConfig{c.bp.Config().(Config).deviceConfig}
}

func (c *incomingTransitionContextImpl) IsAddingDependency() bool {
	return c.bp.IsAddingDependency()
}

func (c *incomingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
	return c.bp.Provider(provider)
}

func (c *incomingTransitionContextImpl) ModuleErrorf(fmt string, args ...interface{}) {
	c.bp.ModuleErrorf(fmt, args)
}

func (c *incomingTransitionContextImpl) PropertyErrorf(property, fmt string, args ...interface{}) {
	c.bp.PropertyErrorf(property, fmt, args)
}

func (c *incomingTransitionContextImpl) HasMutatorFinished(mutatorName string) bool {
	return c.bp.HasMutatorFinished(mutatorName)
}

func (c *incomingTransitionContextImpl) OtherModulePropertyErrorf(module ModuleOrProxy, property string, fmt string, args ...interface{}) {
	c.bp.OtherModulePropertyErrorf(module, property, fmt, args...)
}

// outgoingTransitionContextImpl wraps a blueprint.OutgoingTransitionContext to convert it to an
// OutgoingTransitionContext.
type outgoingTransitionContextImpl struct {
	archModuleContext
	bp blueprint.OutgoingTransitionContext
}

func (c *outgoingTransitionContextImpl) Module() Module {
	return c.bp.Module().(Module)
}

func (c *outgoingTransitionContextImpl) ModuleName() string {
	return c.bp.ModuleName()
}

func (c *outgoingTransitionContextImpl) DepTag() blueprint.DependencyTag {
	return c.bp.DepTag()
}

func (c *outgoingTransitionContextImpl) Config() Config {
	return c.bp.Config().(Config)
}

func (c *outgoingTransitionContextImpl) DeviceConfig() DeviceConfig {
	return DeviceConfig{c.bp.Config().(Config).deviceConfig}
}

func (c *outgoingTransitionContextImpl) provider(provider blueprint.AnyProviderKey) (any, bool) {
	return c.bp.Provider(provider)
}

// UsesUnbundledVariantDepTag is an interface that dependency tags can implement to indicate they
// want the variant of the module that would be used for unbundled builds. This is used by
// unbundled_builder. Historically, make did not know/care about individual variants, so when
// you listed apps in TARGET_BUILD_APPS, make would build whatever variant was available.
type UsesUnbundledVariantDepTag interface {
	UsesUnbundledVariant()
}
