/*
 * Copyright (C) 2018 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 com.android.tools.build.bundletool.splitters;

import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_M_API_VERSION;
import static com.android.tools.build.bundletool.model.utils.Versions.ANDROID_Q_API_VERSION;
import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.androidManifest;
import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withMaxSdkVersion;
import static com.android.tools.build.bundletool.testing.ManifestProtoUtils.withMinSdkVersion;
import static com.android.tools.build.bundletool.testing.TargetingUtils.lPlusVariantTargeting;
import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeDirectoryTargeting;
import static com.android.tools.build.bundletool.testing.TargetingUtils.nativeLibraries;
import static com.android.tools.build.bundletool.testing.TargetingUtils.targetedNativeDirectory;
import static com.android.tools.build.bundletool.testing.TargetingUtils.variantMinSdkTargeting;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.extensions.proto.ProtoTruth.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

import com.android.aapt.Resources.XmlNode;
import com.android.bundle.Files.NativeLibraries;
import com.android.bundle.Targeting.VariantTargeting;
import com.android.tools.build.bundletool.model.BundleModule;
import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException;
import com.android.tools.build.bundletool.testing.BundleModuleBuilder;
import com.google.common.collect.ImmutableCollection;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;

@RunWith(JUnit4.class)
public class VariantGeneratorTest {
  @Test
  public void targetsPreLOnlyInManifest_throws() throws Exception {
    int preL = 20;
    BundleModule bundleModule =
        new BundleModuleBuilder("testModule")
            .setManifest(androidManifest("com.test.app", withMaxSdkVersion(preL)))
            .build();

    CommandExecutionException exception =
        assertThrows(
            CommandExecutionException.class,
            () -> new VariantGenerator(bundleModule).generateVariants());

    assertThat(exception)
        .hasMessageThat()
        .contains("does not target devices on Android L or above");
  }

  @Test
  public void variantsWithNativeLibsDexFiles_allOptimizationsDisabled() throws Exception {
    VariantGenerator variantGenerator = new VariantGenerator(createSingleLibraryDexModule());

    ImmutableCollection<VariantTargeting> splits = variantGenerator.generateVariants();
    assertThat(splits).containsExactly(lPlusVariantTargeting());
  }

  @Test
  public void variantsWithNativeLibsDexFiles_withNativeLibsOptimization() throws Exception {

    VariantGenerator variantGenerator =
        new VariantGenerator(
            createSingleLibraryDexModule(),
            ApkGenerationConfiguration.builder()
                .setEnableNativeLibraryCompressionSplitter(true)
                .build());

    ImmutableCollection<VariantTargeting> splits = variantGenerator.generateVariants();
    assertThat(splits)
        .containsExactly(lPlusVariantTargeting(), variantMinSdkTargeting(ANDROID_M_API_VERSION));
  }

  @Test
  public void variantsWithNativeLibsDexFiles_withDexOptimization() throws Exception {
    VariantGenerator variantGenerator =
        new VariantGenerator(
            createSingleLibraryDexModule(),
            ApkGenerationConfiguration.builder().setEnableDexCompressionSplitter(true).build());

    ImmutableCollection<VariantTargeting> splits = variantGenerator.generateVariants();
    assertThat(splits)
        .containsExactly(lPlusVariantTargeting(), variantMinSdkTargeting(ANDROID_Q_API_VERSION));
  }

  @Test
  public void variantsWithNativeLibsDexFiles_withNativeLibsAndDexOptimization() throws Exception {

    VariantGenerator variantGenerator =
        new VariantGenerator(
            createSingleLibraryDexModule(),
            ApkGenerationConfiguration.builder()
                .setEnableDexCompressionSplitter(true)
                .setEnableNativeLibraryCompressionSplitter(true)
                .build());

    ImmutableCollection<VariantTargeting> splits = variantGenerator.generateVariants();
    assertThat(splits)
        .containsExactly(
            lPlusVariantTargeting(),
            variantMinSdkTargeting(ANDROID_M_API_VERSION),
            variantMinSdkTargeting(ANDROID_Q_API_VERSION));
  }

  @Test
  public void variantsWithAllOptimizations_minSdkAffectsLPlusVariant() throws Exception {

    VariantGenerator variantGenerator =
        new VariantGenerator(
            createSingleLibraryDexModuleMinSdk(22),
            ApkGenerationConfiguration.builder()
                .setEnableDexCompressionSplitter(true)
                .setEnableNativeLibraryCompressionSplitter(true)
                .build());

    ImmutableCollection<VariantTargeting> splits = variantGenerator.generateVariants();
    assertThat(splits)
        .containsExactly(
            variantMinSdkTargeting(22),
            variantMinSdkTargeting(ANDROID_M_API_VERSION),
            variantMinSdkTargeting(ANDROID_Q_API_VERSION));
  }

  @Test
  public void variantsWithAllOptimizations_minSdkRemovesLPlusVariant() throws Exception {

    VariantGenerator variantGenerator =
        new VariantGenerator(
            createSingleLibraryDexModuleMinSdk(ANDROID_M_API_VERSION),
            ApkGenerationConfiguration.builder()
                .setEnableDexCompressionSplitter(true)
                .setEnableNativeLibraryCompressionSplitter(true)
                .build());

    ImmutableCollection<VariantTargeting> splits = variantGenerator.generateVariants();
    assertThat(splits)
        .containsExactly(
            variantMinSdkTargeting(ANDROID_M_API_VERSION),
            variantMinSdkTargeting(ANDROID_Q_API_VERSION));
  }

  @Test
  public void variantsWithAllOptimizations_maxSdkRemovesDexVariant() throws Exception {
    VariantGenerator variantGenerator =
        new VariantGenerator(
            createSingleLibraryDexModuleMaxSdk(ANDROID_Q_API_VERSION - 1),
            ApkGenerationConfiguration.builder()
                .setEnableDexCompressionSplitter(true)
                .setEnableNativeLibraryCompressionSplitter(true)
                .build());

    ImmutableCollection<VariantTargeting> splits = variantGenerator.generateVariants();
    assertThat(splits)
        .containsExactly(lPlusVariantTargeting(), variantMinSdkTargeting(ANDROID_M_API_VERSION));
  }

  @Test
  public void variantsWithAllOptimizations_sdkRangeCropsVariants() throws Exception {
    VariantGenerator variantGenerator =
        new VariantGenerator(
            createSingleLibraryDexModuleSdkRange(
                ANDROID_M_API_VERSION - 1, ANDROID_M_API_VERSION + 1),
            ApkGenerationConfiguration.builder()
                .setEnableDexCompressionSplitter(true)
                .setEnableNativeLibraryCompressionSplitter(true)
                .build());

    ImmutableCollection<VariantTargeting> splits = variantGenerator.generateVariants();
    assertThat(splits)
        .containsExactly(
            variantMinSdkTargeting(ANDROID_M_API_VERSION - 1),
            variantMinSdkTargeting(ANDROID_M_API_VERSION));
  }

  @Test
  public void variantsWithNativeLibsDexFiles_withInstantModule_allOptimizationsDisabled()
      throws Exception {

    VariantGenerator variantGenerator =
        new VariantGenerator(
            createSingleLibraryDexModule(),
            ApkGenerationConfiguration.builder().setForInstantAppVariants(true).build());

    ImmutableCollection<VariantTargeting> splits = variantGenerator.generateVariants();
    assertThat(splits).containsExactly(lPlusVariantTargeting());
  }

  @Test
  public void variantsWithNativeLibsDexFiles_withInstantModule_withNativeLibsAndDexOptimization()
      throws Exception {

    VariantGenerator variantGenerator =
        new VariantGenerator(
            createSingleLibraryDexModule(),
            ApkGenerationConfiguration.builder()
                .setForInstantAppVariants(true)
                .setEnableDexCompressionSplitter(true)
                .setEnableNativeLibraryCompressionSplitter(true)
                .build());

    ImmutableCollection<VariantTargeting> splits = variantGenerator.generateVariants();
    assertThat(splits).containsExactly(lPlusVariantTargeting());
  }

  /** Creates a minimal module with one native library and dex files. */
  private static BundleModule createSingleLibraryDexModule() throws Exception {
    return createSingleLibraryDexModule(androidManifest("com.test.app"));
  }

  private static BundleModule createSingleLibraryDexModule(XmlNode androidManifest)
      throws Exception {
    NativeLibraries nativeConfig =
        nativeLibraries(targetedNativeDirectory("lib/x86", nativeDirectoryTargeting("x86")));
    return new BundleModuleBuilder("testModule")
        .setManifest(androidManifest)
        .setNativeConfig(nativeConfig)
        .addFile("lib/x86/liba.so")
        .addFile("dex/classes.dex")
        .build();
  }

  private static BundleModule createSingleLibraryDexModuleMinSdk(int minSdkVersion)
      throws Exception {
    return createSingleLibraryDexModule(
        androidManifest("com.test.app", withMinSdkVersion(minSdkVersion)));
  }

  private static BundleModule createSingleLibraryDexModuleMaxSdk(int maxSdkVersion)
      throws Exception {
    return createSingleLibraryDexModule(
        androidManifest("com.test.app", withMaxSdkVersion(maxSdkVersion)));
  }

  private static BundleModule createSingleLibraryDexModuleSdkRange(
      int minSdkVersion, int maxSdkVersion) throws Exception {
    return createSingleLibraryDexModule(
        androidManifest(
            "com.test.app", withMinSdkVersion(minSdkVersion), withMaxSdkVersion(maxSdkVersion)));
  }
}