/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * 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.facebook.buck.core.build.execution.context;

import com.facebook.buck.android.exopackage.AndroidDevicesHelper;
import com.facebook.buck.core.cell.CellPathResolver;
import com.facebook.buck.core.filesystems.AbsPath;
import com.facebook.buck.core.rulekey.RuleKeyDiagnosticsMode;
import com.facebook.buck.core.util.immutables.BuckStyleValueWithBuilder;
import com.facebook.buck.event.BuckEventBus;
import com.facebook.buck.event.IsolatedEventBus;
import com.facebook.buck.io.filesystem.ProjectFilesystemFactory;
import com.facebook.buck.util.Console;
import com.facebook.buck.util.Verbosity;
import com.facebook.buck.worker.WorkerProcessPool;
import com.google.common.io.Closer;
import java.io.PrintStream;
import java.nio.file.Path;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.immutables.value.Value;

/** The context exposed for executing {@link com.facebook.buck.step.Step}s */
@BuckStyleValueWithBuilder
@SuppressWarnings(
    "immutables:from") // Suppress warning for event bus being different type in superclass
public abstract class StepExecutionContext extends IsolatedExecutionContext {

  /**
   * Creates {@link StepExecutionContext} from {@link ExecutionContext}, {@code ruleCellRoot} and
   * {@code actionId}
   */
  public static StepExecutionContext from(
      ExecutionContext executionContext, AbsPath ruleCellRoot, String actionId) {
    return StepExecutionContext.builder()
        .setConsole(executionContext.getConsole())
        .setBuckEventBus(executionContext.getBuckEventBus())
        .setPlatform(executionContext.getPlatform())
        .setEnvironment(executionContext.getEnvironment())
        .setProcessExecutor(executionContext.getProcessExecutor())
        .setAndroidDevicesHelper(executionContext.getAndroidDevicesHelper())
        .setPersistentWorkerPools(executionContext.getPersistentWorkerPools())
        .setCellPathResolver(executionContext.getCellPathResolver())
        .setBuildCellRootPath(executionContext.getBuildCellRootPath())
        .setProjectFilesystemFactory(executionContext.getProjectFilesystemFactory())
        .setClassLoaderCache(executionContext.getClassLoaderCache())
        .setShouldReportAbsolutePaths(executionContext.shouldReportAbsolutePaths())
        .setRuleKeyDiagnosticsMode(executionContext.getRuleKeyDiagnosticsMode())
        .setTruncateFailingCommandEnabled(executionContext.isTruncateFailingCommandEnabled())
        .setWorkerProcessPools(executionContext.getWorkerProcessPools())
        .setRuleCellRoot(ruleCellRoot)
        .setActionId(actionId)
        .build();
  }

  public abstract BuckEventBus getBuckEventBus();

  public abstract Optional<AndroidDevicesHelper> getAndroidDevicesHelper();

  /**
   * Worker process pools that are persisted across buck invocations inside buck daemon. If buck is
   * running without daemon, there will be no persisted pools.
   */
  public abstract Optional<ConcurrentMap<String, WorkerProcessPool>> getPersistentWorkerPools();

  public abstract CellPathResolver getCellPathResolver();

  /**
   * The absolute path to the cell where this build was invoked.
   *
   * <p>For example, consider two cells: cell1 and cell2. If a build like "buck build cell2//:bar"
   * was invoked from cell1, this method would return cell1's path.
   *
   * <p>See {@link com.facebook.buck.core.build.context.BuildContext#getBuildCellRootPath}.
   */
  public abstract Path getBuildCellRootPath();

  public abstract ProjectFilesystemFactory getProjectFilesystemFactory();

  @Value.Default
  public boolean shouldReportAbsolutePaths() {
    return false;
  }

  @Value.Default
  public RuleKeyDiagnosticsMode getRuleKeyDiagnosticsMode() {
    return RuleKeyDiagnosticsMode.NEVER;
  }

  @Value.Default
  public boolean isTruncateFailingCommandEnabled() {
    return true;
  }

  /**
   * Worker process pools that you can populate as needed. These will be destroyed as soon as buck
   * invocation finishes, thus, these pools are not persisted across buck invocations.
   */
  @Value.Default
  public ConcurrentMap<String, WorkerProcessPool> getWorkerProcessPools() {
    return new ConcurrentHashMap<>();
  }

  @Override
  @Value.Default
  public IsolatedEventBus getIsolatedEventBus() {
    return getBuckEventBus().isolated();
  }

  @Override
  public StepExecutionContext createSubContext(
      PrintStream newStdout, PrintStream newStderr, Optional<Verbosity> verbosityOverride) {
    Console console =
        new Console(
            verbosityOverride.orElse(this.getConsole().getVerbosity()),
            newStdout,
            newStderr,
            this.getConsole().getAnsi());

    // This should replace (or otherwise retain) all of the closeable parts of the context.
    return StepExecutionContext.builder()
        .from(this)
        .setConsole(console)
        .setProcessExecutor(getProcessExecutor().cloneWithOutputStreams(newStdout, newStderr))
        .setClassLoaderCache(getClassLoaderCache().addRef())
        .setAndroidDevicesHelper(Optional.empty())
        .setWorkerProcessPools(new ConcurrentHashMap<>())
        .build();
  }

  @Override
  protected void registerCloseables(Closer closer) {
    super.registerCloseables(closer);
    getAndroidDevicesHelper().ifPresent(closer::register);
    // The closer closes in reverse order, so do the clear first.
    closer.register(getWorkerProcessPools()::clear);
    getWorkerProcessPools().values().forEach(closer::register);
  }

  public static Builder builder() {
    return new Builder();
  }

  public static class Builder extends ImmutableStepExecutionContext.Builder {}
}

@@ -1136,88 +1136,8 @@
 ry;%0A
-import com.facebook.buck.util.Console;%0Aimport com.facebook.buck.util.Verbosity;%0A
 impo
@@ -1223,36 +1223,8 @@
 er;%0A
-import java.io.PrintStream;%0A
 impo
@@ -4855,836 +4855,8 @@
  %7D%0A%0A
-  @Override%0A  public StepExecutionContext createSubContext(%0A      PrintStream newStdout, PrintStream newStderr, Optional%3CVerbosity%3E verbosityOverride) %7B%0A    Console console =%0A        new Console(%0A            verbosityOverride.orElse(this.getConsole().getVerbosity()),%0A            newStdout,%0A            newStderr,%0A            this.getConsole().getAnsi());%0A%0A    // This should replace (or otherwise retain) all of the closeable parts of the context.%0A    return StepExecutionContext.builder()%0A        .from(this)%0A        .setConsole(console)%0A        .setProcessExecutor(getProcessExecutor().cloneWithOutputStreams(newStdout, newStderr))%0A        .setClassLoaderCache(getClassLoaderCache().addRef())%0A        .setAndroidDevicesHelper(Optional.empty())%0A        .setWorkerProcessPools(new ConcurrentHashMap%3C%3E())%0A        .build();%0A  %7D%0A%0A
   @O

Remove createSubContext from StepExecutionContext