// Copyright 2017 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 cc

import (
	"fmt"
	"strings"

	"android/soong/android"
	"android/soong/etc"
)

var (
	llndkLibrarySuffix = ".llndk"
)

// Holds properties to describe a stub shared library based on the provided version file.
type llndkLibraryProperties struct {
	// Relative path to the symbol map.
	// An example file can be seen here: TODO(danalbert): Make an example.
	Symbol_file *string `android:"path,arch_variant"`

	// Whether to export any headers as -isystem instead of -I. Mainly for use by
	// bionic/libc.
	Export_headers_as_system *bool

	// Whether the system library uses symbol versions.
	Unversioned *bool

	// list of llndk headers to re-export include directories from.
	Export_llndk_headers []string

	// list of directories relative to the Blueprints file that willbe added to the include path
	// (using -I) for any module that links against the LLNDK variant of this module, replacing
	// any that were listed outside the llndk clause.
	Override_export_include_dirs []string

	// whether this module can be directly depended upon by libs that are installed
	// to /vendor and /product.
	// When set to true, this module can only be depended on by VNDK libraries, not
	// vendor nor product libraries. This effectively hides this module from
	// non-system modules. Default value is false.
	Private *bool

	// if true, make this module available to provide headers to other modules that set
	// llndk.symbol_file.
	Llndk_headers *bool

	// moved_to_apex marks this module has having been distributed through an apex module.
	Moved_to_apex *bool
}

func makeLlndkVars(ctx android.MakeVarsContext) {

}

func init() {
	RegisterLlndkLibraryTxtType(android.InitRegistrationContext)
	android.RegisterParallelSingletonType("movedToApexLlndkLibraries", movedToApexLlndkLibrariesFactory)
}

func movedToApexLlndkLibrariesFactory() android.Singleton {
	return &movedToApexLlndkLibraries{}
}

type movedToApexLlndkLibraries struct {
	movedToApexLlndkLibraries []string
}

func (s *movedToApexLlndkLibraries) GenerateBuildActions(ctx android.SingletonContext) {
	// Make uses LLNDK_MOVED_TO_APEX_LIBRARIES to generate the linker config.
	movedToApexLlndkLibrariesMap := make(map[string]bool)
	ctx.VisitAllModuleProxies(func(module android.ModuleProxy) {
		if library, ok := android.OtherModuleProvider(ctx, module, LinkableInfoProvider); ok {
			if library.HasLLNDKStubs && library.IsLLNDKMovedToApex {
				movedToApexLlndkLibrariesMap[library.ImplementationModuleName] = true
			}
		}
	})
	s.movedToApexLlndkLibraries = android.SortedKeys(movedToApexLlndkLibrariesMap)

	var sb strings.Builder
	for i, l := range s.movedToApexLlndkLibraries {
		if i > 0 {
			sb.WriteRune(' ')
		}
		sb.WriteString(l)
		sb.WriteString(".so")
	}
	android.WriteFileRule(ctx, MovedToApexLlndkLibrariesFile(ctx), sb.String())
}

func MovedToApexLlndkLibrariesFile(ctx android.PathContext) android.WritablePath {
	return android.PathForIntermediates(ctx, "moved_to_apex_llndk_libraries.txt")
}

func (s *movedToApexLlndkLibraries) MakeVars(ctx android.MakeVarsContext) {
	ctx.Strict("LLNDK_MOVED_TO_APEX_LIBRARIES", strings.Join(s.movedToApexLlndkLibraries, " "))
}

func RegisterLlndkLibraryTxtType(ctx android.RegistrationContext) {
	ctx.RegisterParallelSingletonModuleType("llndk_libraries_txt", llndkLibrariesTxtFactory)
}

type llndkLibrariesTxtModule struct {
	android.SingletonModuleBase

	outputFile  android.OutputPath
	moduleNames []string
	fileNames   []string
}

var _ etc.PrebuiltEtcModule = &llndkLibrariesTxtModule{}

// llndk_libraries_txt is a singleton module whose content is a list of LLNDK libraries
// generated by Soong but can be referenced by other modules.
// For example, apex_vndk can depend on these files as prebuilt.
// Make uses LLNDK_LIBRARIES to determine which libraries to install.
// HWASAN is only part of the LL-NDK in builds in which libc depends on HWASAN.
// Therefore, by removing the library here, we cause it to only be installed if libc
// depends on it.
func llndkLibrariesTxtFactory() android.SingletonModule {
	m := &llndkLibrariesTxtModule{}
	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
	return m
}

