// Copyright (C) 2022 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 (
	"encoding/json"
	"fmt"

	"android/soong/android"
)

func init() {
	android.RegisterModuleType("fuzzer_bindings_test", fuzzerBindingsTestFactory)
	android.PreArchMutators(registerFuzzerMutators)
}

func registerFuzzerMutators(ctx android.RegisterMutatorsContext) {
	ctx.BottomUp("addFuzzerConfigDeps", addFuzzerConfigDeps).Parallel()
}

func addFuzzerConfigDeps(ctx android.BottomUpMutatorContext) {
	if _, ok := ctx.Module().(*fuzzerBindingsTestModule); ok {
		for _, fuzzers := range ServiceFuzzerBindings {
			for _, fuzzer := range fuzzers {
				if !ctx.OtherModuleExists(fuzzer) && !ctx.Config().AllowMissingDependencies() {
					panic(fmt.Errorf("Fuzzer doesn't exist : %s", fuzzer))
				}
			}
		}
	}
}

type bindingsTestProperties struct {
	// Contexts files to be tested.
	Srcs []string `android:"path"`
}

type fuzzerBindingsTestModule struct {
	android.ModuleBase
	tool          string
	properties    bindingsTestProperties
	testTimestamp android.ModuleOutPath
}

// fuzzer_bindings_test checks if a fuzzer is implemented for every service in service_contexts
func fuzzerBindingsTestFactory() android.Module {
	m := &fuzzerBindingsTestModule{tool: "fuzzer_bindings_check"}
	m.AddProperties(&m.properties)
	android.InitAndroidArchModule(m, android.DeviceSupported, android.MultilibCommon)
	return m
}

func (m *fuzzerBindingsTestModule) GenerateAndroidBuildActions(ctx android.ModuleContext) {
	tool := m.tool
	if tool != "fuzzer_bindings_check" {
		panic(fmt.Errorf("%q: unknown tool name: %q", ctx.ModuleName(), tool))
	}

	if len(m.properties.Srcs) == 0 {
		ctx.PropertyErrorf("srcs", "can't be empty")
		return
	}

	// Generate a json file which contains existing bindings
	rootPath := android.PathForIntermediates(ctx, "bindings.json")
	jsonString, parseError := json.Marshal(ServiceFuzzerBindings)
	if parseError != nil {
		panic(fmt.Errorf("Error while marshalling ServiceFuzzerBindings dict. Check Format"))
	}
	android.WriteFileRule(ctx, rootPath, string(jsonString))

	//input module json, service context and binding files here
	srcs := android.PathsForModuleSrc(ctx, m.properties.Srcs)
	rule := android.NewRuleBuilder(pctx, ctx)

	rule.Command().BuiltTool(tool).Flag("-s").Inputs(srcs).Flag("-b").Input(rootPath)

	// Every Soong module needs to generate an output even if it doesn't require it
	m.testTimestamp = android.PathForModuleOut(ctx, "timestamp")
	rule.Command().Text("touch").Output(m.testTimestamp)
	rule.Build("fuzzer_bindings_test", "running service:fuzzer bindings test: "+ctx.ModuleName())

	ctx.CheckbuildFile(m.testTimestamp)
}

func (m *fuzzerBindingsTestModule) AndroidMkEntries() []android.AndroidMkEntries {
	return []android.AndroidMkEntries{android.AndroidMkEntries{
		Class: "FAKE",
		// OutputFile is needed, even though BUILD_PHONY_PACKAGE doesn't use it.
		// Without OutputFile this module won't be exported to Makefile.
		OutputFile: android.OptionalPathForPath(m.testTimestamp),
		Include:    "$(BUILD_PHONY_PACKAGE)",
		ExtraEntries: []android.AndroidMkExtraEntriesFunc{
			func(ctx android.AndroidMkExtraEntriesContext, entries *android.AndroidMkEntries) {
				entries.SetString("LOCAL_ADDITIONAL_DEPENDENCIES", m.testTimestamp.String())
			},
		},
	}}
}
