// Copyright (C) 2018 The Android Open Source Project
//
// 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 aidl

import (
	"android/soong/android"
	"android/soong/cc"
	"android/soong/genrule"
	"android/soong/java"
	"android/soong/phony"
	"fmt"
	"io"
	"path/filepath"
	"strconv"
	"strings"
	"sync"

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

var (
	aidlInterfaceSuffix = "_interface"
	aidlApiSuffix       = "-api"
	langCpp             = "cpp"
	langJava            = "java"
	langNdk             = "ndk"
	langNdkPlatform     = "ndk_platform"
	futureVersion       = "10000"

	pctx = android.NewPackageContext("android/aidl")

	aidlDirPrepareRule = pctx.StaticRule("aidlDirPrepareRule", blueprint.RuleParams{
		Command: `rm -rf "${outDir}" && mkdir -p "${outDir}" && ` +
			`touch ${out}`,
		Description: "create ${out}",
	}, "outDir")

	aidlCppRule = pctx.StaticRule("aidlCppRule", blueprint.RuleParams{
		Command: `mkdir -p "${headerDir}" && ` +
			`${aidlCmd} --lang=${lang} ${optionalFlags} --structured --ninja -d ${out}.d ` +
			`-h ${headerDir} -o ${outDir} ${imports} ${in}`,
		Depfile:     "${out}.d",
		Deps:        blueprint.DepsGCC,
		CommandDeps: []string{"${aidlCmd}"},
		Description: "AIDL ${lang} ${in}",
	}, "imports", "lang", "headerDir", "outDir", "optionalFlags")

	aidlJavaRule = pctx.StaticRule("aidlJavaRule", blueprint.RuleParams{
		Command: `${aidlCmd} --lang=java ${optionalFlags} --structured --ninja -d ${out}.d ` +
			`-o ${outDir} ${imports} ${in}`,
		Depfile:     "${out}.d",
		Deps:        blueprint.DepsGCC,
		CommandDeps: []string{"${aidlCmd}"},
		Description: "AIDL Java ${in}",
	}, "imports", "outDir", "optionalFlags")

	aidlDumpApiRule = pctx.StaticRule("aidlDumpApiRule", blueprint.RuleParams{
		Command: `rm -rf "${out}" && mkdir -p "${out}" && ` +
			`${aidlCmd} --dumpapi --structured ${imports} --out ${out} ${in}`,
		CommandDeps: []string{"${aidlCmd}"},
	}, "imports")

	aidlDumpMappingsRule = pctx.StaticRule("aidlDumpMappingsRule", blueprint.RuleParams{
		Command: `rm -rf "${outDir}" && mkdir -p "${outDir}" && ` +
			`${aidlCmd} --apimapping ${outDir}/intermediate.txt ${in} ${imports} && ` +
			`${aidlToJniCmd} ${outDir}/intermediate.txt ${out}`,
		CommandDeps: []string{"${aidlCmd}"},
	}, "imports", "outDir")

	aidlFreezeApiRule = pctx.AndroidStaticRule("aidlFreezeApiRule",
		blueprint.RuleParams{
			Command: `mkdir -p ${to} && rm -rf ${to}/* && ` +
				`${bpmodifyCmd} -w -m ${name} -parameter versions -a ${version} ${bp} && ` +
				`cp -rf ${in}/* ${to} && ` +
				`find ${to} -type f -exec bash -c ` +
				`"cat ${apiPreamble} {} > {}.temp; mv {}.temp {}" \; && ` +
				`touch ${out}`,
			CommandDeps: []string{"${bpmodifyCmd}"},
		}, "to", "name", "version", "bp", "apiPreamble")

	aidlCheckApiRule = pctx.StaticRule("aidlCheckApiRule", blueprint.RuleParams{
		Command: `(${aidlCmd} --checkapi ${old} ${new} && touch ${out}) || ` +
			`(cat ${messageFile} && exit 1)`,
		CommandDeps: []string{"${aidlCmd}"},
		Description: "AIDL CHECK API: ${new} against ${old}",
	}, "old", "new", "messageFile")

	aidlDiffApiRule = pctx.StaticRule("aidlDiffApiRule", blueprint.RuleParams{
		Command: `(diff -r -B -I '//.*' ${old} ${new} && touch ${out}) || ` +
			`(cat ${messageFile} && exit 1)`,
		Description: "Check equality of ${new} and ${old}",
	}, "old", "new", "messageFile")
)

func init() {
	pctx.HostBinToolVariable("aidlCmd", "aidl")
	pctx.HostBinToolVariable("bpmodifyCmd", "bpmodify")
	pctx.SourcePathVariable("aidlToJniCmd", "system/tools/aidl/build/aidl_to_jni.py")
	android.RegisterModuleType("aidl_interface", aidlInterfaceFactory)
	android.RegisterModuleType("aidl_mapping", aidlMappingFactory)
}

// wrap(p, a, s) = [p + v + s for v in a]
func wrap(prefix string, strs []string, suffix string) []string {
	ret := make([]string, len(strs))
	for i, v := range strs {
		ret[i] = prefix + v + suffix
	}
	return ret
}

// concat(a...) = sum((i for i in a), [])
func concat(sstrs ...[]string) []string {
	var ret []string
	for _, v := range sstrs {
		ret = append(ret, v...)
	}
	return ret
}

func isRelativePath(path string) bool {
	if path == "" {
		return true
	}
	return filepath.Clean(path) == path && path != ".." &&
		!strings.HasPrefix(path, "../") && !strings.HasPrefix(path, "/")
}

type aidlGenProperties struct {
	Srcs     []string
	AidlRoot string // base directory for the input aidl file
	Imports  []string
	Lang     string // target language [java|cpp|ndk]
	BaseName string
	GenLog   bool
	Version  string
}

type aidlGenRule struct {
	android.ModuleBase

	properties aidlGenProperties

	implicitInputs android.Paths
	importFlags    string

	genOutDir    android.ModuleGenPath
	genHeaderDir android.ModuleGenPath
	genOutputs   android.WritablePaths
}

var _ android.SourceFileProducer = (*aidlGenRule)(nil)
var _ genrule.SourceFileGenerator = (*aidlGenRule)(nil)

func (g *aidlGenRule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	genDirTimestamp := android.PathForModuleGen(ctx, "timestamp")
	g.implicitInputs = append(g.implicitInputs, genDirTimestamp)

	var importPaths []string
	ctx.VisitDirectDeps(func(dep android.Module) {
		if importedAidl, ok := dep.(*aidlInterface); ok {
			importPaths = append(importPaths, importedAidl.properties.Full_import_paths...)
		} else if api, ok := dep.(*aidlApi); ok {
			// When compiling an AIDL interface, also make sure that each
			// version of the interface is compatible with its previous version
			for _, path := range api.checkApiTimestamps {
				g.implicitInputs = append(g.implicitInputs, path)
			}
		}
	})
	g.importFlags = strings.Join(wrap("-I", importPaths, ""), " ")

	srcs := android.PathsWithModuleSrcSubDir(ctx, android.PathsForModuleSrc(ctx, g.properties.Srcs), g.properties.AidlRoot)

	g.genOutDir = android.PathForModuleGen(ctx)
	g.genHeaderDir = android.PathForModuleGen(ctx, "include")
	for _, src := range srcs {
		g.genOutputs = append(g.genOutputs, g.generateBuildActionsForSingleAidl(ctx, src))
	}

	// This is to clean genOutDir before generating any file
	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
		Rule:      aidlDirPrepareRule,
		Implicits: srcs,
		Output:    genDirTimestamp,
		Args: map[string]string{
			"outDir": g.genOutDir.String(),
		},
	})
}

