// Copyright 2000-2020 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.stash;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.ChangesUtil;
import com.intellij.openapi.vcs.changes.VcsDirtyScopeManager;
import com.intellij.openapi.vcs.changes.VcsShelveChangesSaver;
import com.intellij.openapi.vcs.changes.shelf.ShelvedChangeList;
import com.intellij.openapi.vcs.changes.shelf.ShelvedChangesViewManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.Functions;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.vcsUtil.VcsFileUtil;
import com.intellij.vcsUtil.VcsUtil;
import git4idea.commands.Git;
import git4idea.commands.GitCommand;
import git4idea.commands.GitCommandResult;
import git4idea.commands.GitLineHandler;
import git4idea.config.GitSaveChangesPolicy;
import git4idea.config.GitVersionSpecialty;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

import java.util.*;

public final class GitShelveChangesSaver extends GitChangesSaver {
  private static final Logger LOG = Logger.getInstance(GitShelveChangesSaver.class);
  private final VcsShelveChangesSaver myVcsShelveChangesSaver;

  public GitShelveChangesSaver(@NotNull Project project,
                               @NotNull Git git,
                               @NotNull ProgressIndicator indicator,
                               @NotNull @Nls String stashMessage) {
    super(project, git, indicator, GitSaveChangesPolicy.SHELVE, stashMessage);
    myVcsShelveChangesSaver = new VcsShelveChangesSaver(project, indicator, stashMessage) {
      @Override
      protected void doRollback(@NotNull Collection<? extends VirtualFile> rootsToSave,
                                @NotNull Collection<Change> shelvedChanges) {
        rollbackChanges(rootsToSave, shelvedChanges);
      }
    };
  }

  @Override
  protected void save(@NotNull Collection<? extends VirtualFile> rootsToSave) throws VcsException {
    myVcsShelveChangesSaver.save(rootsToSave);
  }

  @Override
  public void load() {
    myVcsShelveChangesSaver.load();
  }

  @Override
  public boolean wereChangesSaved() {
    List<ShelvedChangeList> shelvedLists = myVcsShelveChangesSaver.getShelvedLists();
    return !shelvedLists.isEmpty();
  }

  @Override
  public void showSavedChanges() {
    List<ShelvedChangeList> shelvedLists = myVcsShelveChangesSaver.getShelvedLists();
    if (!shelvedLists.isEmpty()) {
      Comparator<ShelvedChangeList> nameComparator = Comparator.comparing(it -> it.getDisplayName(), String.CASE_INSENSITIVE_ORDER);
      List<ShelvedChangeList> sorted = ContainerUtil.sorted(shelvedLists, nameComparator);
      ShelvedChangesViewManager.getInstance(myProject).activateView(sorted.get(0));
    }
  }

  private void rollbackChanges(@NotNull Collection<? extends VirtualFile> rootsToSave,
                               @NotNull Collection<Change> shelvedChanges) {
    if (GitVersionSpecialty.RESTORE_SUPPORTED.existsIn(myProject)) {
      Set<FilePath> filePaths = new HashSet<>();
      for (Change change : shelvedChanges) {
        ContainerUtil.addAllNotNull(filePaths, ChangesUtil.getBeforePath(change));
        ContainerUtil.addAllNotNull(filePaths, ChangesUtil.getAfterPath(change));
      }

      VcsUtil.groupByRoots(myProject, filePaths, Functions.identity()).forEach((root, paths) -> {
        if (rootsToSave.contains(root.getPath())) {
          restoreStagedWorktree(myProject, root.getPath(), paths);
        }
      });
    }
    else {
      for (VirtualFile root : rootsToSave) {
        resetHardLocal(myProject, root);
      }
    }

    for (VirtualFile root : rootsToSave) {
      VcsDirtyScopeManager.getInstance(myProject).dirDirtyRecursively(root);
    }
  }

  private static void resetHardLocal(@NotNull Project project, @NotNull VirtualFile root) {
    GitLineHandler handler = new GitLineHandler(project, root, GitCommand.RESET);
    handler.addParameters("--hard");
    handler.endOptions();
    GitCommandResult result = Git.getInstance().runCommand(handler);
    if (!result.success()) {
      LOG.warn("Can't reset changes:" + result.getErrorOutputAsJoinedString());
    }
  }

  private static void restoreStagedWorktree(@NotNull Project project, @NotNull VirtualFile root, @NotNull List<FilePath> filePaths) {
    for (List<String> paths : VcsFileUtil.chunkPaths(root, filePaths)) {
      GitLineHandler handler = new GitLineHandler(project, root, GitCommand.RESTORE);
      handler.addParameters("--staged", "--worktree", "--source=HEAD");
      handler.endOptions();
      handler.addParameters(paths);
      GitCommandResult result = Git.getInstance().runCommand(handler);
      if (!result.success()) {
        LOG.warn("Can't restore changes:" + result.getErrorOutputAsJoinedString());
      }
    }
  }

  @NonNls
  @Override
  public String toString() {
    return "ShelveChangesSaver. Lists: " + myVcsShelveChangesSaver.getShelvedLists();
  }
}

@@ -808,44 +808,8 @@
 le;%0A
-import com.intellij.util.Functions;%0A
 impo
@@ -907,32 +907,20 @@
 ort 
-com.intellij.vcsUtil.Vcs
+git4idea.Git
 Util
@@ -1161,16 +1161,95 @@
 cialty;%0A
+import git4idea.repo.GitRepository;%0Aimport git4idea.repo.GitRepositoryManager;%0A
 import o
@@ -3684,71 +3684,74 @@
     
-Vcs
+Git
 Util.
-groupByRoots(myProject, filePaths, Functions.identity()
+sortFilePathsByGitRootIgnoringMissing(myProject, filePaths
 ).fo
@@ -3787,16 +3787,17 @@
     if (
+!
 rootsToS
@@ -3817,20 +3817,482 @@
 root
-.getPath()))
+)) %7B%0A          LOG.warn(String.format(%22Paths not under shelved root: root - %25s, paths - %25s, shelved roots - %25s%22, root, paths, rootsToSave));%0A          return;%0A        %7D%0A%0A        GitRepository repository = GitRepositoryManager.getInstance(myProject).getRepositoryForRoot(root);%0A        boolean isFreshRepository = repository != null && repository.getCurrentRevision() == null;%0A        if (isFreshRepository) %7B%0A          resetHardLocal(myProject, root);%0A        %7D%0A        else
  %7B%0A 
@@ -4341,18 +4341,8 @@
 root
-.getPath()
 , pa

fix automatic shelve for fresh repositories