/*
 * 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.intellij.ideabuck.build;

import com.facebook.buck.intellij.ideabuck.config.BuckExecutableSettingsProvider;
import com.facebook.buck.intellij.ideabuck.config.BuckModule;
import com.facebook.buck.intellij.ideabuck.config.BuckProjectSettingsProvider;
import com.facebook.buck.intellij.ideabuck.ui.BuckUIManager;
import com.facebook.buck.intellij.ideabuck.ui.components.BuckDebugPanel;
import com.intellij.execution.filters.HyperlinkInfo;
import com.intellij.execution.ui.ConsoleViewContentType;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.wm.WindowManager;
import javax.swing.JComponent;
import org.jetbrains.annotations.Nullable;

/**
 * Run buck build commands in background thread, then parse the output from stderr and print to
 * IntelliJ's console window.
 *
 * <p>We can also create a websocket client and collect information from buck's local http server.
 * However, http server of buck is still immaturity, lots of useful information is not available.
 * For example, we can't get compiler error outputs. Therefore the only way for us is parsing the
 * stderr output of the buck process.
 */
public class BuckBuildManager {

  public static final String NOT_BUCK_PROJECT_ERROR_MESSAGE = "Not a valid Buck project!\n";
  private BuckCommandHandler currentRunningBuckCommandHandler;

  private boolean isBuilding = false;
  private boolean isKilling = false;

  public static synchronized BuckBuildManager getInstance(Project project) {
    return ServiceManager.getService(project, BuckBuildManager.class);
  }

  public synchronized void setProgress(double fraction) {
    return;
  }

  /** Get saved target for this project from settings. */
  public static String getCurrentSavedTarget(Project project) {
    return BuckProjectSettingsProvider.getInstance(project).getLastAlias().orElse(null);
  }

  public BuckCommandHandler getCurrentRunningBuckCommandHandler() {
    return currentRunningBuckCommandHandler;
  }

  public boolean isBuilding() {
    return isBuilding;
  }

  public synchronized void setBuilding(Project project, boolean value) {
    isBuilding = value;
    BuckUIManager.getInstance(project).getBuckToolWindow().updateActionsNow();
  }

  public boolean isKilling() {
    return isKilling;
  }

  public synchronized void setKilling(Project project, boolean value) {
    isKilling = value;
    BuckUIManager.getInstance(project).getBuckToolWindow().updateActionsNow();
  }

  public boolean isBuckProject(Project project) {
    if (project == null) {
      return false;
    }
    VirtualFile base = project.getBaseDir();
    return base.findChild(BuckBuildUtil.BUCK_CONFIG_FILE) != null;
  }

  /**
   * Print "no selected target" error message to console window. Also provide a hyperlink which can
   * directly jump to "Choose Target" GUI window.
   */
  public void showNoTargetMessage(Project project) {
    BuckModule buckModule = project.getComponent(BuckModule.class);
    buckModule.getBuckEventsConsumer().consumeConsoleEvent("Please choose a build target!");

    BuckDebugPanel buckDebugPanel = BuckUIManager.getInstance(project).getBuckDebugPanel();

    buckDebugPanel.outputConsoleMessage("Please ", ConsoleViewContentType.ERROR_OUTPUT);
    buckDebugPanel.outputConsoleHyperlink(
        "choose a build target!\n",
        new HyperlinkInfo() {
          @Override
          public void navigate(Project project) {
            JComponent frame = WindowManager.getInstance().getIdeFrame(project).getComponent();
            AnAction action = ActionManager.getInstance().getAction("buck.ChooseTarget");
            action.actionPerformed(
                new AnActionEvent(
                    null,
                    DataManager.getInstance().getDataContext(frame),
                    ActionPlaces.UNKNOWN,
                    action.getTemplatePresentation(),
                    ActionManager.getInstance(),
                    0));
          }
        });
  }