func (g *aidlGenRule) generateBuildActionsForSingleAidl(ctx android.ModuleContext, src android.Path) android.WritablePath {
	var outFile android.WritablePath
	if g.properties.Lang == langJava {
		outFile = android.PathForModuleGen(ctx, pathtools.ReplaceExtension(src.Rel(), "java"))
	} else {
		outFile = android.PathForModuleGen(ctx, pathtools.ReplaceExtension(src.Rel(), "cpp"))
	}

	var optionalFlags []string
	if g.properties.Version != "" {
		optionalFlags = append(optionalFlags, "--version "+g.properties.Version)
	}

	if g.properties.Lang == langJava {
		ctx.ModuleBuild(pctx, android.ModuleBuildParams{
			Rule:      aidlJavaRule,
			Input:     src,
			Implicits: g.implicitInputs,
			Output:    outFile,
			Args: map[string]string{
				"imports":       g.importFlags,
				"outDir":        g.genOutDir.String(),
				"optionalFlags": strings.Join(optionalFlags, " "),
			},
		})
	} else {
		typeName := strings.TrimSuffix(filepath.Base(src.Rel()), ".aidl")
		packagePath := filepath.Dir(src.Rel())
		baseName := typeName
		// TODO(b/111362593): aidl_to_cpp_common.cpp uses heuristics to figure out if
		//   an interface name has a leading I. Those same heuristics have been
		//   moved here.
		if len(baseName) >= 2 && baseName[0] == 'I' &&
			strings.ToUpper(baseName)[1] == baseName[1] {
			baseName = strings.TrimPrefix(typeName, "I")
		}

		prefix := ""
		if g.properties.Lang == langNdk || g.properties.Lang == langNdkPlatform {
			prefix = "aidl"
		}

		var headers android.WritablePaths
		headers = append(headers, g.genHeaderDir.Join(ctx, prefix, packagePath,
			typeName+".h"))
		headers = append(headers, g.genHeaderDir.Join(ctx, prefix, packagePath,
			"Bp"+baseName+".h"))
		headers = append(headers, g.genHeaderDir.Join(ctx, prefix, packagePath,
			"Bn"+baseName+".h"))

		if g.properties.GenLog {
			optionalFlags = append(optionalFlags, "--log")
		}

		aidlLang := g.properties.Lang
		if aidlLang == langNdkPlatform {
			aidlLang = "ndk"
		}

		ctx.ModuleBuild(pctx, android.ModuleBuildParams{
			Rule:            aidlCppRule,
			Input:           src,
			Implicits:       g.implicitInputs,
			Output:          outFile,
			ImplicitOutputs: headers,
			Args: map[string]string{
				"imports":       g.importFlags,
				"lang":          aidlLang,
				"headerDir":     g.genHeaderDir.String(),
				"outDir":        g.genOutDir.String(),
				"optionalFlags": strings.Join(optionalFlags, " "),
			},
		})
	}

	return outFile
}

