// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package git4idea.actions;

import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileVisitor;
import com.intellij.util.ui.UIUtil;
import com.intellij.vcsUtil.VcsFileUtil;
import git4idea.GitVcs;
import git4idea.util.GitUIUtil;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static com.intellij.openapi.vfs.VirtualFileVisitor.SKIP_ROOT;

/**
 * Basic abstract action handler for all Git actions to extend.
 */
public abstract class BasicAction extends DumbAwareAction {

  @Override
  public void actionPerformed(@NotNull AnActionEvent event) {
    Project project = event.getRequiredData(CommonDataKeys.PROJECT);
    ApplicationManager.getApplication().runWriteAction(() -> FileDocumentManager.getInstance().saveAllDocuments());
    final VirtualFile[] vFiles = event.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY);
    assert vFiles != null : "The action is only available when files are selected";

    final GitVcs vcs = GitVcs.getInstance(project);
    if (!ProjectLevelVcsManager.getInstance(project).checkAllFilesAreUnder(vcs, vFiles)) {
      return;
    }
    final String actionName = getActionName();

    final VirtualFile[] affectedFiles = collectAffectedFiles(project, vFiles);
    final List<VcsException> exceptions = new ArrayList<>();
    final boolean background = perform(project, vcs, exceptions, affectedFiles);
    if (!background) {
      GitVcs.runInBackground(new Task.Backgroundable(project, getActionName()) {
        @Override
        public void run(@NotNull ProgressIndicator indicator) {
          VfsUtil.markDirtyAndRefresh(false, true, false, affectedFiles);
          VcsFileUtil.markFilesDirty(project, Arrays.asList(affectedFiles));
          UIUtil.invokeLaterIfNeeded(() -> GitUIUtil.showOperationErrors(project, exceptions, actionName));
        }
      });
    }
  }

  /**
   * Perform the action over set of files
   *
   * @param project       the context project
   * @param mksVcs        the vcs instance
   * @param exceptions    the list of exceptions to be collected.
   * @param affectedFiles the files to be affected by the operation
   * @return true if the operation scheduled a background job, or cleanup is not needed
   */
  protected abstract boolean perform(@NotNull Project project,
                                     GitVcs mksVcs,
                                     @NotNull List<VcsException> exceptions,
                                     @NotNull VirtualFile[] affectedFiles);

  /**
   * given a list of action-target files, returns ALL the files that should be
   * subject to the action Does not keep directories, but recursively adds
   * directory contents
   *
   * @param project the project subject of the action
   * @param files   the root selection
   * @return the complete set of files this action should apply to
   */
  @NotNull
  protected VirtualFile[] collectAffectedFiles(@NotNull Project project, @NotNull VirtualFile[] files) {
    List<VirtualFile> affectedFiles = new ArrayList<>(files.length);
    ProjectLevelVcsManager projectLevelVcsManager = ProjectLevelVcsManager.getInstance(project);
    for (VirtualFile file : files) {
      if (!file.isDirectory() && projectLevelVcsManager.getVcsFor(file) instanceof GitVcs) {
        affectedFiles.add(file);
      }
      else if (file.isDirectory()) {
        addChildren(project, affectedFiles, file);
      }

    }
    return VfsUtilCore.toVirtualFileArray(affectedFiles);
  }

  /**
   * recursively adds all the children of file to the files list, for which
   * this action makes sense ({@link #appliesTo(Project, VirtualFile)}
   * returns true)
   *
   * @param project the project subject of the action
   * @param files   result list
   * @param file    the file whose children should be added to the result list
   *                (recursively)
   */
  private void addChildren(@NotNull final Project project, @NotNull final List<? super VirtualFile> files, @NotNull VirtualFile file) {
    VfsUtilCore.visitChildrenRecursively(file, new VirtualFileVisitor(SKIP_ROOT, null) {
      @Override
      public boolean visitFile(@NotNull VirtualFile file) {
        if (!file.isDirectory() && appliesTo(project, file)) {
          files.add(file);
        }
        return true;
      }
    });
  }

  /**
   * @return the name of action (it is used in a number of ui elements)
   */
  @NotNull
  protected abstract String getActionName();

  /**
   * Check if the action is applicable to the file. The default checks if the file is a directory
   *
   * @param project the context project
   * @param file    the file to check
   * @return true if the action is applicable to the virtual file
   */
  @SuppressWarnings({"UnusedDeclaration"})
  protected boolean appliesTo(@NotNull Project project, @NotNull VirtualFile file) {
    return !file.isDirectory();
  }

  /**
   * Disable the action if the event does not apply in this context.
   *
   * @param e The update event
   */
  @Override
  public void update(@NotNull AnActionEvent e) {
    super.update(e);
    Presentation presentation = e.getPresentation();
    Project project = e.getData(CommonDataKeys.PROJECT);
    if (project == null) {
      presentation.setEnabledAndVisible(false);
      return;
    }

    VirtualFile[] vFiles = e.getData(CommonDataKeys.VIRTUAL_FILE_ARRAY);
    if (vFiles == null || vFiles.length == 0) {
      presentation.setEnabled(false);
      presentation.setVisible(true);
      return;
    }
    GitVcs vcs = GitVcs.getInstance(project);
    boolean enabled = ProjectLevelVcsManager.getInstance(project).checkAllFilesAreUnder(vcs, vFiles) && isEnabled(project, vcs, vFiles);
    // only enable action if all the targets are under the vcs and the action supports all of them

    presentation.setEnabled(enabled);
    if (ActionPlaces.isPopupPlace(e.getPlace())) {
      presentation.setVisible(enabled);
    }
    else {
      presentation.setVisible(true);
    }
  }

  /**
   * Check if the action should be enabled for the set of the files
   *
   * @param project the context project
   * @param vcs     the vcs to use
   * @param vFiles  the set of files
   * @return true if the action should be enabled
   */
  protected abstract boolean isEnabled(@NotNull Project project, @NotNull GitVcs vcs, @NotNull VirtualFile... vFiles);

  /**
   * Save all files in the application (the operation creates write action)
   */
  public static void saveAll() {
    ApplicationManager.getApplication().runWriteAction(() -> FileDocumentManager.getInstance().saveAllDocuments());
  }
}

@@ -388,68 +388,8 @@
 on;%0A
-import com.intellij.openapi.application.ApplicationManager;%0A
 impo
@@ -1532,65 +1532,8 @@
 %0A   
- ApplicationManager.getApplication().runWriteAction(() -%3E
  Fil
@@ -1577,25 +1577,24 @@
 lDocuments()
-)
 ;%0A    final 
@@ -7142,178 +7142,137 @@
   * 
-Save all files in the application (the operation creates write action)%0A   */%0A  public static void saveAll() %7B%0A    ApplicationManager.getApplication().runWriteAction(() -%3E
+@deprecated Use %7B@link FileDocumentManager#saveAllDocuments()%7D directly.%0A   */%0A  @Deprecated%0A  public static void saveAll() %7B%0A   
  Fil
@@ -7320,17 +7320,16 @@
 uments()
-)
 ;%0A  %7D%0A%7D%0A

cleanup: don't wrap saveAllDocuments into a write action, deprecate the trivial delegating method