// Copyright (C) 2024 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 fsgen

import (
	"android/soong/android"
	"android/soong/etc"
	"android/soong/filesystem"
	"fmt"
	"strconv"
	"strings"

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

// Returns the appropriate dpi for recovery common resources selection. Replicates the logic in
// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2536;drc=a6af369e71ded123734523ea640b97b70a557cb9
func getDpi(ctx android.EarlyModuleContext) string {
	recoveryDensity := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.TargetScreenDensity
	if len(recoveryDensity) == 0 {
		aaptPreferredConfig := ctx.Config().ProductAAPTPreferredConfig()
		if len(aaptPreferredConfig) > 0 {
			recoveryDensity = aaptPreferredConfig
		} else {
			recoveryDensity = "mdpi"
		}
	}
	if !android.InList(recoveryDensity, []string{"xxxhdpi", "xxhdpi", "xhdpi", "hdpi", "mdpi"}) {
		recoveryDensity = strings.TrimSuffix(recoveryDensity, "dpi")
		dpiInt, err := strconv.ParseInt(recoveryDensity, 10, 64)
		if err != nil {
			panic(fmt.Sprintf("Error in parsing recoveryDensity: %s", err.Error()))
		}
		if dpiInt >= 560 {
			recoveryDensity = "xxxhdpi"
		} else if dpiInt >= 400 {
			recoveryDensity = "xxhdpi"
		} else if dpiInt >= 280 {
			recoveryDensity = "xhdpi"
		} else if dpiInt >= 200 {
			recoveryDensity = "hdpi"
		} else {
			recoveryDensity = "mdpi"
		}
	}

	if p := android.ExistentPathForSource(ctx, fmt.Sprintf("bootable/recovery/res-%s", recoveryDensity)); !p.Valid() {
		recoveryDensity = "xhdpi"
	}

	return recoveryDensity
}

// Returns the name of the appropriate prebuilt module for installing font.png file.
// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2536;drc=a6af369e71ded123734523ea640b97b70a557cb9
func getRecoveryFontModuleName(ctx android.LoadHookContext) string {
	if android.InList(getDpi(ctx), []string{"xxxhdpi", "xxhdpi", "xhdpi"}) {
		return "recovery-fonts-18"
	}
	return "recovery-fonts-12"
}

// Returns the module name and image width for recovery background image generation.
// https://source.corp.google.com/h/googleplex-android/platform/build/+/f1cb088a1446adc54950bd6b447eac9fc90831f6:core/Makefile;l=2539-2556;drc=acc8d6594498f50db20f04e0e05fff09750c412c;bpv=1;bpt=0
func getRecoveryBackgroundPicturesGeneratorModuleName(ctx android.EarlyModuleContext) (string, int64) {
	dpi := getDpi(ctx)
	if !android.InList(dpi, []string{"xxxhdpi", "xxhdpi"}) {
		return "", -1
	}
	var recoveryUiScreenWidth int64
	if dpi == "xxxhdpi" {
		recoveryUiScreenWidth = 1440 - 10
	} else { // xxhdpi
		recoveryUiScreenWidth = 1080 - 10
	}

	if marginWidth, exists := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.PrivateRecoveryUiProperties["margin_width"]; exists && marginWidth != "" {
		if marginWidth, err := strconv.ParseInt(marginWidth, 10, 64); err == nil {
			recoveryUiScreenWidth -= marginWidth
		} else {
			ctx.ModuleErrorf("Error parsing TARGET_RECOVERY_UI_MARGIN_WIDTH %s", err)
		}
	}

	return generatedModuleNameForPartition(ctx.Config(), "background_recovery_images"), recoveryUiScreenWidth
}

// Returns a new list of symlinks with prefix added to the dest directory for all symlinks
func symlinksWithNamePrefix(symlinks []filesystem.SymlinkDefinition, prefix string) []filesystem.SymlinkDefinition {
	ret := make([]filesystem.SymlinkDefinition, len(symlinks))
	for i, symlink := range symlinks {
		ret[i] = symlink.CopyWithNamePrefix(prefix)
	}
	return ret
}

// Creates a prebuilt_etc for recovery partition if TARGET_RECOVERY_WIPE is not empty.
// Returns the name of the autogenerated module.
func createTargetRecoveryWipeModuleName(ctx android.LoadHookContext) string {
	recoveryWipe := ctx.Config().ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse.TargetRecoveryWipe
	if recoveryWipe == "" {
		return ""
	}
	name := generatedModuleName(ctx.Config(), "target_recovery_wipe")
	ctx.CreateModuleInDirectory(
		etc.PrebuiltEtcFactory,
		".",
		&struct {
			Name     *string
			Srcs     []string
			Dsts     []string
			Recovery *bool
		}{
			Name:     proptools.StringPtr(name),
			Srcs:     []string{recoveryWipe},
			Dsts:     []string{"recovery.wipe"},
			Recovery: proptools.BoolPtr(true),
		},
	)
	return name
}

func getRecoveryFsTabSrc(config android.Config) (recoveryFstabSrc string) {
	partitionVars := config.ProductVariables().PartitionVarsForSoongMigrationOnlyDoNotUse
	if len(partitionVars.TargetRecoveryFstab) > 0 {
		recoveryFstabSrc = partitionVars.TargetRecoveryFstab
	} else if len(partitionVars.TargetRecoveryFstabGenrule) > 0 {
		recoveryFstabSrc = ":" + partitionVars.TargetRecoveryFstabGenrule

	} else {
		recoveryFstabSrc = partitionVars.TargetRecoveryFstabDefault
	}
	return
}

// Creates a prebuilt_etc module for recovery partition based on the values of
// TARGET_RECOVERY_FSTAB, TARGET_RECOVERY_FSTAB_GENRULE and TARGET_DEVICE_DIR.
// https://cs.android.com/android/platform/superproject/main/+/main:build/make/core/Makefile;l=2616-2622;drc=a951ebf0198006f7fd38073a05c442d0eb92f97b
func handleRecoveryFstab(ctx android.LoadHookContext) (moduleName string) {
	if recoveryFstabSrc := getRecoveryFsTabSrc(ctx.Config()); len(recoveryFstabSrc) > 0 {
		moduleName = generatedModuleName(ctx.Config(), "recovery_fstab")
		ctx.CreateModuleInDirectory(
			etc.PrebuiltEtcFactory,
			".",
			&struct {
				Name     *string
				Dsts     []string
				Recovery *bool
			}{
				Name:     proptools.StringPtr(moduleName),
				Dsts:     []string{"recovery.fstab"},
				Recovery: proptools.BoolPtr(true),
			},
		)
	}
	return
}

func setRecoveryFstabSrcs(ctx android.BottomUpMutatorContext) {
	if !shouldEnableFilesystemCreator(ctx) {
		return
	}
	recoveryFstabModuleName := ctx.Config().Get(fsGenStateOnceKey).(*FsGenState).recoveryFstabModuleName
	if len(recoveryFstabModuleName) > 0 && ctx.ModuleName() == recoveryFstabModuleName {
		recoveryFstabSrc := getRecoveryFsTabSrc(ctx.Config())
		resolvedRecoveryFstabSrcName := recoveryFstabSrc
		// If the recovery fstab src start with ":", it's depending on a genrule
		// module. However the genrule may be under a soong namespace, thus this
		// needs to be resolved to a fully qualified module name.
		if strings.HasPrefix(recoveryFstabSrc, ":") {
			for _, namespace := range ctx.Config().ProductVariables().NamespacesToExport {
				if moduleName := fmt.Sprintf("//%s:%s", namespace, recoveryFstabSrc[1:]); ctx.OtherModuleExists(moduleName) {
					resolvedRecoveryFstabSrcName = moduleName
				}
			}
		}
		proptools.AppendMatchingProperties(
			ctx.Module().GetProperties(),
			&struct {
				Srcs []string
			}{
				Srcs: []string{resolvedRecoveryFstabSrcName},
			},
			nil,
		)
	}
}