func (g *aidlGenRule) GeneratedSourceFiles() android.Paths {
	return g.genOutputs.Paths()
}

func (g *aidlGenRule) Srcs() android.Paths {
	return g.genOutputs.Paths()
}

func (g *aidlGenRule) GeneratedDeps() android.Paths {
	return g.genOutputs.Paths()
}

func (g *aidlGenRule) GeneratedHeaderDirs() android.Paths {
	return android.Paths{g.genHeaderDir}
}

func (g *aidlGenRule) DepsMutator(ctx android.BottomUpMutatorContext) {
	ctx.AddDependency(ctx.Module(), nil, wrap("", g.properties.Imports, aidlInterfaceSuffix)...)
	ctx.AddDependency(ctx.Module(), nil, g.properties.BaseName+aidlApiSuffix)
}

func aidlGenFactory() android.Module {
	g := &aidlGenRule{}
	g.AddProperties(&g.properties)
	android.InitAndroidModule(g)
	return g
}

type aidlApiProperties struct {
	BaseName string
	Inputs   []string
	Imports  []string
	Api_dir  *string
	Versions []string
	AidlRoot string // base directory for the input aidl file
}

type aidlApi struct {
	android.ModuleBase

	properties aidlApiProperties

	// for triggering api check for version X against version X-1
	checkApiTimestamps android.WritablePaths

	// for triggering freezing API as the new version
	freezeApiTimestamp android.WritablePath
}

func (m *aidlApi) apiDir() string {
	if m.properties.Api_dir != nil {
		return *(m.properties.Api_dir)
	} else {
		return "api"
	}
}

// Version of the interface at ToT if it is frozen
func (m *aidlApi) validateCurrentVersion(ctx android.ModuleContext) string {
	if len(m.properties.Versions) == 0 {
		return "1"
	} else {
		latestVersion := m.properties.Versions[len(m.properties.Versions)-1]

		i, err := strconv.ParseInt(latestVersion, 10, 64)
		if err != nil {
			ctx.PropertyErrorf("versions", "must be integers")
			return ""
		}

		return strconv.FormatInt(i+1, 10)
	}
}

func (m *aidlApi) createApiDumpFromSource(ctx android.ModuleContext) (apiDir android.WritablePath, apiFiles android.WritablePaths) {
	var importPaths []string
	ctx.VisitDirectDeps(func(dep android.Module) {
		if importedAidl, ok := dep.(*aidlInterface); ok {
			importPaths = append(importPaths, importedAidl.properties.Full_import_paths...)
		}
	})

	var srcs android.Paths
	for _, input := range m.properties.Inputs {
		path := android.PathForModuleSrc(ctx, input)
		path = android.PathWithModuleSrcSubDir(ctx, path, m.properties.AidlRoot)
		srcs = append(srcs, path)
	}

	apiDir = android.PathForModuleOut(ctx, "dump")
	for _, src := range srcs {
		apiFiles = append(apiFiles, android.PathForModuleOut(ctx, "dump", src.Rel()))
	}
	imports := strings.Join(wrap("-I", importPaths, ""), " ")
	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
		Rule:            aidlDumpApiRule,
		Inputs:          srcs,
		Output:          apiDir,
		ImplicitOutputs: apiFiles,
		Args: map[string]string{
			"imports": imports,
		},
	})
	return apiDir, apiFiles
}

