/*
 * Copyright 2010 Google Inc.
 *
 * 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.google.gwt.dev;

import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.dev.CompileTaskRunner.CompileTask;
import com.google.gwt.dev.cfg.ModuleDef;
import com.google.gwt.dev.cfg.ModuleDefLoader;
import com.google.gwt.dev.jjs.JavaToJavaScriptCompiler;
import com.google.gwt.dev.jjs.PermutationResult;
import com.google.gwt.dev.util.PerfCounter;
import com.google.gwt.dev.util.arg.ArgHandlerPerm;
import com.google.gwt.dev.util.arg.OptionPerm;

import java.io.File;
import java.util.List;

/**
 * Performs the second phase of compilation, converting one of the Precompile's
 * serialized AST files into JavaScript outputs.
 */
public class CompileOnePerm {

  /**
   * Options for CompilePerm.
   */
  public interface CompileOnePermOptions extends PrecompileTaskOptions, OptionPerm {
  }

  static class ArgProcessor extends PrecompileTaskArgProcessor {
    public ArgProcessor(CompileOnePermOptions options) {
      super(options);
      registerHandler(new ArgHandlerPerm(options));
    }

    @Override
    protected String getName() {
      return CompileOnePerm.class.getName();
    }
  }

  /**
   * Concrete class to implement compiler perm options.
   */
  static class CompileOnePermOptionsImpl extends PrecompileTaskOptionsImpl
      implements CompileOnePermOptions {

    private int permToCompile = -1;

    public CompileOnePermOptionsImpl() {
    }

    public CompileOnePermOptionsImpl(CompileOnePermOptions other) {
      copyFrom(other);
    }

    public void copyFrom(CompileOnePermOptions other) {
      super.copyFrom(other);
      setPermToCompile(other.getPermToCompile());
    }

    @Override
    public int getPermToCompile() {
      return permToCompile;
    }

    @Override
    public void setPermToCompile(int permToCompile) {
      this.permToCompile = permToCompile;
    }
  }

  public static void main(String[] args) {
    int exitCode = -1;
    /*
     * NOTE: main always exits with a call to System.exit to terminate any
     * non-daemon threads that were started in Generators. Typically, this is to
     * shutdown AWT related threads, since the contract for their termination is
     * still implementation-dependent.
     */
    final CompileOnePermOptions options = new CompileOnePermOptionsImpl();
    if (new ArgProcessor(options).processArgs(args)) {
      CompileTask task = new CompileTask() {
        @Override
        public boolean run(TreeLogger logger) throws UnableToCompleteException {
          return new CompileOnePerm(options).run(logger);
        }
      };
      if (CompileTaskRunner.runWithAppropriateLogger(options, task)) {
        // Exit w/ success code.
        exitCode = 0;
      }
    }
    PerfCounter.print();
    System.exit(exitCode);
  }

  /**
   * Return the filename corresponding to the given permutation number,
   * one-based.
   */
  static File makePermFilename(File compilerWorkDir, int permNumber) {
    return new File(compilerWorkDir, "permutation-" + permNumber + ".js");
  }

  /**
   * Run Compile where precompilation occurred previously on sharded steps of
   * Precompile. There will be a different serialized AST file named
   * permutation-*.ser, one per permutation.
   *
   * @param logger tree logger
   * @param moduleName Name of the GWT module with the entry point
   * @param permId permutations to compile
   * @param compilerWorkDir directory where work files are stored
   *
   * @return <code>true</code> if compilation succeeds
   */
  private static boolean compileSpecificPermutation(TreeLogger logger,
      String moduleName, PrecompileTaskOptions precompilationOptions, int permId,
      File compilerWorkDir) throws UnableToCompleteException {
    ModuleDef module = ModuleDefLoader.loadFromClassPath(logger, moduleName);

    CompilerContext compilerContext = new CompilerContext.Builder()
        .options(precompilationOptions).module(module).build();

    logger = logger.branch(TreeLogger.INFO, "Compiling permutation " + permId);

    File precompilationFile = new File(compilerWorkDir,
        PrecompileOnePerm.getPrecompileFilename(permId));
    Precompilation precompilation = (Precompilation) CompilePerms.readPrecompilationFile(
        logger, precompilationFile);

    // Choose the permutation that goes with this precompilation
    Permutation[] subPerms = CompilePerms.selectPermutationsForPrecompilation(
        new int[]{permId}, precompilation);
    assert subPerms.length == 1;

    PermutationResult permResult =
        JavaToJavaScriptCompiler.compilePermutation(precompilation.getUnifiedAst(), logger,
            compilerContext, subPerms[0]);
    Link.linkOnePermutationToJar(logger, module, compilerContext.getPublicResourceOracle(),
        precompilation.getGeneratedArtifacts(), permResult, makePermFilename(
            compilerWorkDir, permId), precompilationOptions);
    return true;
  }

  private final CompileOnePermOptionsImpl options;

  public CompileOnePerm(CompileOnePermOptions options) {
    this.options = new CompileOnePermOptionsImpl(options);
  }

  public boolean run(TreeLogger logger) throws UnableToCompleteException {
    List<String> moduleNames = options.getModuleNames();
    if (moduleNames.size() != 1) {
      logger.log(TreeLogger.ERROR, "Expected a single module name.");
      return false;
    }
    int permToRun = options.getPermToCompile();
    if (permToRun < 0) {
      logger.log(TreeLogger.ERROR,
          "Expected argument -perm to specify the permutation to compile.");
      return false;
    }

    String moduleName = moduleNames.get(0);
    File compilerWorkDir = options.getCompilerWorkDir(moduleName);

    // Look for the sentinel file that indicates that this compilation already
    // has a precompilation result from a previous Precompile step.
    PrecompileTaskOptions precompilationOptions = AnalyzeModule.readAnalyzeModuleOptionsFile(
        logger, compilerWorkDir);
    if (precompilationOptions == null) {
      logger.log(TreeLogger.ERROR, "Could not read file "
          + AnalyzeModule.OPTIONS_FILENAME + " output from AnalyzeModule step.");
      return false;
    }

    return compileSpecificPermutation(logger, moduleName,
        precompilationOptions, permToRun, compilerWorkDir);
  }
}
