// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.jetbrains.idea.svn.integrate;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.versionBrowser.ChangeBrowserSettings;
import com.intellij.util.Consumer;
import com.intellij.util.PairFunction;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.svn.history.LogHierarchyNode;
import org.jetbrains.idea.svn.history.SvnChangeList;
import org.jetbrains.idea.svn.history.SvnCommittedChangesProvider;
import org.jetbrains.idea.svn.history.SvnRepositoryLocation;
import org.jetbrains.idea.svn.mergeinfo.MergeCheckResult;
import org.jetbrains.idea.svn.mergeinfo.MergeChecker;
import org.jetbrains.idea.svn.mergeinfo.OneShotMergeInfoHelper;

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

import static com.intellij.openapi.progress.ProgressManager.progress;
import static com.intellij.openapi.progress.ProgressManager.progress2;
import static org.jetbrains.idea.svn.SvnBundle.message;

public class MergeCalculatorTask extends BaseMergeTask {
  private static final Logger LOG = Logger.getInstance(MergeCalculatorTask.class);

  public static final String PROP_BUNCH_SIZE = "idea.svn.quick.merge.bunch.size";
  private static final int BUNCH_SIZE = 100;

  private final @Nullable SvnBranchPointsCalculator.WrapperInvertor myCopyPoint;
  private final @NotNull OneShotMergeInfoHelper myMergeChecker;
  private final @NotNull List<SvnChangeList> myChangeLists;
  private final @NotNull Consumer<MergeCalculatorTask> myCallback;
  private boolean myAllListsLoaded;

  public MergeCalculatorTask(@NotNull QuickMerge mergeProcess,
                             @Nullable SvnBranchPointsCalculator.WrapperInvertor copyPoint,
                             @NotNull Consumer<MergeCalculatorTask> callback) {
    super(mergeProcess);
    myCopyPoint = copyPoint;
    myCallback = callback;
    myChangeLists = new ArrayList<>();
    // TODO: Previously it was configurable - either to use OneShotMergeInfoHelper or BranchInfo as merge checker, but later that logic
    // TODO: was commented (in 80ebdbfea5210f6c998e67ddf28ca9c670fa4efe on 5/28/2010).
    // TODO: Still check if we need to preserve such configuration or it is sufficient to always use OneShotMergeInfoHelper.
    myMergeChecker = new OneShotMergeInfoHelper(myMergeContext);
  }

  public boolean areAllListsLoaded() {
    return myAllListsLoaded;
  }

  public @NotNull MergeChecker getMergeChecker() {
    return myMergeChecker;
  }

  public @NotNull List<SvnChangeList> getChangeLists() {
    return myChangeLists;
  }

  @Override
  public void run() throws VcsException {
    progress(message("progress.text.collecting.merge.information"));
    myMergeChecker.prepare();

    if (myCopyPoint != null) {
      myChangeLists.addAll(getNotMergedChangeLists(getChangeListsAfter(myCopyPoint.getTrue().getTargetRevision())));
      myAllListsLoaded = true;
    }
    else {
      Pair<List<SvnChangeList>, Boolean> loadResult = loadChangeLists(myMergeContext, -1, getBunchSize(-1));

      myChangeLists.addAll(loadResult.first);
      myAllListsLoaded = loadResult.second;
    }

    if (!myChangeLists.isEmpty()) {
      myCallback.consume(this);
    }
    else {
      LOG.info("Info: Everything is up-to-date");

      myMergeProcess.end(message("notification.content.everything.is.up.to.date"), false);
    }
  }

  private @NotNull List<Pair<SvnChangeList, LogHierarchyNode>> getChangeListsAfter(long revision) throws VcsException {
    ChangeBrowserSettings settings = new ChangeBrowserSettings();
    settings.CHANGE_AFTER = Long.toString(revision);
    settings.USE_CHANGE_AFTER_FILTER = true;

    return getChangeLists(myMergeContext, settings, revision, -1, Pair::create);
  }

  private @NotNull List<SvnChangeList> getNotMergedChangeLists(@NotNull List<Pair<SvnChangeList, LogHierarchyNode>> changeLists) {
    List<SvnChangeList> result = new ArrayList<>();

    progress(message("progress.text.collecting.not.merged.revisions"));
    for (Pair<SvnChangeList, LogHierarchyNode> pair : changeLists) {
      SvnChangeList changeList = pair.getFirst();

      progress2(message("progress.text2.processing.revision", changeList.getNumber()));
      if (MergeCheckResult.NOT_MERGED.equals(myMergeChecker.checkList(changeList)) && !myMergeChecker.checkListForPaths(pair.getSecond())) {
        result.add(changeList);
      }
    }

    return result;
  }

  public static @NotNull Pair<List<SvnChangeList>, Boolean> loadChangeLists(@NotNull MergeContext mergeContext, long beforeRevision, int size)
    throws VcsException {
    ChangeBrowserSettings settings = new ChangeBrowserSettings();
    if (beforeRevision > 0) {
      settings.CHANGE_BEFORE = String.valueOf(beforeRevision);
      settings.USE_CHANGE_BEFORE_FILTER = true;
    }

    List<SvnChangeList> changeLists = getChangeLists(mergeContext, settings, beforeRevision, size, (changeList, tree) -> changeList);
    return Pair.create(
      ContainerUtil.getFirstItems(changeLists, size),
      changeLists.size() < size + 1);
  }

  public static int getBunchSize(int size) {
    Integer configuredSize = Integer.getInteger(PROP_BUNCH_SIZE);

    return configuredSize != null ? configuredSize : size > 0 ? size : BUNCH_SIZE;
  }

  private static @NotNull <T> List<T> getChangeLists(@NotNull MergeContext mergeContext,
                                                     @NotNull ChangeBrowserSettings settings,
                                                     long revisionToExclude,
                                                     int size,
                                                     @NotNull PairFunction<SvnChangeList, LogHierarchyNode, T> resultProvider) throws VcsException {
    List<T> result = new ArrayList<>();

    ((SvnCommittedChangesProvider)mergeContext.getVcs().getCommittedChangesProvider())
      .getCommittedChangesWithMergedRevisons(settings, new SvnRepositoryLocation(mergeContext.getSourceUrl()),
                                             size > 0 ? size + (revisionToExclude > 0 ? 2 : 1) : 0,
                                             (changeList, tree) -> {
                                               if (revisionToExclude != changeList.getNumber()) {
                                                 result.add(resultProvider.fun(changeList, tree));
                                               }
                                             });

    return result;
  }
}