func (m *aidlApi) freezeApiDumpAsVersion(ctx android.ModuleContext, apiDumpDir android.Path, apiFiles android.Paths, version string) android.WritablePath {
	timestampFile := android.PathForModuleOut(ctx, "freezeapi_"+version+".timestamp")

	modulePath := android.PathForModuleSrc(ctx).String()

	var implicits android.Paths
	implicits = append(implicits, apiFiles...)

	apiPreamble := android.PathForSource(ctx, "system/tools/aidl/build/api_preamble.txt")
	implicits = append(implicits, apiPreamble)

	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
		Rule:        aidlFreezeApiRule,
		Description: "Freezing AIDL API of " + m.properties.BaseName + " as version " + version,
		Implicits:   implicits,
		Output:      timestampFile,
		Args: map[string]string{
			"to":          filepath.Join(modulePath, m.apiDir(), version),
			"name":        m.properties.BaseName,
			"version":     version,
			"bp":          android.PathForModuleSrc(ctx, "Android.bp").String(),
			"apiPreamble": apiPreamble.String(),
		},
	})
	return timestampFile
}

func (m *aidlApi) checkCompatibility(ctx android.ModuleContext, oldApiDir android.Path, oldApiFiles android.Paths, newApiDir android.Path, newApiFiles android.Paths) android.WritablePath {
	newVersion := newApiDir.Base()
	timestampFile := android.PathForModuleOut(ctx, "checkapi_"+newVersion+".timestamp")
	messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_compatibility.txt")
	var implicits android.Paths
	implicits = append(implicits, oldApiFiles...)
	implicits = append(implicits, newApiFiles...)
	implicits = append(implicits, messageFile)
	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
		Rule:      aidlCheckApiRule,
		Implicits: implicits,
		Output:    timestampFile,
		Args: map[string]string{
			"old":         oldApiDir.String(),
			"new":         newApiDir.String(),
			"messageFile": messageFile.String(),
		},
	})
	return timestampFile
}

func (m *aidlApi) checkEquality(ctx android.ModuleContext, oldApiDir android.Path, oldApiFiles android.Paths, newApiDir android.Path, newApiFiles android.Paths) android.WritablePath {
	newVersion := newApiDir.Base()
	timestampFile := android.PathForModuleOut(ctx, "checkapi_"+newVersion+".timestamp")
	messageFile := android.PathForSource(ctx, "system/tools/aidl/build/message_check_equality.txt")
	var implicits android.Paths
	implicits = append(implicits, oldApiFiles...)
	implicits = append(implicits, newApiFiles...)
	implicits = append(implicits, messageFile)
	ctx.ModuleBuild(pctx, android.ModuleBuildParams{
		Rule:      aidlDiffApiRule,
		Implicits: implicits,
		Output:    timestampFile,
		Args: map[string]string{
			"old":         oldApiDir.String(),
			"new":         newApiDir.String(),
			"messageFile": messageFile.String(),
		},
	})
	return timestampFile
}

func (m *aidlApi) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	currentVersion := m.validateCurrentVersion(ctx)

	if ctx.Failed() {
		return
	}

	currentDumpDir, currentApiFiles := m.createApiDumpFromSource(ctx)
	m.freezeApiTimestamp = m.freezeApiDumpAsVersion(ctx, currentDumpDir, currentApiFiles.Paths(), currentVersion)

	apiDirs := make(map[string]android.Path)
	apiFiles := make(map[string]android.Paths)
	for _, ver := range m.properties.Versions {
		apiDir := android.PathForModuleSrc(ctx, m.apiDir(), ver)
		apiDirs[ver] = apiDir
		apiFiles[ver] = ctx.Glob(filepath.Join(apiDir.String(), "**/*.aidl"), nil)
	}
	apiDirs[currentVersion] = currentDumpDir
	apiFiles[currentVersion] = currentApiFiles.Paths()

	// Check that version X is backward compatible with version X-1
	for i, newVersion := range m.properties.Versions {
		if i != 0 {
			oldVersion := m.properties.Versions[i-1]
			checkApiTimestamp := m.checkCompatibility(ctx, apiDirs[oldVersion], apiFiles[oldVersion], apiDirs[newVersion], apiFiles[newVersion])
			m.checkApiTimestamps = append(m.checkApiTimestamps, checkApiTimestamp)
		}
	}

	// ... and that the currentVersion (ToT) is backwards compatible with or
	// equal to the latest frozen version
	if len(m.properties.Versions) >= 1 {
		latestVersion := m.properties.Versions[len(m.properties.Versions)-1]
		var checkApiTimestamp android.WritablePath
		if ctx.Config().DefaultAppTargetSdkInt() != android.FutureApiLevel {
			// If API is frozen, don't allow any change to the API
			checkApiTimestamp = m.checkEquality(ctx, apiDirs[latestVersion], apiFiles[latestVersion], apiDirs[currentVersion], apiFiles[currentVersion])
		} else {
			// If not, allow backwards compatible changes to the API
			checkApiTimestamp = m.checkCompatibility(ctx, apiDirs[latestVersion], apiFiles[latestVersion], apiDirs[currentVersion], apiFiles[currentVersion])
		}
		m.checkApiTimestamps = append(m.checkApiTimestamps, checkApiTimestamp)
	}
}