func (txt *llndkLibrariesTxtModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	filename := txt.Name()

	txt.outputFile = android.PathForModuleOut(ctx, filename).OutputPath

	installPath := android.PathForModuleInstall(ctx, "etc")
	ctx.InstallFile(installPath, filename, txt.outputFile)

	ctx.SetOutputFiles(android.Paths{txt.outputFile}, "")

	etc.SetCommonPrebuiltEtcInfo(ctx, txt)
}

func getVndkFileName(info *LinkerInfo) (string, error) {
	if info != nil {
		if info.LibraryDecoratorInfo != nil {
			return info.LibraryDecoratorInfo.VndkFileName, nil
		}
		if info.PrebuiltLibraryLinkerInfo != nil {
			return info.PrebuiltLibraryLinkerInfo.VndkFileName, nil
		}
	}
	return "", fmt.Errorf("VNDK library should have libraryDecorator or prebuiltLibraryLinker as linker: %T", info)
}

func (txt *llndkLibrariesTxtModule) GenerateSingletonBuildActions(ctx android.SingletonContext) {
	if txt.outputFile.String() == "" {
		// Skip if target file path is empty
		return
	}

	ctx.VisitAllModuleProxies(func(m android.ModuleProxy) {
		ccInfo, ok := android.OtherModuleProvider(ctx, m, CcInfoProvider)
		if !ok {
			return
		}
		linkableInfo, ok := android.OtherModuleProvider(ctx, m, LinkableInfoProvider)
		if !ok {
			return
		}
		if linkableInfo.IsLlndk && !linkableInfo.Header && !linkableInfo.IsVndkPrebuiltLibrary {
			filename, err := getVndkFileName(ccInfo.LinkerInfo)
			if err != nil {
				ctx.ModuleErrorf(m, "%s", err)
			}

			if !strings.HasPrefix(ctx.ModuleName(m), "libclang_rt.hwasan") {
				txt.moduleNames = append(txt.moduleNames, ctx.ModuleName(m))
			}
			txt.fileNames = append(txt.fileNames, filename)
		}
	})
	txt.moduleNames = android.SortedUniqueStrings(txt.moduleNames)
	txt.fileNames = android.SortedUniqueStrings(txt.fileNames)

	android.WriteFileRule(ctx, txt.outputFile, strings.Join(txt.fileNames, "\n"))
}

func (txt *llndkLibrariesTxtModule) AndroidMkEntries() []android.AndroidMkEntries {
	return []android.AndroidMkEntries{{
		Class:      "ETC",
		OutputFile: android.OptionalPathForPath(txt.outputFile),
		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
				entries.SetString("LOCAL_MODULE_STEM", txt.outputFile.Base())
			},
		},
	}}
}

func (txt *llndkLibrariesTxtModule) MakeVars(ctx android.MakeVarsContext) {
	ctx.Strict("LLNDK_LIBRARIES", strings.Join(txt.moduleNames, " "))
}

// PrebuiltEtcModule interface
func (txt *llndkLibrariesTxtModule) BaseDir() string {
	return "etc"
}

// PrebuiltEtcModule interface
func (txt *llndkLibrariesTxtModule) SubDir() string {
	return ""
}

func llndkMutator(mctx android.BottomUpMutatorContext) {
	m, ok := mctx.Module().(*Module)
	if !ok {
		return
	}

	if shouldSkipLlndkMutator(mctx, m) {
		return
	}

	lib, isLib := m.linker.(*libraryDecorator)
	prebuiltLib, isPrebuiltLib := m.linker.(*prebuiltLibraryLinker)

	if m.InVendorOrProduct() && isLib && lib.HasLLNDKStubs() {
		m.VendorProperties.IsLLNDK = true
	}
	if m.InVendorOrProduct() && isPrebuiltLib && prebuiltLib.HasLLNDKStubs() {
		m.VendorProperties.IsLLNDK = true
	}

	if vndkprebuilt, ok := m.linker.(*vndkPrebuiltLibraryDecorator); ok {
		if !Bool(vndkprebuilt.properties.Vndk.Enabled) {
			m.VendorProperties.IsLLNDK = true
		}
	}
}

// Check for modules that mustn't be LLNDK
func shouldSkipLlndkMutator(mctx android.BottomUpMutatorContext, m *Module) bool {
	if !m.Enabled(mctx) {
		return true
	}
	if !m.Device() {
		return true
	}
	if m.Target().NativeBridge == android.NativeBridgeEnabled {
		return true
	}
	return false
}
