/*
 * Copyright 2011 Google Inc. All Rights Reserved.
 *
 * 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.devtools.j2objc;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Lists;
import com.google.devtools.j2objc.Options.TimingLevel;
import com.google.devtools.j2objc.pipeline.GenerationBatch;
import com.google.devtools.j2objc.pipeline.InputFilePreprocessor;
import com.google.devtools.j2objc.pipeline.ProcessingContext;
import com.google.devtools.j2objc.pipeline.TranslationProcessor;
import com.google.devtools.j2objc.util.CodeReferenceMap;
import com.google.devtools.j2objc.util.ErrorUtil;
import com.google.devtools.j2objc.util.FileUtil;
import com.google.devtools.j2objc.util.Parser;
import com.google.devtools.j2objc.util.ProGuardUsageParser;
import com.google.devtools.j2objc.util.UnicodeUtils;
import java.io.File;
import java.io.IOException;
import java.util.List;
import java.util.Set;

/**
 * Translation tool for generating Objective C source files from Java sources.
 * This tool is not intended to be a general purpose converter, but instead is
 * focused on what is needed for business logic libraries written in Java to
 * run natively on iOS.  In particular, no attempt is made to translate Java
 * UI framework code to any iOS frameworks.
 *
 * @author Tom Ball
 */
public class J2ObjC {

  static {
    // Always enable assertions in translator.
    ClassLoader loader = J2ObjC.class.getClassLoader();
    if (loader != null) {
      loader.setPackageAssertionStatus(J2ObjC.class.getPackage().getName(), true);
    }
  }

  public static String getFileHeader(Options options, String sourceFileName) {
    return options.emitSourceHeaders()
        ? UnicodeUtils.format(Options.getFileHeader(), sourceFileName)
        : "";
  }

  private static void checkErrors(boolean treatWarningsAsErrors) {
    int errors = ErrorUtil.errorCount();
    if (treatWarningsAsErrors) {
      errors += ErrorUtil.warningCount();
    }
    if (errors > 0) {
      System.exit(errors);
    }
  }

  @VisibleForTesting
  public static Parser createParser(Options options) {
    Parser parser = Parser.newParser(options);
    parser.addClasspathEntries(options.fileUtil().getClassPathEntries());
    parser.addSourcepathEntries(options.fileUtil().getSourcePathEntries());
    parser.setEnableDocComments(options.docCommentsEnabled());
    return parser;
  }

  private static CodeReferenceMap loadDeadCodeMap(Options options) {
    return ProGuardUsageParser.parseDeadCodeFile(options.getProGuardUsageFile());
  }

  /**
   * Runs the entire J2ObjC pipeline.
   * @param fileArgs the files to process, same format as command-line args to {@link #main}.
   */
  public static void run(List<String> fileArgs, Options options) {
    File preProcessorTempDir = null;
    File strippedSourcesDir = null;
    Parser parser = null;
    try {
      List<ProcessingContext> inputs = Lists.newArrayList();
      GenerationBatch batch = new GenerationBatch(options);
      batch.processFileArgs(fileArgs);
      inputs.addAll(batch.getInputs());
      if (ErrorUtil.errorCount() > 0) {
        return;
      }

      parser = createParser(options);
      Parser.ProcessingResult processingResult = parser.processAnnotations(fileArgs, inputs);
      List<ProcessingContext> generatedInputs = processingResult.getGeneratedSources();
      inputs.addAll(generatedInputs); // Ensure all generatedInputs are at end of input list.
      preProcessorTempDir = processingResult.getSourceOutputDirectory();
      if (ErrorUtil.errorCount() > 0) {
        return;
      }
      if (preProcessorTempDir != null) {
        parser.addSourcepathEntry(preProcessorTempDir.getAbsolutePath());
      }

      InputFilePreprocessor inputFilePreprocessor = new InputFilePreprocessor(parser);
      inputFilePreprocessor.processInputs(inputs);
      if (ErrorUtil.errorCount() > 0) {
        return;
      }
      strippedSourcesDir = inputFilePreprocessor.getStrippedSourcesDir();
      if (strippedSourcesDir != null) {
        parser.prependSourcepathEntry(strippedSourcesDir.getPath());
      }

      options.getHeaderMap().loadMappings();
      TranslationProcessor translationProcessor =
          new TranslationProcessor(parser, loadDeadCodeMap(options));
      translationProcessor.processInputs(inputs);
      if (ErrorUtil.errorCount() > 0) {
        return;
      }
      translationProcessor.postProcess();

      options.getHeaderMap().printMappings();
    } finally {
      if (parser != null) {
        try {
          parser.close();
        } catch (IOException e) {
          ErrorUtil.error(e.getMessage());
        }
      }
      Set<String> tempDirs = options.fileUtil().getTempDirs();
      for (String dir : tempDirs) {
        FileUtil.deleteTempDir(new File(dir));
      }
      FileUtil.deleteTempDir(preProcessorTempDir);
      FileUtil.deleteTempDir(strippedSourcesDir);
    }
  }

  /**
   * Entry point for tool.
   * Initializes {@link Options}, calls {@link #run}, and exits.
   *
   * @param args command-line arguments: flags and source file names
   */
  public static void main(String[] args) {
    if (args.length == 0) {
      Options.help(true);
    }
    long startTime = System.currentTimeMillis();

    List<String> files = null;
    Options options = new Options();

    try {
      files = options.load(args);
      if (files.isEmpty()) {
        Options.usage("no source files");
      }
    } catch (IOException e) {
      ErrorUtil.error(e.getMessage());
      System.exit(1);
    }

    run(files, options);

    TimingLevel timingLevel = options.timingLevel();
    if (timingLevel == TimingLevel.TOTAL || timingLevel == TimingLevel.ALL) {
      System.out.printf("j2objc execution time: %d ms\n", System.currentTimeMillis() - startTime);
    }

    // Run last, since it calls System.exit() with the number of errors.
    checkErrors(options.treatWarningsAsErrors());
  }
}