func (m *aidlApi) AndroidMk() android.AndroidMkData {
	return android.AndroidMkData{
		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
			android.WriteAndroidMkData(w, data)
			targetName := m.properties.BaseName + "-freeze-api"
			fmt.Fprintln(w, ".PHONY:", targetName)
			fmt.Fprintln(w, targetName+":", m.freezeApiTimestamp.String())
		},
	}
}

func (m *aidlApi) DepsMutator(ctx android.BottomUpMutatorContext) {
	ctx.AddDependency(ctx.Module(), nil, wrap("", m.properties.Imports, aidlInterfaceSuffix)...)
}

func aidlApiFactory() android.Module {
	m := &aidlApi{}
	m.AddProperties(&m.properties)
	android.InitAndroidModule(m)
	return m
}

type aidlInterfaceProperties struct {
	// Vndk properties for interface library only.
	cc.VndkProperties

	// Whether the library can be installed on the vendor image.
	Vendor_available *bool
	// Top level directories for includes.
	// TODO(b/128940869): remove it if aidl_interface can depend on framework.aidl
	Include_dirs []string
	// Relative path for includes. By default assumes AIDL path is relative to current directory.
	// TODO(b/111117220): automatically compute by letting AIDL parse multiple files simultaneously
	Local_include_dir string

	// The owner of the module
	Owner *string

	// List of .aidl files which compose this interface. These may be globbed.
	Srcs []string

	Imports []string

	// Used by gen dependency to fill out aidl include path
	Full_import_paths []string `blueprint:"mutated"`

	// Directory where API dumps are. Default is "api".
	Api_dir *string

	// Previous API versions that are now frozen. The version that is last in
	// the list is considered as the most recent version.
	Versions []string

	Backend struct {
		Java struct {
			// Whether to generate Java code using Java binder APIs
			// Default: true
			Enabled *bool
			// Set to the version of the sdk to compile against
			// Default: system_current
			Sdk_version *string
		}
		Cpp struct {
			// Whether to generate C++ code using C++ binder APIs
			// Default: true
			Enabled *bool
			// Whether to generate additional code for gathering information
			// about the transactions
			// Default: false
			Gen_log *bool
		}
		Ndk struct {
			// Whether to generate C++ code using NDK binder APIs
			// Default: true
			Enabled *bool
			// Whether to generate additional code for gathering information
			// about the transactions
			// Default: false
			Gen_log *bool
		}
	}
}

type aidlInterface struct {
	android.ModuleBase

	properties aidlInterfaceProperties

	// Unglobbed sources
	rawSrcs []string
}

func (i *aidlInterface) shouldGenerateJavaBackend() bool {
	// explicitly true if not specified to give early warning to devs
	return i.properties.Backend.Java.Enabled == nil || *i.properties.Backend.Java.Enabled
}

func (i *aidlInterface) shouldGenerateCppBackend() bool {
	// explicitly true if not specified to give early warning to devs
	return i.properties.Backend.Cpp.Enabled == nil || *i.properties.Backend.Cpp.Enabled
}

func (i *aidlInterface) shouldGenerateNdkBackend() bool {
	// explicitly true if not specified to give early warning to devs
	return i.properties.Backend.Ndk.Enabled == nil || *i.properties.Backend.Ndk.Enabled
}

func (i *aidlInterface) checkAndUpdateSources(mctx android.LoadHookContext) {
	prefix := mctx.ModuleDir()
	for _, source := range i.properties.Srcs {
		if pathtools.IsGlob(source) {
			globbedSrcFiles, err := mctx.GlobWithDeps(filepath.Join(prefix, source), nil)
			if err != nil {
				mctx.ModuleErrorf("glob: %s", err.Error())
			}
			for _, globbedSrc := range globbedSrcFiles {
				relativeGlobbedSrc, err := filepath.Rel(prefix, globbedSrc)
				if err != nil {
					panic(err)
				}

				i.rawSrcs = append(i.rawSrcs, relativeGlobbedSrc)
			}
		} else {
			i.rawSrcs = append(i.rawSrcs, source)
		}
	}

	if len(i.rawSrcs) == 0 {
		mctx.PropertyErrorf("srcs", "No sources provided.")
	}

	for _, source := range i.rawSrcs {
		if !strings.HasSuffix(source, ".aidl") {
			mctx.PropertyErrorf("srcs", "Source must be a .aidl file: "+source)
			continue
		}

		relativePath, err := filepath.Rel(i.properties.Local_include_dir, source)
		if err != nil || !isRelativePath(relativePath) {
			mctx.PropertyErrorf("srcs", "Source is not in local_include_dir: "+source)
		}
	}
}

