// Copyright 2020 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 (
	"fmt"
	"path/filepath"
	"testing"

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

func fixtureSetBootJarsProductVariable(bootJars ...string) android.FixturePreparer {
	return android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
		variables.BootJars = android.CreateTestConfiguredJarList(bootJars)
	})
}

func fixtureSetPrebuiltHiddenApiDirProductVariable(prebuiltHiddenApiDir *string) android.FixturePreparer {
	return android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
		variables.PrebuiltHiddenApiDir = prebuiltHiddenApiDir
	})
}

var hiddenApiFixtureFactory = android.GroupFixturePreparers(
	prepareForJavaTest, PrepareForTestWithHiddenApiBuildComponents)

func TestHiddenAPISingleton(t *testing.T) {
	result := android.GroupFixturePreparers(
		hiddenApiFixtureFactory,
		fixtureSetBootJarsProductVariable("platform:foo"),
	).RunTestWithBp(t, `
		java_library {
			name: "foo",
			srcs: ["a.java"],
			compile_dex: true,
		}
	`)

	hiddenAPI := result.SingletonForTests("hiddenapi")
	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
	want := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
}

func TestHiddenAPIIndexSingleton(t *testing.T) {
	result := android.GroupFixturePreparers(
		hiddenApiFixtureFactory,
		PrepareForTestWithJavaSdkLibraryFiles,
		FixtureWithLastReleaseApis("bar"),
		fixtureSetBootJarsProductVariable("platform:foo", "platform:bar"),
	).RunTestWithBp(t, `
		java_library {
			name: "foo",
			srcs: ["a.java"],
			compile_dex: true,

			hiddenapi_additional_annotations: [
				"foo-hiddenapi-annotations",
			],
		}

		java_library {
			name: "foo-hiddenapi-annotations",
			srcs: ["a.java"],
			compile_dex: true,
		}

		java_import {
			name: "foo",
			jars: ["a.jar"],
			compile_dex: true,
			prefer: false,
		}

		java_sdk_library {
			name: "bar",
			srcs: ["a.java"],
			compile_dex: true,
		}
	`)

	hiddenAPIIndex := result.SingletonForTests("hiddenapi_index")
	indexRule := hiddenAPIIndex.Rule("singleton-merged-hiddenapi-index")
	CheckHiddenAPIRuleInputs(t, `
.intermediates/bar/android_common/hiddenapi/index.csv
.intermediates/foo/android_common/hiddenapi/index.csv
`,
		indexRule)

	// Make sure that the foo-hiddenapi-annotations.jar is included in the inputs to the rules that
	// creates the index.csv file.
	foo := result.ModuleForTests("foo", "android_common")
	indexParams := foo.Output("hiddenapi/index.csv")
	CheckHiddenAPIRuleInputs(t, `
.intermediates/foo-hiddenapi-annotations/android_common/javac/foo-hiddenapi-annotations.jar
.intermediates/foo/android_common/javac/foo.jar
`, indexParams)
}

func TestHiddenAPISingletonWithSourceAndPrebuiltPreferredButNoDex(t *testing.T) {
	expectedErrorMessage :=
		"hiddenapi has determined that the source module \"foo\" should be ignored as it has been" +
			" replaced by the prebuilt module \"prebuilt_foo\" but unfortunately it does not provide a" +
			" suitable boot dex jar"

	android.GroupFixturePreparers(
		hiddenApiFixtureFactory,
		fixtureSetBootJarsProductVariable("platform:foo"),
	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(expectedErrorMessage)).
		RunTestWithBp(t, `
		java_library {
			name: "foo",
			srcs: ["a.java"],
			compile_dex: true,
		}

		java_import {
			name: "foo",
			jars: ["a.jar"],
			prefer: true,
		}
	`)
}

func TestHiddenAPISingletonWithPrebuilt(t *testing.T) {
	result := android.GroupFixturePreparers(
		hiddenApiFixtureFactory,
		fixtureSetBootJarsProductVariable("platform:foo"),
	).RunTestWithBp(t, `
		java_import {
			name: "foo",
			jars: ["a.jar"],
			compile_dex: true,
	}
	`)

	hiddenAPI := result.SingletonForTests("hiddenapi")
	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
	want := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, want)
}

func TestHiddenAPISingletonWithPrebuiltUseSource(t *testing.T) {
	result := android.GroupFixturePreparers(
		hiddenApiFixtureFactory,
		fixtureSetBootJarsProductVariable("platform:foo"),
	).RunTestWithBp(t, `
		java_library {
			name: "foo",
			srcs: ["a.java"],
			compile_dex: true,
		}

		java_import {
			name: "foo",
			jars: ["a.jar"],
			compile_dex: true,
			prefer: false,
		}
	`)

	hiddenAPI := result.SingletonForTests("hiddenapi")
	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
	fromSourceJarArg := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, fromSourceJarArg)

	prebuiltJarArg := "--boot-dex=out/soong/.intermediates/foo/android_common/dex/foo.jar"
	android.AssertStringDoesNotContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, prebuiltJarArg)
}

