// Copyright 2018 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 java

import (
	"android/soong/android"
	"android/soong/dexpreopt"
)

type dexpreopter struct {
	dexpreoptProperties DexpreoptProperties

	installPath     android.OutputPath
	uncompressedDex bool
	isSDKLibrary    bool
	isTest          bool
	isInstallable   bool

	builtInstalled string
}

type DexpreoptProperties struct {
	Dex_preopt struct {
		// If false, prevent dexpreopting and stripping the dex file from the final jar.  Defaults to
		// true.
		Enabled *bool

		// If true, never strip the dex files from the final jar when dexpreopting.  Defaults to false.
		No_stripping *bool

		// If true, generate an app image (.art file) for this module.
		App_image *bool

		// If true, use a checked-in profile to guide optimization.  Defaults to false unless
		// a matching profile is set or a profile is found in PRODUCT_DEX_PREOPT_PROFILE_DIR
		// that matches the name of this module, in which case it is defaulted to true.
		Profile_guided *bool

		// If set, provides the path to profile relative to the Android.bp file.  If not set,
		// defaults to searching for a file that matches the name of this module in the default
		// profile location set by PRODUCT_DEX_PREOPT_PROFILE_DIR, or empty if not found.
		Profile *string
	}
}

func (d *dexpreopter) dexpreoptDisabled(ctx android.ModuleContext) bool {
	global := dexpreoptGlobalConfig(ctx)

	if global.DisablePreopt {
		return true
	}

	if inList(ctx.ModuleName(), global.DisablePreoptModules) {
		return true
	}

	if ctx.Config().UnbundledBuild() {
		return true
	}

	if d.isTest {
		return true
	}

	if !BoolDefault(d.dexpreoptProperties.Dex_preopt.Enabled, true) {
		return true
	}

	if !d.isInstallable {
		return true
	}

	// TODO: contains no java code

	return false
}

func odexOnSystemOther(ctx android.ModuleContext, installPath android.OutputPath) bool {
	return dexpreopt.OdexOnSystemOtherByName(ctx.ModuleName(), android.InstallPathToOnDevicePath(ctx, installPath), dexpreoptGlobalConfig(ctx))
}

func (d *dexpreopter) dexpreopt(ctx android.ModuleContext, dexJarFile android.ModuleOutPath) android.ModuleOutPath {
	if d.dexpreoptDisabled(ctx) {
		return dexJarFile
	}

	global := dexpreoptGlobalConfig(ctx)
	bootImage := defaultBootImageConfig(ctx)
	defaultBootImage := bootImage
	if global.UseApexImage {
		bootImage = apexBootImageConfig(ctx)
	}

	var archs []android.ArchType
	for _, a := range ctx.MultiTargets() {
		archs = append(archs, a.Arch.ArchType)
	}
	if len(archs) == 0 {
		// assume this is a java library, dexpreopt for all arches for now
		for _, target := range ctx.Config().Targets[android.Android] {
			archs = append(archs, target.Arch.ArchType)
		}
		if inList(ctx.ModuleName(), global.SystemServerJars) && !d.isSDKLibrary {
			// If the module is not an SDK library and it's a system server jar, only preopt the primary arch.
			archs = archs[:1]
		}
	}
	if ctx.Config().SecondArchIsTranslated() {
		// Only preopt primary arch for translated arch since there is only an image there.
		archs = archs[:1]
	}

	var images android.Paths
	for _, arch := range archs {
		images = append(images, bootImage.images[arch])
	}

	dexLocation := android.InstallPathToOnDevicePath(ctx, d.installPath)

	strippedDexJarFile := android.PathForModuleOut(ctx, "dexpreopt", dexJarFile.Base())

	var profileClassListing android.OptionalPath
	profileIsTextListing := false
	if BoolDefault(d.dexpreoptProperties.Dex_preopt.Profile_guided, true) {
		// If dex_preopt.profile_guided is not set, default it based on the existence of the
		// dexprepot.profile option or the profile class listing.
		if String(d.dexpreoptProperties.Dex_preopt.Profile) != "" {
			profileClassListing = android.OptionalPathForPath(
				android.PathForModuleSrc(ctx, String(d.dexpreoptProperties.Dex_preopt.Profile)))
			profileIsTextListing = true
		} else {
			profileClassListing = android.ExistentPathForSource(ctx,
				global.ProfileDir, ctx.ModuleName()+".prof")
		}
	}

	dexpreoptConfig := dexpreopt.ModuleConfig{
		Name:            ctx.ModuleName(),
		DexLocation:     dexLocation,
		BuildPath:       android.PathForModuleOut(ctx, "dexpreopt", ctx.ModuleName()+".jar").OutputPath,
		DexPath:         dexJarFile,
		UncompressedDex: d.uncompressedDex,
		HasApkLibraries: false,
		PreoptFlags:     nil,

		ProfileClassListing:  profileClassListing,
		ProfileIsTextListing: profileIsTextListing,

		EnforceUsesLibraries:  false,
		OptionalUsesLibraries: nil,
		UsesLibraries:         nil,
		LibraryPaths:          nil,

		Archs:           archs,
		DexPreoptImages: images,

		// We use the dex paths and dex locations of the default boot image, as it
		// contains the full dexpreopt boot classpath. Other images may just contain a subset of
		// the dexpreopt boot classpath.
		PreoptBootClassPathDexFiles:     defaultBootImage.dexPaths.Paths(),
		PreoptBootClassPathDexLocations: defaultBootImage.dexLocations,

		PreoptExtractedApk: false,

		NoCreateAppImage:    !BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, true),
		ForceCreateAppImage: BoolDefault(d.dexpreoptProperties.Dex_preopt.App_image, false),

		NoStripping:     Bool(d.dexpreoptProperties.Dex_preopt.No_stripping),
		StripInputPath:  dexJarFile,
		StripOutputPath: strippedDexJarFile.OutputPath,
	}

	dexpreoptRule, err := dexpreopt.GenerateDexpreoptRule(ctx, global, dexpreoptConfig)
	if err != nil {
		ctx.ModuleErrorf("error generating dexpreopt rule: %s", err.Error())
		return dexJarFile
	}

	dexpreoptRule.Build(pctx, ctx, "dexpreopt", "dexpreopt")

	d.builtInstalled = dexpreoptRule.Installs().String()

	stripRule, err := dexpreopt.GenerateStripRule(global, dexpreoptConfig)
	if err != nil {
		ctx.ModuleErrorf("error generating dexpreopt strip rule: %s", err.Error())
		return dexJarFile
	}

	stripRule.Build(pctx, ctx, "dexpreopt_strip", "dexpreopt strip")

	return strippedDexJarFile
}