func (i *aidlInterface) checkImports(mctx android.LoadHookContext) {
	for _, anImport := range i.properties.Imports {
		other := lookupInterface(anImport)

		if other == nil {
			mctx.PropertyErrorf("imports", "Import does not exist: "+anImport)
		}

		if i.shouldGenerateCppBackend() && !other.shouldGenerateCppBackend() {
			mctx.PropertyErrorf("backend.cpp.enabled",
				"C++ backend not enabled in the imported AIDL interface %q", anImport)
		}

		if i.shouldGenerateNdkBackend() && !other.shouldGenerateNdkBackend() {
			mctx.PropertyErrorf("backend.ndk.enabled",
				"NDK backend not enabled in the imported AIDL interface %q", anImport)
		}
	}
}

func (i *aidlInterface) versionedName(version string) string {
	name := i.ModuleBase.Name()
	if version != futureVersion && version != "" {
		name = name + "-V" + version
	}
	return name
}

func (i *aidlInterface) srcsForVersion(mctx android.LoadHookContext, version string) (srcs []string, base string) {
	if version == futureVersion || version == "" {
		return i.rawSrcs, i.properties.Local_include_dir
	} else {
		var apiDir string
		if i.properties.Api_dir != nil {
			apiDir = *(i.properties.Api_dir)
		} else {
			apiDir = "api"
		}
		base = filepath.Join(apiDir, version)
		full_paths, err := mctx.GlobWithDeps(filepath.Join(mctx.ModuleDir(), base, "**/*.aidl"), nil)
		if err != nil {
			panic(err)
		}
		for _, path := range full_paths {
			// Here, we need path local to the module
			srcs = append(srcs, strings.TrimPrefix(path, mctx.ModuleDir()+"/"))
		}
		return srcs, base
	}
}

func aidlInterfaceHook(mctx android.LoadHookContext, i *aidlInterface) {
	if !isRelativePath(i.properties.Local_include_dir) {
		mctx.PropertyErrorf("local_include_dir", "must be relative path: "+i.properties.Local_include_dir)
	}
	var importPaths []string
	importPaths = append(importPaths, filepath.Join(mctx.ModuleDir(), i.properties.Local_include_dir))
	importPaths = append(importPaths, i.properties.Include_dirs...)

	i.properties.Full_import_paths = importPaths

	i.checkAndUpdateSources(mctx)
	i.checkImports(mctx)

	if mctx.Failed() {
		return
	}

	var libs []string

	currentVersion := ""
	if len(i.properties.Versions) > 0 {
		currentVersion = futureVersion
	}

	if i.shouldGenerateCppBackend() {
		libs = append(libs, addCppLibrary(mctx, i, currentVersion, langCpp))
		for _, version := range i.properties.Versions {
			addCppLibrary(mctx, i, version, langCpp)
		}
	}

	if i.shouldGenerateNdkBackend() {
		// TODO(b/119771576): inherit properties and export 'is vendor' computation from cc.go
		if !proptools.Bool(i.properties.Vendor_available) {
			libs = append(libs, addCppLibrary(mctx, i, currentVersion, langNdk))
			for _, version := range i.properties.Versions {
				addCppLibrary(mctx, i, version, langNdk)
			}
		}
		// TODO(b/121157555): combine with '-ndk' variant
		libs = append(libs, addCppLibrary(mctx, i, currentVersion, langNdkPlatform))
		for _, version := range i.properties.Versions {
			addCppLibrary(mctx, i, version, langNdkPlatform)
		}
	}

	libs = append(libs, addJavaLibrary(mctx, i, currentVersion))
	for _, version := range i.properties.Versions {
		addJavaLibrary(mctx, i, version)
	}

	addApiModule(mctx, i)

	// Reserve this module name for future use
	mctx.CreateModule(android.ModuleFactoryAdaptor(phony.PhonyFactory), &phonyProperties{
		Name:     proptools.StringPtr(i.ModuleBase.Name()),
		Required: libs,
	})
}

