package org.robolectric.internal;

import java.io.File;
import java.net.URL;
import org.robolectric.annotation.Config;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.res.FileFsFile;
import org.robolectric.res.FsFile;
import org.robolectric.util.Logger;
import org.robolectric.util.ReflectionHelpers;

public class GradleManifestFactory implements ManifestFactory {
  @Override
  public ManifestIdentifier identify(Config config) {
    if (config.constants() == Void.class) {
      Logger.error("Field 'constants' not specified in @Config annotation");
      Logger.error("This is required when using Robolectric with Gradle!");
      throw new RuntimeException("No 'constants' field in @Config annotation!");
    }

    final String buildOutputDir = getBuildOutputDir(config);
    final String type = getType(config);
    final String flavor = getFlavor(config);
    final String abiSplit = getAbiSplit(config);
    final String packageName = config.packageName().isEmpty()
        ? config.constants().getPackage().getName()
        : config.packageName();

    final FileFsFile res;
    final FileFsFile assets;
    final FileFsFile manifest;

    if (FileFsFile.from(buildOutputDir, "data-binding-layout-out").exists()) {
      // Android gradle plugin 1.5.0+ puts the merged layouts in data-binding-layout-out.
      // https://github.com/robolectric/robolectric/issues/2143
      res = FileFsFile.from(buildOutputDir, "data-binding-layout-out", flavor, type);
    } else if (FileFsFile.from(buildOutputDir, "res", "merged").exists()) {
      // res/merged added in Android Gradle plugin 1.3-beta1
      res = FileFsFile.from(buildOutputDir, "res", "merged", flavor, type);
    } else if (FileFsFile.from(buildOutputDir, "res").exists()) {
      res = FileFsFile.from(buildOutputDir, "res", flavor, type);
    } else {
      res = FileFsFile.from(buildOutputDir, "bundles", flavor, type, "res");
    }

    if (!Config.DEFAULT_ASSET_FOLDER.equals(config.assetDir())
            && FileFsFile.from(buildOutputDir, config.assetDir()).exists()) {
      assets = FileFsFile.from(buildOutputDir, config.assetDir());
    } else if (FileFsFile.from(buildOutputDir, "assets").exists()) {
      assets = FileFsFile.from(buildOutputDir, "assets", flavor, type);
    } else {
      assets = FileFsFile.from(buildOutputDir, "bundles", flavor, type, "assets");
    }

    String manifestName = config.manifest();
    URL manifestUrl = getClass().getClassLoader().getResource(manifestName);
    if (manifestUrl != null && manifestUrl.getProtocol().equals("file")) {
      manifest = FileFsFile.from(manifestUrl.getPath());
    } else if (FileFsFile.from(buildOutputDir, "manifests", "full").exists()) {
      manifest = FileFsFile.from(buildOutputDir, "manifests", "full", flavor, abiSplit, type, manifestName);
    } else if (FileFsFile.from(buildOutputDir, "manifests", "aapt").exists()) {
      // Android gradle plugin 2.2.0+ can put library manifest files inside of "aapt" instead of "full"
      manifest = FileFsFile.from(buildOutputDir, "manifests", "aapt", flavor, abiSplit, type, manifestName);
    } else {
      manifest = FileFsFile.from(buildOutputDir, "bundles", flavor, abiSplit, type, manifestName);
    }

    return new ManifestIdentifier(manifest, res, assets, packageName, null);
  }

  @Override
  public AndroidManifest create(ManifestIdentifier manifestIdentifier) {
    FsFile manifestFile = manifestIdentifier.getManifestFile();
    FsFile resDir = manifestIdentifier.getResDir();
    FsFile assetDir = manifestIdentifier.getAssetDir();
    final String packageName = manifestIdentifier.getPackageName();

    Logger.debug("Robolectric assets directory: " + assetDir.getPath());
    Logger.debug("   Robolectric res directory: " + resDir.getPath());
    Logger.debug("   Robolectric manifest path: " + manifestFile.getPath());
    Logger.debug("    Robolectric package name: " + packageName);
    return new AndroidManifest(manifestFile, resDir, assetDir, packageName);
  }

  private static String getBuildOutputDir(Config config) {
    return config.buildDir() + File.separator + "intermediates";
  }

  private static String getType(Config config) {
    try {
      return ReflectionHelpers.getStaticField(config.constants(), "BUILD_TYPE");
    } catch (Throwable e) {
      return null;
    }
  }

  private static String getFlavor(Config config) {
    try {
      return ReflectionHelpers.getStaticField(config.constants(), "FLAVOR");
    } catch (Throwable e) {
      return null;
    }
  }

  private static String getAbiSplit(Config config) {
    try {
      return config.abiSplit();
    } catch (Throwable e) {
      return null;
    }
  }
}

@@ -70,16 +70,99 @@
 et.URL;%0A
+import java.nio.file.Files;%0Aimport java.nio.file.Path;%0Aimport java.nio.file.Paths;%0A
 import o
@@ -4159,67 +4159,866 @@
     
-return config.buildDir() + File.separator + %22intermediates%22
+Path buildDir = Paths.get(config.buildDir(), %22intermediates%22);%0A    if (!Files.exists(buildDir)) %7B%0A      // By default build dir is a relative path. However, the build dir lookup may fail if the%0A      // working directory of the test configuration in Android Studio is not set to the module%0A      // root directory (e.g it is set to the entire project root directory). Attempt to locate it%0A      // relative to the constants class, which is generated in the build output directory.%0A      String moduleRoot = config.constants().getResource(%22%22).toString().replace(%22file:%22, %22%22);%0A      int idx = moduleRoot.lastIndexOf(File.separator + %22intermediates%22);%0A      if (idx %3E 0) %7B%0A        buildDir = Paths.get(moduleRoot.substring(0, idx), %22intermediates%22);%0A      %7D else %7B%0A        Logger.error(%22Failed to locate build dir%22);%0A      %7D%0A    %7D%0A    return buildDir.toString()
 ;%0A  

Make build output dir support different working directories