// Copyright 2019 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 sdk

import (
	"android/soong/android"
	"log"
	"os"
	"testing"

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

// Needed in an _test.go file in this package to ensure tests run correctly, particularly in IDE.
func TestMain(m *testing.M) {
	if android.BuildOs != android.Linux {
		// b/145598135 - Generating host snapshots for anything other than linux is not supported.
		log.Printf("Skipping as sdk snapshot generation is only supported on %s not %s", android.Linux, android.BuildOs)
		os.Exit(0)
	}

	runTestWithBuildDir(m)
}

func TestDepNotInRequiredSdks(t *testing.T) {
	testSdkError(t, `module "myjavalib".*depends on "otherlib".*that isn't part of the required SDKs:.*`, `
		sdk {
			name: "mysdk",
			java_header_libs: ["sdkmember"],
		}

		sdk_snapshot {
			name: "mysdk@1",
			java_header_libs: ["sdkmember_mysdk_1"],
		}

		java_import {
			name: "sdkmember",
			prefer: false,
			host_supported: true,
		}

		java_import {
			name: "sdkmember_mysdk_1",
			sdk_member_name: "sdkmember",
			host_supported: true,
		}

		java_library {
			name: "myjavalib",
			srcs: ["Test.java"],
			libs: [
				"sdkmember",
				"otherlib",
			],
			system_modules: "none",
			sdk_version: "none",
			compile_dex: true,
			host_supported: true,
			apex_available: ["myapex"],
		}

		// this lib is no in mysdk
		java_library {
			name: "otherlib",
			srcs: ["Test.java"],
			system_modules: "none",
			sdk_version: "none",
			compile_dex: true,
			host_supported: true,
		}

		apex {
			name: "myapex",
			java_libs: ["myjavalib"],
			uses_sdks: ["mysdk@1"],
			key: "myapex.key",
			certificate: ":myapex.cert",
		}
	`)
}

// Ensure that prebuilt modules have the same effective visibility as the source
// modules.
func TestSnapshotVisibility(t *testing.T) {
	packageBp := `
		package {
			default_visibility: ["//other/foo"],
		}

		sdk {
			name: "mysdk",
			visibility: [
				"//other/foo",
				// This short form will be replaced with //package:__subpackages__ in the
				// generated sdk_snapshot.
				":__subpackages__",
			],
			prebuilt_visibility: [
				"//prebuilts/mysdk",
			],
			java_header_libs: [
				"myjavalib",
				"mypublicjavalib",
				"mydefaultedjavalib",
				"myprivatejavalib",
			],
		}

		java_library {
			name: "myjavalib",
			// Uses package default visibility
			srcs: ["Test.java"],
			system_modules: "none",
			sdk_version: "none",
		}

		java_defaults {
			name: "java-defaults",
			visibility: ["//other/bar"],
		}

		java_library {
			name: "mypublicjavalib",
			defaults: ["java-defaults"],
      visibility: ["//visibility:public"],
			srcs: ["Test.java"],
			system_modules: "none",
			sdk_version: "none",
		}

		java_defaults {
			name: "myjavadefaults",
			visibility: ["//other/bar"],
		}

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

		java_library {
			name: "myprivatejavalib",
			srcs: ["Test.java"],
			visibility: ["//visibility:private"],
			system_modules: "none",
			sdk_version: "none",
		}
	`

	result := testSdkWithFs(t, ``,
		map[string][]byte{
			"package/Test.java":  nil,
			"package/Android.bp": []byte(packageBp),
		})

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

java_import {
    name: "mysdk_myjavalib@current",
    sdk_member_name: "myjavalib",
    visibility: [
        "//other/foo",
        "//package",
        "//prebuilts/mysdk",
    ],
    jars: ["java/myjavalib.jar"],
}

java_import {
    name: "myjavalib",
    prefer: false,
    visibility: [
        "//other/foo",
        "//package",
        "//prebuilts/mysdk",
    ],
    jars: ["java/myjavalib.jar"],
}

java_import {
    name: "mysdk_mypublicjavalib@current",
    sdk_member_name: "mypublicjavalib",
    visibility: ["//visibility:public"],
    jars: ["java/mypublicjavalib.jar"],
}

java_import {
    name: "mypublicjavalib",
    prefer: false,
    visibility: ["//visibility:public"],
    jars: ["java/mypublicjavalib.jar"],
}

java_import {
    name: "mysdk_mydefaultedjavalib@current",
    sdk_member_name: "mydefaultedjavalib",
    visibility: [
        "//other/bar",
        "//package",
        "//prebuilts/mysdk",
    ],
    jars: ["java/mydefaultedjavalib.jar"],
}

java_import {
    name: "mydefaultedjavalib",
    prefer: false,
    visibility: [
        "//other/bar",
        "//package",
        "//prebuilts/mysdk",
    ],
    jars: ["java/mydefaultedjavalib.jar"],
}

java_import {
    name: "mysdk_myprivatejavalib@current",
    sdk_member_name: "myprivatejavalib",
    visibility: [
        "//package",
        "//prebuilts/mysdk",
    ],
    jars: ["java/myprivatejavalib.jar"],
}

java_import {
    name: "myprivatejavalib",
    prefer: false,
    visibility: [
        "//package",
        "//prebuilts/mysdk",
    ],
    jars: ["java/myprivatejavalib.jar"],
}

sdk_snapshot {
    name: "mysdk@current",
    visibility: [
        "//other/foo",
        "//package:__subpackages__",
    ],
    java_header_libs: [
        "mysdk_myjavalib@current",
        "mysdk_mypublicjavalib@current",
        "mysdk_mydefaultedjavalib@current",
        "mysdk_myprivatejavalib@current",
    ],
}
`))
}

func TestPrebuiltVisibilityProperty_IsValidated(t *testing.T) {
	testSdkError(t, `prebuilt_visibility: cannot mix "//visibility:private" with any other visibility rules`, `
		sdk {
			name: "mysdk",
			prebuilt_visibility: [
				"//foo",
				"//visibility:private",
			],
		}
`)
}

func TestPrebuiltVisibilityProperty_AddPrivate(t *testing.T) {
	testSdkError(t, `prebuilt_visibility: "//visibility:private" does not widen the visibility`, `
		sdk {
			name: "mysdk",
			prebuilt_visibility: [
				"//visibility:private",
			],
			java_header_libs: [
				"myjavalib",
			],
		}

		java_library {
			name: "myjavalib",
			// Uses package default visibility
			srcs: ["Test.java"],
			system_modules: "none",
			sdk_version: "none",
		}
`)
}

func TestSDkInstall(t *testing.T) {
	sdk := `
		sdk {
			name: "mysdk",
		}
	`
	result := testSdkWithFs(t, ``,
		map[string][]byte{
			"Android.bp": []byte(sdk),
		})

	result.CheckSnapshot("mysdk", "",
		checkAllOtherCopyRules(`.intermediates/mysdk/common_os/mysdk-current.zip -> mysdk-current.zip`),
	)
}

type EmbeddedPropertiesStruct struct {
	S_Embedded_Common    string `android:"arch_variant"`
	S_Embedded_Different string `android:"arch_variant"`
}

type testPropertiesStruct struct {
	name        string
	private     string
	Public_Kept string `sdk:"keep"`
	S_Common    string
	S_Different string `android:"arch_variant"`
	A_Common    []string
	A_Different []string `android:"arch_variant"`
	F_Common    *bool
	F_Different *bool `android:"arch_variant"`
	EmbeddedPropertiesStruct
}

func (p *testPropertiesStruct) optimizableProperties() interface{} {
	return p
}

func (p *testPropertiesStruct) String() string {
	return p.name
}

var _ propertiesContainer = (*testPropertiesStruct)(nil)

func TestCommonValueOptimization(t *testing.T) {
	common := &testPropertiesStruct{name: "common"}
	structs := []propertiesContainer{
		&testPropertiesStruct{
			name:        "struct-0",
			private:     "common",
			Public_Kept: "common",
			S_Common:    "common",
			S_Different: "upper",
			A_Common:    []string{"first", "second"},
			A_Different: []string{"alpha", "beta"},
			F_Common:    proptools.BoolPtr(false),
			F_Different: proptools.BoolPtr(false),
			EmbeddedPropertiesStruct: EmbeddedPropertiesStruct{
				S_Embedded_Common:    "embedded_common",
				S_Embedded_Different: "embedded_upper",
			},
		},
		&testPropertiesStruct{
			name:        "struct-1",
			private:     "common",
			Public_Kept: "common",
			S_Common:    "common",
			S_Different: "lower",
			A_Common:    []string{"first", "second"},
			A_Different: []string{"alpha", "delta"},
			F_Common:    proptools.BoolPtr(false),
			F_Different: proptools.BoolPtr(true),
			EmbeddedPropertiesStruct: EmbeddedPropertiesStruct{
				S_Embedded_Common:    "embedded_common",
				S_Embedded_Different: "embedded_lower",
			},
		},
	}

	extractor := newCommonValueExtractor(common)

	h := TestHelper{t}

	err := extractor.extractCommonProperties(common, structs)
	h.AssertDeepEquals("unexpected error", nil, err)

	h.AssertDeepEquals("common properties not correct",
		&testPropertiesStruct{
			name:        "common",
			private:     "",
			Public_Kept: "",
			S_Common:    "common",
			S_Different: "",
			A_Common:    []string{"first", "second"},
			A_Different: []string(nil),
			F_Common:    proptools.BoolPtr(false),
			F_Different: nil,
			EmbeddedPropertiesStruct: EmbeddedPropertiesStruct{
				S_Embedded_Common:    "embedded_common",
				S_Embedded_Different: "",
			},
		},
		common)

	h.AssertDeepEquals("updated properties[0] not correct",
		&testPropertiesStruct{
			name:        "struct-0",
			private:     "common",
			Public_Kept: "common",
			S_Common:    "",
			S_Different: "upper",
			A_Common:    nil,
			A_Different: []string{"alpha", "beta"},
			F_Common:    nil,
			F_Different: proptools.BoolPtr(false),
			EmbeddedPropertiesStruct: EmbeddedPropertiesStruct{
				S_Embedded_Common:    "",
				S_Embedded_Different: "embedded_upper",
			},
		},
		structs[0])

	h.AssertDeepEquals("updated properties[1] not correct",
		&testPropertiesStruct{
			name:        "struct-1",
			private:     "common",
			Public_Kept: "common",
			S_Common:    "",
			S_Different: "lower",
			A_Common:    nil,
			A_Different: []string{"alpha", "delta"},
			F_Common:    nil,
			F_Different: proptools.BoolPtr(true),
			EmbeddedPropertiesStruct: EmbeddedPropertiesStruct{
				S_Embedded_Common:    "",
				S_Embedded_Different: "embedded_lower",
			},
		},
		structs[1])
}

func TestCommonValueOptimization_InvalidArchSpecificVariants(t *testing.T) {
	common := &testPropertiesStruct{name: "common"}
	structs := []propertiesContainer{
		&testPropertiesStruct{
			name:     "struct-0",
			S_Common: "should-be-but-is-not-common0",
		},
		&testPropertiesStruct{
			name:     "struct-1",
			S_Common: "should-be-but-is-not-common1",
		},
	}

	extractor := newCommonValueExtractor(common)

	h := TestHelper{t}

	err := extractor.extractCommonProperties(common, structs)
	h.AssertErrorMessageEquals("unexpected error", `field "S_Common" is not tagged as "arch_variant" but has arch specific properties:
    "struct-0" has value "should-be-but-is-not-common0"
    "struct-1" has value "should-be-but-is-not-common1"`, err)
}