func addCppLibrary(mctx android.LoadHookContext, i *aidlInterface, version string, lang string) string {
	cppSourceGen := i.versionedName(version) + "-" + lang + "-source"
	cppModuleGen := i.versionedName(version) + "-" + lang

	srcs, base := i.srcsForVersion(mctx, version)
	if len(srcs) == 0 {
		// This can happen when the version is about to be frozen; the version
		// directory is created but API dump hasn't been copied there.
		// Don't create a library for the yet-to-be-frozen version.
		return ""
	}

	genLog := false
	if lang == langCpp {
		genLog = proptools.Bool(i.properties.Backend.Cpp.Gen_log)
	} else if lang == langNdk || lang == langNdkPlatform {
		genLog = proptools.Bool(i.properties.Backend.Ndk.Gen_log)
	}

	mctx.CreateModule(android.ModuleFactoryAdaptor(aidlGenFactory), &nameProperties{
		Name: proptools.StringPtr(cppSourceGen),
	}, &aidlGenProperties{
		Srcs:     srcs,
		AidlRoot: base,
		Imports:  concat(i.properties.Imports, []string{i.ModuleBase.Name()}),
		Lang:     lang,
		BaseName: i.ModuleBase.Name(),
		GenLog:   genLog,
		Version:  version,
	})

	importExportDependencies := wrap("", i.properties.Imports, "-"+lang)
	var libJSONCppDependency []string
	var staticLibDependency []string
	var sdkVersion *string
	var stl *string
	var cpp_std *string
	if lang == langCpp {
		importExportDependencies = append(importExportDependencies, "libbinder", "libutils")
		if genLog {
			libJSONCppDependency = []string{"libjsoncpp"}
		}
		sdkVersion = nil
		stl = nil
		cpp_std = nil
	} else if lang == langNdk {
		importExportDependencies = append(importExportDependencies, "libbinder_ndk")
		if genLog {
			staticLibDependency = []string{"libjsoncpp_ndk"}
		}
		sdkVersion = proptools.StringPtr("current")
		stl = proptools.StringPtr("c++_shared")
	} else if lang == langNdkPlatform {
		importExportDependencies = append(importExportDependencies, "libbinder_ndk")
		if genLog {
			libJSONCppDependency = []string{"libjsoncpp"}
		}
	} else {
		panic("Unrecognized language: " + lang)
	}

	mctx.CreateModule(android.ModuleFactoryAdaptor(cc.LibraryFactory), &ccProperties{
		Name:                      proptools.StringPtr(cppModuleGen),
		Owner:                     i.properties.Owner,
		Vendor_available:          i.properties.Vendor_available,
		Defaults:                  []string{"aidl-cpp-module-defaults"},
		Generated_sources:         []string{cppSourceGen},
		Generated_headers:         []string{cppSourceGen},
		Export_generated_headers:  []string{cppSourceGen},
		Static:                    staticLib{Whole_static_libs: libJSONCppDependency},
		Shared:                    sharedLib{Shared_libs: libJSONCppDependency, Export_shared_lib_headers: libJSONCppDependency},
		Static_libs:               staticLibDependency,
		Shared_libs:               importExportDependencies,
		Export_shared_lib_headers: importExportDependencies,
		Sdk_version:               sdkVersion,
		Stl:                       stl,
		Cpp_std:                   cpp_std,
		Cflags:                    []string{"-Wextra", "-Wall", "-Werror"},
	}, &i.properties.VndkProperties)

	return cppModuleGen
}

func addJavaLibrary(mctx android.LoadHookContext, i *aidlInterface, version string) string {
	javaSourceGen := i.versionedName(version) + "-java-source"
	javaModuleGen := i.versionedName(version) + "-java"

	srcs, base := i.srcsForVersion(mctx, version)
	if len(srcs) == 0 {
		// This can happen when the version is about to be frozen; the version
		// directory is created but API dump hasn't been copied there.
		// Don't create a library for the yet-to-be-frozen version.
		return ""
	}

	sdkVersion := proptools.StringDefault(i.properties.Backend.Java.Sdk_version, "system_current")

	mctx.CreateModule(android.ModuleFactoryAdaptor(aidlGenFactory), &nameProperties{
		Name: proptools.StringPtr(javaSourceGen),
	}, &aidlGenProperties{
		Srcs:     srcs,
		AidlRoot: base,
		Imports:  concat(i.properties.Imports, []string{i.ModuleBase.Name()}),
		Lang:     langJava,
		BaseName: i.ModuleBase.Name(),
		Version:  version,
	})

	mctx.CreateModule(android.ModuleFactoryAdaptor(java.LibraryFactory), &javaProperties{
		Name:              proptools.StringPtr(javaModuleGen),
		Owner:             i.properties.Owner,
		Installable:       proptools.BoolPtr(true),
		Defaults:          []string{"aidl-java-module-defaults"},
		No_framework_libs: proptools.BoolPtr(true),
		Sdk_version:       proptools.StringPtr(sdkVersion),
		Static_libs:       wrap("", i.properties.Imports, "-java"),
		Srcs:              []string{":" + javaSourceGen},
	})

	return javaModuleGen
}

func addApiModule(mctx android.LoadHookContext, i *aidlInterface) string {
	apiModule := i.ModuleBase.Name() + aidlApiSuffix
	mctx.CreateModule(android.ModuleFactoryAdaptor(aidlApiFactory), &nameProperties{
		Name: proptools.StringPtr(apiModule),
	}, &aidlApiProperties{
		BaseName: i.ModuleBase.Name(),
		Inputs:   i.rawSrcs,
		Imports:  concat(i.properties.Imports, []string{i.ModuleBase.Name()}),
		Api_dir:  i.properties.Api_dir,
		AidlRoot: i.properties.Local_include_dir,
		Versions: i.properties.Versions,
	})
	return apiModule
}

