// Copyright (C) 2023 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 selinux

import (
	"maps"

	"android/soong/android"

	"github.com/google/blueprint"
)

var (
	flagsDepTag      = dependencyTag{name: "flags"}
	buildFlagsDepTag = dependencyTag{name: "build_flags"}
)

func init() {
	ctx := android.InitRegistrationContext
	ctx.RegisterModuleType("se_flags", flagsFactory)
	ctx.RegisterModuleType("se_flags_collector", flagsCollectorFactory)
}

type flagsProperties struct {
	// List of build time flags for flag-guarding.
	Flags []string

	// List of se_flags_collector modules to export flags to.
	Export_to []string
}

type flagsModule struct {
	android.ModuleBase
	properties flagsProperties
}

type flagsInfo struct {
	Flags []string
}

var flagsProviderKey = blueprint.NewProvider[flagsInfo]()

// se_flags contains a list of build time flags for sepolicy.  Build time flags are defined under
// .scl files (e.g. build/release/build_flags.scl). By importing flags with se_flags modules,
// sepolicy rules can be guarded by `is_flag_enabled` / `is_flag_disabled` macro.
//
// For example, an Android.bp file could have:
//
//	se_flags {
//		name: "aosp_selinux_flags",
//		flags: ["RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT"],
//		export_to: ["all_selinux_flags"],
//	}
//
// And then one could flag-guard .te file rules:
//
//	is_flag_enabled(RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT, `
//		type vfio_handler, domain, coredomain;
//		binder_use(vfio_handler)
//	')
//
// or contexts entries:
//
//	is_flag_enabled(RELEASE_AVF_ENABLE_DEVICE_ASSIGNMENT, `
//		android.system.virtualizationservice_internal.IVfioHandler u:object_r:vfio_handler_service:s0
//	')
func flagsFactory() android.Module {
	module := &flagsModule{}
	module.AddProperties(&module.properties)
	android.InitAndroidModule(module)
	return module
}

func (f *flagsModule) DepsMutator(ctx android.BottomUpMutatorContext) {
	// dep se_flag_collector -> se_flags
	for _, export := range f.properties.Export_to {
		ctx.AddReverseDependency(ctx.Module(), flagsDepTag, export)
	}
}

func (f *flagsModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	android.SetProvider(ctx, flagsProviderKey, flagsInfo{
		Flags: f.properties.Flags,
	})
}

type buildFlagsInfo struct {
	BuildFlags map[string]string
}

var buildFlagsInfoProvider = blueprint.NewProvider[buildFlagsInfo]()

type flagsCollectorModule struct {
	android.ModuleBase
	buildFlags map[string]string
}

// se_flags_collector module collects flags from exported se_flags modules (see export_to property
// of se_flags modules), and then converts them into build-time flags.  It will be used to generate
// M4 macros to flag-guard sepolicy.
func flagsCollectorFactory() android.Module {
	module := &flagsCollectorModule{}
	android.InitAndroidModule(module)
	return module
}

func (f *flagsCollectorModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	var flags []string
	ctx.VisitDirectDepsProxyWithTag(flagsDepTag, func(m android.ModuleProxy) {
		if dep, ok := android.OtherModuleProvider(ctx, m, flagsProviderKey); ok {
			flags = append(flags, dep.Flags...)
		} else {
			ctx.ModuleErrorf("unknown dependency %q", ctx.OtherModuleName(m))
		}
	})
	buildFlags := make(map[string]string)
	for _, flag := range android.SortedUniqueStrings(flags) {
		if val, ok := ctx.Config().GetBuildFlag(flag); ok {
			buildFlags[flag] = val
		}
	}
	android.SetProvider(ctx, buildFlagsInfoProvider, buildFlagsInfo{
		BuildFlags: buildFlags,
	})
}

type flaggableModuleProperties struct {
	// List of se_flag_collector modules to be passed to M4 macro.
	Build_flags []string
}

type flaggableModule interface {
	android.Module
	flagModuleBase() *flaggableModuleBase
	flagDeps(ctx android.BottomUpMutatorContext)
	getBuildFlags(ctx android.ModuleContext) map[string]string
}

type flaggableModuleBase struct {
	properties flaggableModuleProperties
}

func initFlaggableModule(m flaggableModule) {
	base := m.flagModuleBase()
	m.AddProperties(&base.properties)
}

func (f *flaggableModuleBase) flagModuleBase() *flaggableModuleBase {
	return f
}

func (f *flaggableModuleBase) flagDeps(ctx android.BottomUpMutatorContext) {
	ctx.AddDependency(ctx.Module(), buildFlagsDepTag, f.properties.Build_flags...)
}

// getBuildFlags returns a map from flag names to flag values.
func (f *flaggableModuleBase) getBuildFlags(ctx android.ModuleContext) map[string]string {
	ret := make(map[string]string)
	ctx.VisitDirectDepsProxyWithTag(buildFlagsDepTag, func(m android.ModuleProxy) {
		if dep, ok := android.OtherModuleProvider(ctx, m, buildFlagsInfoProvider); ok {
			maps.Copy(ret, dep.BuildFlags)
		} else {
			ctx.PropertyErrorf("build_flags", "unknown dependency %q", ctx.OtherModuleName(m))
		}
	})
	return ret
}
