// Copyright 2007 The Bazel Authors. 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.build.buildjar;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.buildjar.javac.JavacOptions;
import com.google.devtools.build.buildjar.javac.plugins.BlazeJavaCompilerPlugin;
import com.google.devtools.build.buildjar.javac.plugins.classloader.ClassLoaderMaskingPlugin;
import com.google.devtools.build.buildjar.javac.plugins.dependency.DependencyModule;
import com.google.devtools.build.buildjar.javac.plugins.errorprone.ErrorPronePlugin;
import com.google.devtools.build.lib.worker.WorkerProtocol.WorkRequest;
import com.google.devtools.build.lib.worker.WorkerProtocol.WorkResponse;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.List;
import java.util.ListIterator;

/**
 * The JavaBuilder main called by bazel.
 */
public abstract class BazelJavaBuilder {

  private static final String CMDNAME = "BazelJavaBuilder";

  /**
   * The main method of the BazelJavaBuilder.
   */
  public static void main(String[] args) {
    if (args.length == 1 && args[0].equals("--persistent_worker")) {
      System.exit(runPersistentWorker());
    } else {
      // This is a single invocation of JavaBuilder that exits after it processed the request.
      int exitCode = 1;
      try (PrintWriter err =
          new PrintWriter(new OutputStreamWriter(System.err, Charset.defaultCharset()))) {
        exitCode = processRequest(Arrays.asList(args), err);
      }
      System.exit(exitCode);
    }
  }

  private static int runPersistentWorker() {
    while (true) {
      try {
        WorkRequest request = WorkRequest.parseDelimitedFrom(System.in);

        if (request == null) {
          break;
        }

        try (StringWriter sw = new StringWriter();
            PrintWriter pw = new PrintWriter(sw)) {
          int exitCode = processRequest(request.getArgumentsList(), pw);
          WorkResponse.newBuilder()
              .setOutput(sw.toString())
              .setExitCode(exitCode)
              .build()
              .writeDelimitedTo(System.out);
          System.out.flush();
        }
      } catch (IOException e) {
        e.printStackTrace();
        return 1;
      }
    }
    return 0;
  }

  public static int processRequest(List<String> args, PrintWriter err) {
    try {
      JavaLibraryBuildRequest build = parse(args);
      AbstractJavaBuilder builder = build.getDependencyModule().reduceClasspath()
          ? new ReducedClasspathJavaLibraryBuilder()
          : new SimpleJavaLibraryBuilder();
      return builder.run(build, err).exitCode;
    } catch (InvalidCommandLineException e) {
      System.err.println(CMDNAME + " threw exception: " + e.getMessage());
      return 1;
    } catch (Exception e) {
      e.printStackTrace();
      return 1;
    }
  }

  private static boolean processAndRemoveExtraChecksOptions(List<String> args) {
    // error-prone is enabled by default for Bazel.
    boolean errorProneEnabled = true;

    ListIterator<String> arg = args.listIterator();
    while (arg.hasNext()) {
      switch (arg.next()) {
        case "-extra_checks":
        case "-extra_checks:on":
          errorProneEnabled = true;
          arg.remove();
          break;
        case "-extra_checks:off":
          errorProneEnabled = false;
          arg.remove();
          break;
      }
    }

    return errorProneEnabled;
  }

  /**
   * Parses the list of arguments into a {@link JavaLibraryBuildRequest}. The returned
   * {@link JavaLibraryBuildRequest} object can be then used to configure the compilation itself.
   *
   * @throws IOException if the argument list contains a file (with the @ prefix) and reading that
   *         file failed
   * @throws InvalidCommandLineException on any command line error
   */
  @VisibleForTesting
  public static JavaLibraryBuildRequest parse(List<String> args) throws IOException,
      InvalidCommandLineException {
    OptionsParser optionsParser = new OptionsParser(args);
    ImmutableList.Builder<BlazeJavaCompilerPlugin> plugins = ImmutableList.builder();
    plugins.add(new ClassLoaderMaskingPlugin());

    // Support for -extra_checks:off was removed from ErrorPronePlugin, but Bazel still needs it,
    // so we'll emulate support for this here by handling the flag ourselves and not loading the
    // plug-in when it is specified.
    boolean errorProneEnabled = processAndRemoveExtraChecksOptions(optionsParser.getJavacOpts());
    if (errorProneEnabled) {
      plugins.add(new ErrorPronePlugin());
    }

    JavaLibraryBuildRequest build =
        new JavaLibraryBuildRequest(optionsParser, plugins.build(), new DependencyModule.Builder());
    build.setJavacOpts(JavacOptions.normalizeOptions(build.getJavacOpts()));
    return build;
  }
}