func (i *aidlInterface) Name() string {
	return i.ModuleBase.Name() + aidlInterfaceSuffix
}
func (i *aidlInterface) GenerateAndroidBuildActions(ctx android.ModuleContext) {
}
func (i *aidlInterface) DepsMutator(ctx android.BottomUpMutatorContext) {
}

var aidlInterfaceMutex sync.Mutex
var aidlInterfaces []*aidlInterface

func aidlInterfaceFactory() android.Module {
	i := &aidlInterface{}
	i.AddProperties(&i.properties)
	android.InitAndroidModule(i)
	android.AddLoadHook(i, func(ctx android.LoadHookContext) { aidlInterfaceHook(ctx, i) })

	aidlInterfaceMutex.Lock()
	aidlInterfaces = append(aidlInterfaces, i)
	aidlInterfaceMutex.Unlock()

	return i
}

func lookupInterface(name string) *aidlInterface {
	for _, i := range aidlInterfaces {
		if i.ModuleBase.Name() == name {
			return i
		}
	}
	return nil
}

type aidlMappingProperties struct {
	// Source file of this prebuilt.
	Srcs   []string `android:"arch_variant"`
	Output string
}

type aidlMapping struct {
	android.ModuleBase
	properties     aidlMappingProperties
	outputFilePath android.WritablePath
}

func (s *aidlMapping) DepsMutator(ctx android.BottomUpMutatorContext) {
	android.ExtractSourcesDeps(ctx, s.properties.Srcs)
}

func addItemsToMap(dest map[string]bool, src []string) {
	for _, item := range src {
		dest[item] = true
	}
}

func (s *aidlMapping) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	var srcs android.Paths
	var all_import_dirs map[string]bool = make(map[string]bool)

	ctx.VisitDirectDeps(func(module android.Module) {
		for _, property := range module.GetProperties() {
			if jproperty, ok := property.(*java.CompilerProperties); ok {
				for _, src := range jproperty.Srcs {
					if strings.HasSuffix(src, ".aidl") {
						full_path := android.PathForModuleSrc(ctx, src)
						srcs = append(srcs, full_path)
						all_import_dirs[filepath.Dir(full_path.String())] = true
					} else if pathtools.IsGlob(src) {
						globbedSrcFiles, err := ctx.GlobWithDeps(src, nil)
						if err == nil {
							for _, globbedSrc := range globbedSrcFiles {
								full_path := android.PathForModuleSrc(ctx, globbedSrc)
								all_import_dirs[full_path.String()] = true
							}
						}
					}
				}
			} else if jproperty, ok := property.(*java.CompilerDeviceProperties); ok {
				addItemsToMap(all_import_dirs, jproperty.Aidl.Include_dirs)
				for _, include_dir := range jproperty.Aidl.Export_include_dirs {
					var full_path = filepath.Join(ctx.ModuleDir(), include_dir)
					all_import_dirs[full_path] = true
				}
				for _, include_dir := range jproperty.Aidl.Local_include_dirs {
					var full_path = filepath.Join(ctx.ModuleSubDir(), include_dir)
					all_import_dirs[full_path] = true
				}
			}
		}
	})

	var import_dirs []string
	for dir := range all_import_dirs {
		import_dirs = append(import_dirs, dir)
	}
	imports := strings.Join(wrap("-I", import_dirs, ""), " ")
	s.outputFilePath = android.PathForModuleOut(ctx, s.properties.Output)
	outDir := android.PathForModuleGen(ctx)
	ctx.Build(pctx, android.BuildParams{
		Rule:   aidlDumpMappingsRule,
		Inputs: srcs,
		Output: s.outputFilePath,
		Args: map[string]string{
			"imports": imports,
			"outDir":  outDir.String(),
		},
	})
}

func InitAidlMappingModule(s *aidlMapping) {
	s.AddProperties(&s.properties)
}

func aidlMappingFactory() android.Module {
	module := &aidlMapping{}
	InitAidlMappingModule(module)
	android.InitAndroidModule(module)
	return module
}

func (m *aidlMapping) AndroidMk() android.AndroidMkData {
	return android.AndroidMkData{
		Custom: func(w io.Writer, name, prefix, moduleDir string, data android.AndroidMkData) {
			android.WriteAndroidMkData(w, data)
			targetName := m.Name()
			fmt.Fprintln(w, ".PHONY:", targetName)
			fmt.Fprintln(w, targetName+":", m.outputFilePath.String())
		},
	}
}