package com.intellij.plugin.buck.build;

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.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 com.intellij.plugin.buck.config.BuckSettingsProvider;
import com.intellij.plugin.buck.ui.BuckToolWindowFactory;
import org.jetbrains.annotations.Nullable;

import javax.swing.JComponent;

/**
 * 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 ProgressIndicator mProgressIndicator;
  private boolean mIsBuilding = false;
  private boolean mIsKilling = false;

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

  public synchronized void setProgress(double fraction) {
    if (mProgressIndicator == null) {
      return;
    }
    mProgressIndicator.setFraction(fraction);
    mProgressIndicator.checkCanceled();
  }

  /**
   * Get saved target for this project from settings.
   */
  public String getCurrentSavedTarget(Project project) {
    if (BuckSettingsProvider.getInstance().getState().lastAlias == null) {
      return null;
    }
    return BuckSettingsProvider.getInstance().getState().lastAlias.get(project.getBasePath());
  }

  public boolean isBuilding() {
    return mIsBuilding;
  }

  public synchronized void setBuilding(Project project, boolean value) {
    mIsBuilding = value;
    BuckToolWindowFactory.updateActionsNow(project);
  }

  public boolean isKilling() {
    return mIsKilling;
  }

  public synchronized void setKilling(Project project, boolean value) {
    mIsKilling = value;
    BuckToolWindowFactory.updateActionsNow(project);
  }

  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) {
    BuckToolWindowFactory.outputConsoleMessage(
        project, "Please ", ConsoleViewContentType.ERROR_OUTPUT);
    BuckToolWindowFactory.outputConsoleHyperlink(
        project,
        "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)
            );
          }
        });
  }

  /**
   * Execute simple process asynchronously with progress.
   *
   * @param handler        a handler
   * @param operationTitle an operation title shown in progress dialog
   */
  public void runBuckCommand(
      final BuckCommandHandler handler,
      final String operationTitle) {
    Project project = handler.project();

    // Always save files
    FileDocumentManager.getInstance().saveAllDocuments();

    String exec = BuckSettingsProvider.getInstance().getState().buckExecutable;
    if (exec == null) {
      BuckToolWindowFactory.outputConsoleMessage(
          project,
          "Please specify the buck executable path!\n",
          ConsoleViewContentType.ERROR_OUTPUT);

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

    final ProgressManager manager = ProgressManager.getInstance();
    manager.run(new Task.Backgroundable(handler.project(), operationTitle, true) {
      public void run(final ProgressIndicator indicator) {
        mProgressIndicator = indicator;
        runInCurrentThread(handler, indicator, true, 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);
  }
}