/*
 * Copyright 2000-2012 JetBrains s.r.o.
 *
 * 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 jetbrains.buildServer.agent.android;

import jetbrains.buildServer.agent.AgentRunningBuild;
import jetbrains.buildServer.agent.BuildFinishedStatus;
import jetbrains.buildServer.agent.BuildProgressLogger;
import jetbrains.buildServer.agent.BuildRunnerContext;
import jetbrains.buildServer.agent.android.tools.AaptTool;
import jetbrains.buildServer.agent.android.tools.ApkUtil;
import jetbrains.buildServer.util.FileUtil;
import org.jetbrains.annotations.NotNull;

import java.io.File;

import static jetbrains.buildServer.runner.android.AndroidRunnerConstants.*;

public class AndroidBuildProcess extends FutureBasedBuildProcess {
  @NotNull private static final String LINE_TERMINATORS_REGEX = "\\r|\\n|\\r\\n|\\u0085|\\u2028|\\u2029";

  @NotNull private final AgentRunningBuild myBuild;
  @NotNull private final BuildRunnerContext myContext;
  @NotNull private final AndroidEnvironment myEnvironment;

  public AndroidBuildProcess(@NotNull final AgentRunningBuild build, @NotNull final BuildRunnerContext context) {
    myBuild = build;
    myContext = context;
    myEnvironment = new AndroidEnvironment(context, true, true);
  }

  @NotNull
  public BuildFinishedStatus call() throws Exception {
    try {
      final BuildProgressLogger logger = myBuild.getBuildLogger();

      logger.targetStarted("Android");

      final File buildTempDirectory = myBuild.getBuildTempDirectory();
      final String manifestFile = getParameter(PARAM_MANIFEST_FILE, true);
      final String[] sourcesDirs = getParameterAndSplitToPaths(PARAM_SOURCES_DIRS);
      final String[] resourcesDirs = getParameterAndSplitToPaths(PARAM_RESOURCES_DIRS);
      final String assetsDir = getParameter(PARAM_ASSETS_DIR, true);
      final String[] nativeLibsDirs = getParameterAndSplitToPaths(PARAM_LIBS_DIRS);
      final String outputFile = getParameter(PARAM_OUTPUT_FILE, true);
      final boolean isReleaseBuild = Boolean.parseBoolean(getParameter(PARAM_RELEASE_BUILD, false));
      final boolean useAdvancedParams = Boolean.parseBoolean(getParameter(PARAM_USE_ADVANCED_PARAMS, false));

      final AaptTool aapt = myEnvironment.getAaptTool();

      logger.targetStarted("Packaging resources...");
      final File resourcesApk = aapt.packageResources(buildTempDirectory, manifestFile, resourcesDirs, assetsDir,
                                                      getAdditionalOptions(useAdvancedParams, ADV_PARAM_AAPT_PACKAGING_ADDITIONAL_OPTIONS));
      logger.targetFinished("Packaging resources...");

      logger.targetStarted("Generating R.java...");
      final File rFileRootDir = aapt.generateRFile(buildTempDirectory, manifestFile, resourcesDirs, assetsDir,
                                                   getAdditionalOptions(useAdvancedParams, ADV_PARAM_AAPT_R_GENERATING_ADDITIONAL_OPTIONS));
      logger.targetFinished("Generating R.java...");

      logger.targetStarted("Compiling sources...");
      final File outputRootDir = myEnvironment.getJavacTool().compileSources(buildTempDirectory, rFileRootDir, sourcesDirs, nativeLibsDirs,
                                                                             getAdditionalOptions(useAdvancedParams, ADV_PARAM_JAVAC_ADDITIONAL_OPTIONS));
      logger.targetFinished("Compiling sources...");

      logger.targetStarted("Packaging classes to .dex file...");
      final File dexFile = myEnvironment.getDxTool().packClasses(buildTempDirectory, outputRootDir, nativeLibsDirs,
                                                                 getAdditionalOptions(useAdvancedParams, ADV_PARAM_DX_ADDITIONAL_OPTIONS));
      logger.targetFinished("Packaging classes to .dex file...");

      logger.targetStarted("Adding classes.dex to result package...");
      final boolean isDebug = AndroidRunnersUtil.isDebug(myContext);
      if (isDebug) {
        logger.message("Source package: " + resourcesApk.getAbsolutePath());
        logger.message("Dex file: " + dexFile.getAbsolutePath());
      }
      final File unsignedApk = ApkUtil.addFileToApk(buildTempDirectory, dexFile, resourcesApk, "classes.dex");
      if (isDebug) {
        logger.message("Result package: " + unsignedApk.getAbsolutePath());
      }
      logger.targetFinished("Adding classes.dex to result package...");

      File unalignedApk = unsignedApk;
      if (AndroidRunnersUtil.mustSignPackage(myContext)) {
        logger.targetStarted("Signing the package...");
        final File keyStore;
        final String storePass, keyAlias, keyPass;
        if (isReleaseBuild) {
          keyStore = new File(getParameter(PARAM_KEY_STORE, true));
          storePass = getParameter(PARAM_STORE_PASS, false);
          keyAlias = getParameter(PARAM_KEY_ALIAS, false);
          keyPass = getParameter(PARAM_KEY_PASS, false);
        }
        else {
          keyStore = myEnvironment.getKeytoolTool().createDebugKeystore(buildTempDirectory);
          storePass = DEBUG_STORE_PASS;
          keyAlias = DEBUG_KEY_ALIAS;
          keyPass = DEBUG_KEY_PASS;
        }
        unalignedApk = myEnvironment.getJarsignerTool().signPackage(buildTempDirectory, unsignedApk, keyStore, storePass, keyAlias, keyPass,
                                                                    getAdditionalOptions(useAdvancedParams, ADV_PARAM_JARSIGNER_ADDITIONAL_OPTIONS));
        logger.targetFinished("Signing the package...");
      }

      File finalApk = unalignedApk;
      if (AndroidRunnersUtil.mustAlignPackage(myContext)) {
        logger.targetStarted("Aligning the package...");
        finalApk = myEnvironment.getZipalignTool().alignPackage(buildTempDirectory, unalignedApk,
                                                                getAdditionalOptions(useAdvancedParams, ADV_PARAM_ZIPALIGN_ADDITIONAL_OPTIONS));
        logger.targetFinished("Aligning the package...");
      }

      FileUtil.copy(finalApk, new File(outputFile));

      logger.message("Android package has been built: " + outputFile);
      logger.targetFinished("Android");

      return BuildFinishedStatus.FINISHED_SUCCESS;
    }
    catch (final NonZeroExitCodeException e) {
      return BuildFinishedStatus.FINISHED_FAILED;
    }
  }

  @NotNull
  private String getAdditionalOptions(final boolean useAdvancedParams, @NotNull final String parameterName) {
    if (!useAdvancedParams) return "";
    final String options = getParameter(parameterName, false);
    return options == null ? "" : options;
  }

  @NotNull
  private String[] getParameterAndSplitToPaths(@NotNull final String parameterName) {
    final String parameter = getParameter(parameterName, false);
    return preparePaths(parameter == null ? new String[0] : parameter.split(LINE_TERMINATORS_REGEX));
  }

  private String getParameter(@NotNull final String parameterName, final boolean isPath) {
    final String value = myContext.getRunnerParameters().get(parameterName);
    if (value == null || value.trim().length() == 0) return null;
    String result = value.trim();
    if (isPath) {
      result = preparePath(result);
    }
    return result;
  }

  @NotNull
  private String[] preparePaths(@NotNull final String[] values) {
    for (int i = 0; i < values.length; i++) {
      values[i] = preparePath(values[i].trim());
    }
    return values;
  }

  @NotNull
  private String preparePath(@NotNull final String value) {
    return new File(value).isAbsolute() ? value : new File(myContext.getBuild().getCheckoutDirectory(), value).getAbsolutePath();
  }
}
