// 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 android

import (
	"testing"

	"github.com/google/blueprint"
)

// Module to be packaged
type componentTestModule struct {
	ModuleBase
	props struct {
		Deps []string
	}
}

// dep tag used in this test. All dependencies are considered as installable.
type installDepTag struct {
	blueprint.BaseDependencyTag
	InstallAlwaysNeededDependencyTag
}

func componentTestModuleFactory() Module {
	m := &componentTestModule{}
	m.AddProperties(&m.props)
	InitAndroidArchModule(m, HostAndDeviceSupported, MultilibBoth)
	return m
}

func (m *componentTestModule) DepsMutator(ctx BottomUpMutatorContext) {
	ctx.AddDependency(ctx.Module(), installDepTag{}, m.props.Deps...)
}

func (m *componentTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
	builtFile := PathForModuleOut(ctx, m.Name())
	dir := ctx.Target().Arch.ArchType.Multilib
	installDir := PathForModuleInstall(ctx, dir)
	ctx.InstallFile(installDir, m.Name(), builtFile)
}

// Module that itself is a package
type packageTestModule struct {
	ModuleBase
	PackagingBase
	properties struct {
		Install_deps []string `android:`
	}
	entries []string
}

func packageMultiTargetTestModuleFactory() Module {
	module := &packageTestModule{}
	InitPackageModule(module)
	InitAndroidMultiTargetsArchModule(module, DeviceSupported, MultilibCommon)
	module.AddProperties(&module.properties)
	return module
}

func packageTestModuleFactory() Module {
	module := &packageTestModule{}
	InitPackageModule(module)
	InitAndroidArchModule(module, DeviceSupported, MultilibBoth)
	module.AddProperties(&module.properties)
	return module
}

type packagingDepTag struct {
	blueprint.BaseDependencyTag
	PackagingItemAlwaysDepTag
}

func (m *packageTestModule) DepsMutator(ctx BottomUpMutatorContext) {
	m.AddDeps(ctx, packagingDepTag{})
	ctx.AddDependency(ctx.Module(), installDepTag{}, m.properties.Install_deps...)
}

func (m *packageTestModule) GenerateAndroidBuildActions(ctx ModuleContext) {
	zipFile := PathForModuleOut(ctx, "myzip.zip")
	m.entries = m.CopyDepsToZip(ctx, zipFile)
}

func runPackagingTest(t *testing.T, multitarget bool, bp string, expected []string) {
	t.Helper()

	var archVariant string
	var moduleFactory ModuleFactory
	if multitarget {
		archVariant = "android_common"
		moduleFactory = packageMultiTargetTestModuleFactory
	} else {
		archVariant = "android_arm64_armv8-a"
		moduleFactory = packageTestModuleFactory
	}

	result := GroupFixturePreparers(
		PrepareForTestWithArchMutator,
		FixtureRegisterWithContext(func(ctx RegistrationContext) {
			ctx.RegisterModuleType("component", componentTestModuleFactory)
			ctx.RegisterModuleType("package_module", moduleFactory)
		}),
		FixtureWithRootAndroidBp(bp),
	).RunTest(t)

	p := result.Module("package", archVariant).(*packageTestModule)
	actual := p.entries
	actual = SortedUniqueStrings(actual)
	expected = SortedUniqueStrings(expected)
	AssertDeepEquals(t, "package entries", expected, actual)
}

func TestPackagingBaseMultiTarget(t *testing.T) {
	multiTarget := true
	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
		}

		package_module {
			name: "package",
			deps: ["foo"],
		}
		`, []string{"lib64/foo"})

	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
			deps: ["bar"],
		}

		component {
			name: "bar",
		}

		package_module {
			name: "package",
			deps: ["foo"],
		}
		`, []string{"lib64/foo", "lib64/bar"})

	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
			deps: ["bar"],
		}

		component {
			name: "bar",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			compile_multilib: "both",
		}
		`, []string{"lib32/foo", "lib32/bar", "lib64/foo", "lib64/bar"})

	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
		}

		component {
			name: "bar",
			compile_multilib: "32",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			multilib: {
				lib32: {
					deps: ["bar"],
				},
			},
			compile_multilib: "both",
		}
		`, []string{"lib32/foo", "lib32/bar", "lib64/foo"})

	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
		}

		component {
			name: "bar",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			multilib: {
				first: {
					deps: ["bar"],
				},
			},
			compile_multilib: "both",
		}
		`, []string{"lib32/foo", "lib64/foo", "lib64/bar"})

	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
		}

		component {
			name: "bar",
		}

		component {
			name: "baz",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			arch: {
				arm64: {
					deps: ["bar"],
				},
				x86_64: {
					deps: ["baz"],
				},
			},
			compile_multilib: "both",
		}
		`, []string{"lib32/foo", "lib64/foo", "lib64/bar"})
}

func TestPackagingBaseSingleTarget(t *testing.T) {
	multiTarget := false
	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
		}

		package_module {
			name: "package",
			deps: ["foo"],
		}
		`, []string{"lib64/foo"})

	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
			deps: ["bar"],
		}

		component {
			name: "bar",
		}

		package_module {
			name: "package",
			deps: ["foo"],
		}
		`, []string{"lib64/foo", "lib64/bar"})

	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
		}

		component {
			name: "bar",
			compile_multilib: "32",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			multilib: {
				lib32: {
					deps: ["bar"],
				},
			},
		}
		`, []string{"lib64/foo"})

	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
		}

		component {
			name: "bar",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			multilib: {
				lib64: {
					deps: ["bar"],
				},
			},
		}
		`, []string{"lib64/foo", "lib64/bar"})

	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
		}

		component {
			name: "bar",
		}

		component {
			name: "baz",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			arch: {
				arm64: {
					deps: ["bar"],
				},
				x86_64: {
					deps: ["baz"],
				},
			},
		}
		`, []string{"lib64/foo", "lib64/bar"})

	runPackagingTest(t, multiTarget,
		`
		component {
			name: "foo",
		}

		component {
			name: "bar",
		}

		package_module {
			name: "package",
			deps: ["foo"],
			install_deps: ["bar"],
		}
		`, []string{"lib64/foo"})
}
