// Copyright (C) 2021 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 sdk

import (
	"fmt"
	"path/filepath"
	"testing"

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

type fakeMemberTrait struct {
	android.SdkMemberTraitBase
}

type fakeMemberType struct {
	android.SdkMemberTypeBase
}

func (t *fakeMemberType) AddDependencies(ctx android.SdkDependencyContext, dependencyTag blueprint.DependencyTag, names []string) {
	for _, name := range names {
		ctx.AddVariationDependencies(nil, dependencyTag, name)

		if ctx.RequiresTrait(name, extraTrait) {
			ctx.AddVariationDependencies(nil, dependencyTag, name+"_extra")
		}
		if ctx.RequiresTrait(name, specialTrait) {
			ctx.AddVariationDependencies(nil, dependencyTag, name+"_special")
		}
	}
}

func (t *fakeMemberType) IsInstance(_ android.ModuleContext, _ android.ModuleProxy) bool {
	return true
}

func (t *fakeMemberType) AddPrebuiltModule(ctx android.SdkMemberContext, member android.SdkMember) android.BpModule {
	moduleType := "java_import"
	if ctx.RequiresTrait(extraTrait) {
		moduleType = "java_test_import"
	}
	return ctx.SnapshotBuilder().AddPrebuiltModule(member, moduleType)
}

func (t *fakeMemberType) CreateVariantPropertiesStruct() android.SdkMemberProperties {
	return &fakeMemberTypeProperties{}
}

type fakeMemberTypeProperties struct {
	android.SdkMemberPropertiesBase

	path android.Path
}

func (t *fakeMemberTypeProperties) PopulateFromVariant(ctx android.SdkMemberContext, variant android.ModuleProxy) {
	headerJars := android.OtherModulePointerProviderOrDefault(ctx.SdkModuleContext(), variant, java.JavaInfoProvider).HeaderJars
	if len(headerJars) != 1 {
		panic(fmt.Errorf("there must be only one header jar from %q", variant.Name()))
	}

	t.path = headerJars[0]
}

func (t *fakeMemberTypeProperties) AddToPropertySet(ctx android.SdkMemberContext, propertySet android.BpPropertySet) {
	if t.path != nil {
		relative := filepath.Join("javalibs", t.path.Base())
		ctx.SnapshotBuilder().CopyToSnapshot(t.path, relative)
		propertySet.AddProperty("jars", []string{relative})
	}
}

var (
	extraTrait = &fakeMemberTrait{
		SdkMemberTraitBase: android.SdkMemberTraitBase{
			PropertyName: "extra",
		},
	}

	specialTrait = &fakeMemberTrait{
		SdkMemberTraitBase: android.SdkMemberTraitBase{
			PropertyName: "special",
		},
	}

	fakeType = &fakeMemberType{
		SdkMemberTypeBase: android.SdkMemberTypeBase{
			PropertyName: "fake_members",
			SupportsSdk:  true,
			Traits: []android.SdkMemberTrait{
				extraTrait,
				specialTrait,
			},
		},
	}
)

func init() {
	android.RegisterSdkMemberTrait(extraTrait)
	android.RegisterSdkMemberTrait(specialTrait)
	android.RegisterSdkMemberType(fakeType)
}

func TestBasicTrait_WithoutTrait(t *testing.T) {
	t.Parallel()
	result := android.GroupFixturePreparers(
		prepareForSdkTestWithJava,
		android.FixtureWithRootAndroidBp(`
			sdk {
				name: "mysdk",
				fake_members: ["myjavalib"],
			}

			java_library {
				name: "myjavalib",
				srcs: ["Test.java"],
				system_modules: "none",
				sdk_version: "none",
			}
		`),
	).RunTest(t)

	CheckSnapshot(t, result, "mysdk", "",
		checkAndroidBpContents(`
// This is auto-generated. DO NOT EDIT.

apex_contributions_defaults {
    name: "mysdk.contributions",
    contents: ["prebuilt_myjavalib"],
}

java_import {
    name: "myjavalib",
    prefer: false,
    visibility: ["//visibility:public"],
    apex_available: ["//apex_available:platform"],
    jars: ["javalibs/myjavalib.jar"],
}
`),
	)
}

func TestBasicTrait_MultipleTraits(t *testing.T) {
	t.Parallel()
	result := android.GroupFixturePreparers(
		prepareForSdkTestWithJava,
		android.FixtureWithRootAndroidBp(`
			sdk {
				name: "mysdk",
				fake_members: ["myjavalib", "anotherjavalib"],
				traits: {
					extra: ["myjavalib"],
					special: ["myjavalib", "anotherjavalib"],
				},
			}

			java_library {
				name: "myjavalib",
				srcs: ["Test.java"],
				system_modules: "none",
				sdk_version: "none",
			}

			java_library {
				name: "myjavalib_extra",
				srcs: ["Test.java"],
				system_modules: "none",
				sdk_version: "none",
			}

			java_library {
				name: "myjavalib_special",
				srcs: ["Test.java"],
				system_modules: "none",
				sdk_version: "none",
			}

			java_library {
				name: "anotherjavalib",
				srcs: ["Test.java"],
				system_modules: "none",
				sdk_version: "none",
			}

			java_library {
				name: "anotherjavalib_special",
				srcs: ["Test.java"],
				system_modules: "none",
				sdk_version: "none",
			}
		`),
	).RunTest(t)

	CheckSnapshot(t, result, "mysdk", "",
		checkAndroidBpContents(`
// This is auto-generated. DO NOT EDIT.

apex_contributions_defaults {
    name: "mysdk.contributions",
    contents: [
        "prebuilt_myjavalib",
        "prebuilt_myjavalib_extra",
        "prebuilt_myjavalib_special",
        "prebuilt_anotherjavalib",
        "prebuilt_anotherjavalib_special",
    ],
}

java_test_import {
    name: "myjavalib",
    prefer: false,
    visibility: ["//visibility:public"],
    apex_available: ["//apex_available:platform"],
    jars: ["javalibs/myjavalib.jar"],
}

java_import {
    name: "myjavalib_extra",
    prefer: false,
    visibility: ["//visibility:public"],
    apex_available: ["//apex_available:platform"],
    jars: ["javalibs/myjavalib_extra.jar"],
}

java_import {
    name: "myjavalib_special",
    prefer: false,
    visibility: ["//visibility:public"],
    apex_available: ["//apex_available:platform"],
    jars: ["javalibs/myjavalib_special.jar"],
}

java_import {
    name: "anotherjavalib",
    prefer: false,
    visibility: ["//visibility:public"],
    apex_available: ["//apex_available:platform"],
    jars: ["javalibs/anotherjavalib.jar"],
}

java_import {
    name: "anotherjavalib_special",
    prefer: false,
    visibility: ["//visibility:public"],
    apex_available: ["//apex_available:platform"],
    jars: ["javalibs/anotherjavalib_special.jar"],
}
`),
	)
}

func TestTraitUnsupportedByMemberType(t *testing.T) {
	t.Parallel()
	android.GroupFixturePreparers(
		prepareForSdkTestWithJava,
		android.FixtureWithRootAndroidBp(`
			sdk {
				name: "mysdk",
				java_header_libs: ["myjavalib"],
				traits: {
					extra: ["myjavalib"],
				},
			}

			java_library {
				name: "myjavalib",
				srcs: ["Test.java"],
				system_modules: "none",
				sdk_version: "none",
			}
		`),
	).ExtendWithErrorHandler(android.FixtureExpectsAtLeastOneErrorMatchingPattern(
		`\Qsdk member "myjavalib" has traits [extra] that are unsupported by its member type "java_header_libs"\E`)).
		RunTest(t)
}