  private void saveAndRun(final BuckCommandHandler handler, Runnable runnable) {
    if (!(handler instanceof BuckKillCommandHandler)) {
      currentRunningBuckCommandHandler = handler;
      // Save files for anything besides buck kill
      ApplicationManager.getApplication()
          .invokeAndWait(
              () -> FileDocumentManager.getInstance().saveAllDocuments(), ModalityState.NON_MODAL);
    }
    Project project = handler.project();
    BuckDebugPanel buckDebugPanel = BuckUIManager.getInstance(project).getBuckDebugPanel();

    String exec = BuckExecutableSettingsProvider.getInstance(project).resolveBuckExecutable();
    if (exec == null) {
      buckDebugPanel.outputConsoleMessage(
          "Please specify the buck executable path!\n", ConsoleViewContentType.ERROR_OUTPUT);

      buckDebugPanel.outputConsoleMessage(
          "Preference -> Tools -> Buck -> Path to Buck executable\n",
          ConsoleViewContentType.NORMAL_OUTPUT);
      return;
    }

    runnable.run();
  }

  /**
   * Execute simple process asynchronously with progress.
   *
   * @param handler a handler
   * @param operationTitle an operation title shown in progress dialog
   */
  public synchronized void runBuckCommand(
      final BuckCommandHandler handler, final String operationTitle) {
    saveAndRun(
        handler,
        () -> {
          final ProgressManager manager = ProgressManager.getInstance();
          ApplicationManager.getApplication()
              .invokeLater(
                  () ->
                      manager.run(
                          new Task.Backgroundable(handler.project(), operationTitle, true) {
                            @Override
                            public void run(final ProgressIndicator indicator) {
                              runInCurrentThread(handler, indicator, true, operationTitle);
                            }
                          }));
        });
  }

  /**
   * Execute simple process synchronously in the current thread.
   *
   * @param handler a handler
   * @param postStartAction an action that is executed after the process starts
   */
  public synchronized void runBuckCommandAndWait(
      final BuckCommandHandler handler, @Nullable Runnable postStartAction) {
    saveAndRun(handler, () -> handler.runInCurrentThread(postStartAction));
  }

  /**
   * Execute simple process asynchronously with progress and connect to Buck server.
   *
   * @param handler a handler
   * @param operationTitle an operation title shown in progress dialog
   * @param module the BuckModule throught which we connect to Buck
   */
  public synchronized void runBuckCommandWhileConnectedToBuck(
      final BuckCommandHandler handler, final String operationTitle, final BuckModule module) {
    runBuckCommand(handler, operationTitle);
  }

  /**
   * Run handler in the current thread.
   *
   * @param handler a handler to run
   * @param indicator a progress manager
   * @param setIndeterminateFlag if true handler is configured as indeterminate
   * @param operationName
   */
  public void runInCurrentThread(
      final BuckCommandHandler handler,
      final ProgressIndicator indicator,
      final boolean setIndeterminateFlag,
      @Nullable final String operationName) {
    runInCurrentThread(
        handler,
        new Runnable() {
          public void run() {
            if (indicator != null) {
              indicator.setText(operationName);
              indicator.setText2("");
              if (setIndeterminateFlag) {
                indicator.setIndeterminate(true);
              }
            }
          }
        });
  }

  /**
   * Run handler in the current thread.
   *
   * @param handler a handler to run
   * @param postStartAction an action that is executed
   */
  public void runInCurrentThread(
      final BuckCommandHandler handler, @Nullable final Runnable postStartAction) {
    handler.runInCurrentThread(postStartAction);
  }

  /**
   * Run handler in the current thread with a post end action.
   *
   * @param handler a handler to run
   * @param postEndAction an action that is executed
   */
  public void runInCurrentThreadPostEnd(
      final BuckCommandHandler handler, @Nullable final Runnable postEndAction) {
    handler.runInCurrentThreadPostEnd(postEndAction);
  }
}
