// Copyright 2016 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.BlazeJavacResult;
import com.google.devtools.build.buildjar.javac.BlazeJavacResult.Status;
import com.google.devtools.build.buildjar.javac.FormattedDiagnostic;
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.dependency.DependencyModule;
import com.google.devtools.build.buildjar.javac.plugins.errorprone.ErrorPronePlugin;
import com.google.devtools.build.lib.worker.ProtoWorkerMessageProcessor;
import com.google.devtools.build.lib.worker.WorkRequestHandler;
import com.google.devtools.build.lib.worker.WorkRequestHandler.WorkRequestHandlerBuilder;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nonnull;

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

  private static final String CMDNAME = "BazelJavaBuilder";

  /** The main method of the BazelJavaBuilder. */
  public static void main(String[] args) {
    BazelJavaBuilder builder = new BazelJavaBuilder();
    if (args.length == 1 && args[0].equals("--persistent_worker")) {
      WorkRequestHandler workerHandler =
          new WorkRequestHandlerBuilder(
                  new WorkRequestHandler.WorkRequestCallback(
                      (workRequest, printWriter) ->
                          builder.parseAndBuild(
                              workRequest.getArgumentsList(),
                              Path.of(workRequest.getSandboxDir()),
                              printWriter)),
                  System.err,
                  new ProtoWorkerMessageProcessor(System.in, System.out))
              .setCpuUsageBeforeGc(Duration.ofSeconds(10))
              .build();
      int exitCode = 1;
      try {
        workerHandler.processRequests();
        exitCode = 0;
      } catch (IOException e) {
        System.err.println(e.getMessage());
      } finally {
        // Prevent hanging threads from keeping the worker alive.
        System.exit(exitCode);
      }
    } else {
      PrintWriter pw =
          new PrintWriter(new OutputStreamWriter(System.err, Charset.defaultCharset()));
      int returnCode;
      try {
        returnCode = builder.parseAndBuild(Arrays.asList(args), Path.of(""), pw);
      } finally {
        pw.flush();
      }
      System.exit(returnCode);
    }
  }

  public int parseAndBuild(List<String> args, @Nonnull Path workDir, PrintWriter pw) {
    try {
      JavaLibraryBuildRequest build = parse(args, workDir);
      try (SimpleJavaLibraryBuilder builder =
          build.getDependencyModule().reduceClasspath()
              ? new ReducedClasspathJavaLibraryBuilder()
              : new SimpleJavaLibraryBuilder()) {

        return build(builder, build, pw);
      }
    } catch (InvalidCommandLineException e) {
      pw.println(CMDNAME + " threw exception: " + e.getMessage());
      return 1;
    } catch (Exception e) {
      e.printStackTrace();
      return 1;
    }
  }

  /**
   * Uses {@code builder} to build the target passed in {@code buildRequest}. All errors and
   * diagnostics should be written to {@code err}.
   *
   * @return An error code, 0 is success, any other value is an error.
   */
  protected int build(
      SimpleJavaLibraryBuilder builder, JavaLibraryBuildRequest buildRequest, Writer err)
      throws Exception {
    BlazeJavacResult result = builder.run(buildRequest);
    if (result.status() == Status.REQUIRES_FALLBACK) {
      return 0;
    }
    for (FormattedDiagnostic d : result.diagnostics()) {
      err.write(d.getFormatted() + "\n");
    }
    err.write(result.output());
    return result.isOk() ? 0 : 1;
  }

  /**
   * 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 JavaLibraryBuildRequest parse(List<String> args, @Nonnull Path workDir)
      throws IOException, InvalidCommandLineException {
    OptionsParser optionsParser =
        new OptionsParser(args, JavacOptions.createWithWarningsAsErrorsDefault(ImmutableList.of()));
    ImmutableList<BlazeJavaCompilerPlugin> plugins =
        ImmutableList.of(new ErrorPronePlugin(BazelScannerSuppliers.bazelChecks()));
    return new JavaLibraryBuildRequest(
        optionsParser, plugins, new DependencyModule.Builder(), workDir);
  }
}