func TestHiddenAPISingletonWithPrebuiltOverrideSource(t *testing.T) {
	result := android.GroupFixturePreparers(
		hiddenApiFixtureFactory,
		fixtureSetBootJarsProductVariable("platform:foo"),
	).RunTestWithBp(t, `
		java_library {
			name: "foo",
			srcs: ["a.java"],
			compile_dex: true,
		}

		java_import {
			name: "foo",
			jars: ["a.jar"],
			compile_dex: true,
			prefer: true,
		}
	`)

	hiddenAPI := result.SingletonForTests("hiddenapi")
	hiddenapiRule := hiddenAPI.Rule("hiddenapi")
	prebuiltJarArg := "--boot-dex=out/soong/.intermediates/prebuilt_foo/android_common/dex/foo.jar"
	android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, prebuiltJarArg)

	fromSourceJarArg := "--boot-dex=out/soong/.intermediates/foo/android_common/aligned/foo.jar"
	android.AssertStringDoesNotContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, fromSourceJarArg)
}

func TestHiddenAPISingletonSdks(t *testing.T) {
	testCases := []struct {
		name             string
		unbundledBuild   bool
		publicStub       string
		systemStub       string
		testStub         string
		corePlatformStub string

		// Additional test preparer
		preparer android.FixturePreparer
	}{
		{
			name:             "testBundled",
			unbundledBuild:   false,
			publicStub:       "android_stubs_current",
			systemStub:       "android_system_stubs_current",
			testStub:         "android_test_stubs_current",
			corePlatformStub: "legacy.core.platform.api.stubs",
			preparer:         android.GroupFixturePreparers(),
		}, {
			name:             "testUnbundled",
			unbundledBuild:   true,
			publicStub:       "sdk_public_current_android",
			systemStub:       "sdk_system_current_android",
			testStub:         "sdk_test_current_android",
			corePlatformStub: "legacy.core.platform.api.stubs",
			preparer:         PrepareForTestWithPrebuiltsOfCurrentApi,
		},
	}
	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			result := android.GroupFixturePreparers(
				hiddenApiFixtureFactory,
				tc.preparer,
				android.FixtureModifyProductVariables(func(variables android.FixtureProductVariables) {
					variables.Always_use_prebuilt_sdks = proptools.BoolPtr(tc.unbundledBuild)
				}),
			).RunTest(t)

			hiddenAPI := result.SingletonForTests("hiddenapi")
			hiddenapiRule := hiddenAPI.Rule("hiddenapi")
			wantPublicStubs := "--public-stub-classpath=" + generateSdkDexPath(tc.publicStub, tc.unbundledBuild)
			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantPublicStubs)

			wantSystemStubs := "--system-stub-classpath=" + generateSdkDexPath(tc.systemStub, tc.unbundledBuild)
			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantSystemStubs)

			wantTestStubs := "--test-stub-classpath=" + generateSdkDexPath(tc.testStub, tc.unbundledBuild)
			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantTestStubs)

			wantCorePlatformStubs := "--core-platform-stub-classpath=" + generateDexPath(defaultJavaDir, tc.corePlatformStub)
			android.AssertStringDoesContain(t, "hiddenapi command", hiddenapiRule.RuleParams.Command, wantCorePlatformStubs)
		})
	}
}

func generateDexedPath(subDir, dex, module string) string {
	return fmt.Sprintf("out/soong/.intermediates/%s/android_common/%s/%s.jar", subDir, dex, module)
}

func generateDexPath(moduleDir string, module string) string {
	return generateDexedPath(filepath.Join(moduleDir, module), "dex", module)
}

func generateSdkDexPath(module string, unbundled bool) string {
	if unbundled {
		return generateDexedPath("prebuilts/sdk/"+module, "dex", module)
	}
	return generateDexPath(defaultJavaDir, module)
}

func TestHiddenAPISingletonWithPrebuiltCsvFile(t *testing.T) {

	// The idea behind this test is to ensure that when the build is
	// confugured with a PrebuiltHiddenApiDir that the rules for the
	// hiddenapi singleton copy the prebuilts to the typical output
	// location, and then use that output location for the hiddenapi encode
	// dex step.

	// Where to find the prebuilt hiddenapi files:
	prebuiltHiddenApiDir := "path/to/prebuilt/hiddenapi"

	result := android.GroupFixturePreparers(
		hiddenApiFixtureFactory,
		fixtureSetBootJarsProductVariable("platform:foo"),
		fixtureSetPrebuiltHiddenApiDirProductVariable(&prebuiltHiddenApiDir),
	).RunTestWithBp(t, `
		java_import {
			name: "foo",
			jars: ["a.jar"],
			compile_dex: true,
	}
	`)

	expectedCpInput := prebuiltHiddenApiDir + "/hiddenapi-flags.csv"
	expectedCpOutput := "out/soong/hiddenapi/hiddenapi-flags.csv"
	expectedFlagsCsv := "out/soong/hiddenapi/hiddenapi-flags.csv"

	foo := result.ModuleForTests("foo", "android_common")

	hiddenAPI := result.SingletonForTests("hiddenapi")
	cpRule := hiddenAPI.Rule("Cp")
	actualCpInput := cpRule.BuildParams.Input
	actualCpOutput := cpRule.BuildParams.Output
	encodeDexRule := foo.Rule("hiddenAPIEncodeDex")
	actualFlagsCsv := encodeDexRule.BuildParams.Args["flagsCsv"]

	android.AssertPathRelativeToTopEquals(t, "hiddenapi cp rule input", expectedCpInput, actualCpInput)

	android.AssertPathRelativeToTopEquals(t, "hiddenapi cp rule output", expectedCpOutput, actualCpOutput)

	android.AssertStringEquals(t, "hiddenapi encode dex rule flags csv", expectedFlagsCsv, actualFlagsCsv)
}
