package org.robolectric.internal;

import org.robolectric.annotation.Config;
import org.robolectric.manifest.AndroidManifest;
import org.robolectric.res.Fs;
import org.robolectric.res.FsFile;
import org.robolectric.util.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.util.*;

public class MavenManifestFactory implements ManifestFactory {

  @Override
  public ManifestIdentifier identify(Config config) {
    if (config.manifest().equals(Config.NONE)) {
      return new ManifestIdentifier(null, null, null, null, null);
    }

    FsFile manifestFile = getBaseDir().join(config.manifest());
    FsFile baseDir = manifestFile.getParent();
    FsFile resDir = baseDir.join(config.resourceDir());
    FsFile assetDir = baseDir.join(config.assetDir());

    List<FsFile> libraryDirs = null;
    if (config.libraries().length > 0) {
      libraryDirs = new ArrayList<>();
      for (String libraryDirName : config.libraries()) {
        libraryDirs.add(baseDir.join(libraryDirName));
      }
    }

    return new ManifestIdentifier(manifestFile, resDir, assetDir, config.packageName(), libraryDirs);
  }

  @Override
  public AndroidManifest create(ManifestIdentifier manifestIdentifier) {
    AndroidManifest appManifest;
    FsFile manifestFile = manifestIdentifier.getManifestFile();
    if (manifestFile == null) {
      appManifest = createDummyManifest(manifestIdentifier.getPackageName());
    } else if (!manifestFile.exists()) {
      System.out.println("WARNING: No manifest file found at " + manifestFile.getPath() + ".");
      System.out.println("Falling back to the Android OS resources only.");
      System.out.println("To remove this warning, annotate your test class with @Config(manifest=Config.NONE).");
      appManifest = createDummyManifest(manifestIdentifier.getPackageName());
    } else {
      FsFile resDir = manifestIdentifier.getResDir();
      FsFile assetDir = manifestIdentifier.getAssetDir();
      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);

      appManifest = new AndroidManifest(manifestFile, resDir, assetDir, packageName);
    }

    List<FsFile> libraryDirs = manifestIdentifier.getLibraryDirs();
    appManifest.setLibraryManifests(createLibraryManifests(appManifest, libraryDirs));
    return appManifest;
  }

  private AndroidManifest createDummyManifest(String packageName) {
    if (packageName == null || packageName.equals("")) {
      packageName = "org.robolectric.default";
    }

    return new AndroidManifest(null, null, null, packageName) {
      @Override
      public int getTargetSdkVersion() {
        return SdkConfig.FALLBACK_SDK_VERSION;
      }
    };
  }

  FsFile getBaseDir() {
    return Fs.currentDirectory();
  }

  /**
   * Search through an AndroidManifest's library directories to load library AppManifest files.
   * For testing, allow a parameter override of the library directories.
   * @param androidManifest The main AppManifest which may have library resources.
   * @param libraryDirectories If not null, override the libraries in androidManifest.
   * @return A list of AndroidManifest objects, one for each library found.
   */
  private static List<AndroidManifest> createLibraryManifests(
      AndroidManifest androidManifest,
      List<FsFile> libraryDirectories) {
    List<AndroidManifest> libraryManifests = new ArrayList<>();
    if (androidManifest != null) {
      // If there is no library override, look through subdirectories.
      if (libraryDirectories == null) {
        libraryDirectories = findLibraries(androidManifest);
      }

      for (FsFile libraryBaseDir : libraryDirectories) {
        AndroidManifest libraryManifest = createLibraryAndroidManifest(libraryBaseDir);
        libraryManifest.setLibraryManifests(
            createLibraryManifests(libraryManifest, null));
        libraryManifests.add(libraryManifest);
      }
    }
    return libraryManifests;
  }

  private static AndroidManifest createLibraryAndroidManifest(FsFile libraryBaseDir) {
    return new AndroidManifest(libraryBaseDir.join(Config.DEFAULT_MANIFEST_NAME), libraryBaseDir.join(Config.DEFAULT_RES_FOLDER), libraryBaseDir.join(Config.DEFAULT_ASSET_FOLDER));
  }

  private static Properties getProperties(FsFile propertiesFile) {
    Properties properties = new Properties();

    // return an empty Properties object if the propertiesFile does not exist
    if (!propertiesFile.exists()) return properties;

    InputStream stream;
    try {
      stream = propertiesFile.getInputStream();
    } catch (IOException e) {
      throw new RuntimeException(e);
    }

    try {
      try {
        properties.load(stream);
      } finally {
        stream.close();
      }
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    return properties;
  }

  /**
   * Find valid library AndroidManifest files referenced from an already loaded AndroidManifest's
   * "project.properties" file.
   * @param androidManifest
   */
  private static List<FsFile> findLibraries(AndroidManifest androidManifest) {
    List<FsFile> libraryBaseDirs = new ArrayList<>();

    if (androidManifest.getResDirectory() != null) {
      FsFile baseDir = androidManifest.getResDirectory().getParent();
      final Properties properties = getProperties(baseDir.join("project.properties"));
      Properties overrideProperties = getProperties(baseDir.join("test-project.properties"));
      properties.putAll(overrideProperties);

      int libRef = 1;
      String lib;
      while ((lib = properties.getProperty("android.library.reference." + libRef)) != null) {
        FsFile libraryBaseDir = baseDir.join(lib);
        if (libraryBaseDir.isDirectory()) {
          // Ignore directories without any files
          FsFile[] libraryBaseDirFiles = libraryBaseDir.listFiles();
          if (libraryBaseDirFiles != null && libraryBaseDirFiles.length > 0) {
            libraryBaseDirs.add(libraryBaseDir);
          }
        }

        libRef++;
      }
    }
    return libraryBaseDirs;
  }
}
