// 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 com.intellij.openapi.vcs.changes;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.util.BackgroundTaskUtil;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.*;
import com.intellij.openapi.vcs.changes.ui.RemoteStatusChangeNodeDecorator;
import com.intellij.openapi.vcs.impl.ProjectLevelVcsManagerImpl;
import com.intellij.openapi.vcs.impl.VcsInitObject;
import com.intellij.openapi.vcs.update.UpdateFilesHelper;
import com.intellij.openapi.vcs.update.UpdatedFiles;
import com.intellij.util.messages.MessageBusConnection;
import com.intellij.util.messages.Topic;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;

public class RemoteRevisionsCache implements VcsListener {
  private static final Logger LOG = Logger.getInstance(RemoteRevisionsCache.class);

  public static final Topic<Runnable> REMOTE_VERSION_CHANGED  = new Topic<>("REMOTE_VERSION_CHANGED", Runnable.class);
  public static final int DEFAULT_REFRESH_INTERVAL = 3 * 60 * 1000;

  private final RemoteRevisionsNumbersCache myRemoteRevisionsNumbersCache;
  private final RemoteRevisionsStateCache myRemoteRevisionsStateCache;

  private final ProjectLevelVcsManager myVcsManager;

  @NotNull private final RemoteStatusChangeNodeDecorator myChangeDecorator;
  private final Project myProject;
  private final Object myLock;
  private final Map<String, RemoteDifferenceStrategy> myKinds;
  private final ControlledCycle myControlledCycle;

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

  private RemoteRevisionsCache(final Project project) {
    myProject = project;
    myLock = new Object();

    myRemoteRevisionsNumbersCache = new RemoteRevisionsNumbersCache(myProject);
    myRemoteRevisionsStateCache = new RemoteRevisionsStateCache(myProject);

    myChangeDecorator = new RemoteStatusChangeNodeDecorator(this);

    ProjectLevelVcsManagerImpl vcsManager = ProjectLevelVcsManagerImpl.getInstanceImpl(project);
    myVcsManager = vcsManager;
    myKinds = new HashMap<>();

    final VcsConfiguration vcsConfiguration = VcsConfiguration.getInstance(myProject);
    myControlledCycle = new ControlledCycle(project, () -> {
      final boolean shouldBeDone = vcsConfiguration.isChangedOnServerEnabled() && myVcsManager.hasActiveVcss();

      if (shouldBeDone) {
        boolean somethingChanged = myRemoteRevisionsNumbersCache.updateStep();
        somethingChanged |= myRemoteRevisionsStateCache.updateStep();
        if (somethingChanged) {
          BackgroundTaskUtil.syncPublisher(myProject, REMOTE_VERSION_CHANGED).run();
        }
      }
      return shouldBeDone;
    }, "Finishing \"changed on server\" update", DEFAULT_REFRESH_INTERVAL);

    MessageBusConnection connection = myProject.getMessageBus().connect();
    connection.subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED, this);
    connection.subscribe(ProjectLevelVcsManager.VCS_CONFIGURATION_CHANGED_IN_PLUGIN, this);

    updateRoots();

    if ((!myProject.isDefault()) && vcsConfiguration.isChangedOnServerEnabled()) {
      vcsManager.addInitializationRequest(VcsInitObject.REMOTE_REVISIONS_CACHE,
                                          () -> {
                                            // do not start if there're no vcses
                                            if (!myVcsManager.hasActiveVcss() || !vcsConfiguration.isChangedOnServerEnabled()) return;
                                            myControlledCycle.startIfNotStarted();
                                          });
    }
  }

  public void updateAutomaticRefreshAlarmState(boolean remoteCacheStateChanged) {
    manageAlarm();
  }

  private void manageAlarm() {
    final VcsConfiguration vcsConfiguration = VcsConfiguration.getInstance(myProject);
    if ((! myProject.isDefault()) && myVcsManager.hasActiveVcss() && vcsConfiguration.isChangedOnServerEnabled()) {
      // will check whether is already started inside
      // interval is checked further, this is small and constant
      myControlledCycle.startIfNotStarted();
    } else {
      myControlledCycle.stop();
    }
  }

  private void updateRoots() {
    final VcsRoot[] roots = myVcsManager.getAllVcsRoots();
    synchronized (myLock) {
      for (VcsRoot root : roots) {
        final AbstractVcs vcs = root.getVcs();
        if (! myKinds.containsKey(vcs.getName())) {
          myKinds.put(vcs.getName(), vcs.getRemoteDifferenceStrategy());
        }
      }
    }
  }

  @Override
  public void directoryMappingChanged() {
    if (! VcsConfiguration.getInstance(myProject).isChangedOnServerEnabled()) {
      manageAlarm();
    } else {
      ApplicationManager.getApplication().executeOnPooledThread(() -> {
        try {
          updateRoots();
          myRemoteRevisionsNumbersCache.directoryMappingChanged();
          myRemoteRevisionsStateCache.directoryMappingChanged();
          manageAlarm();
        } catch (ProcessCanceledException ignore) {
        }
      });
    }
  }

  public void changeUpdated(@NotNull String path, @NotNull AbstractVcs vcs) {
    if (RemoteDifferenceStrategy.ASK_TREE_PROVIDER.equals(vcs.getRemoteDifferenceStrategy())) {
      myRemoteRevisionsStateCache.changeUpdated(path, vcs);
    }
    else {
      myRemoteRevisionsNumbersCache.changeUpdated(path, vcs);
    }
  }

  public void invalidate(final UpdatedFiles updatedFiles) {
    final Map<String, RemoteDifferenceStrategy> strategyMap;
    synchronized (myLock) {
      strategyMap = new HashMap<>(myKinds);
    }
    final Collection<String> newForTree = new ArrayList<>();
    final Collection<String> newForUsual = new ArrayList<>();
    UpdateFilesHelper.iterateAffectedFiles(updatedFiles, pair -> {
      final String vcsName = pair.getSecond();
      RemoteDifferenceStrategy strategy = strategyMap.get(vcsName);
      if (strategy == null) {
        final AbstractVcs vcs = myVcsManager.findVcsByName(vcsName);
        if (vcs == null) return;
        strategy = vcs.getRemoteDifferenceStrategy();
      }
      if (RemoteDifferenceStrategy.ASK_TREE_PROVIDER.equals(strategy)) {
        newForTree.add(pair.getFirst());
      } else {
        newForUsual.add(pair.getFirst());
      }
    });

    myRemoteRevisionsStateCache.invalidate(newForTree);
    myRemoteRevisionsNumbersCache.invalidate(newForUsual);
  }

  public void changeRemoved(@NotNull String path, @NotNull AbstractVcs vcs) {
    if (RemoteDifferenceStrategy.ASK_TREE_PROVIDER.equals(vcs.getRemoteDifferenceStrategy())) {
      myRemoteRevisionsStateCache.changeRemoved(path, vcs);
    }
    else {
      myRemoteRevisionsNumbersCache.changeRemoved(path, vcs);
    }
  }

  /**
   * @return false if not up to date
   */
  public boolean isUpToDate(@NotNull Change change) {
    if (myProject.isDisposed()) return true;
    final AbstractVcs vcs = ChangesUtil.getVcsForChange(change, myProject);
    if (vcs == null) return true;
    final RemoteDifferenceStrategy strategy = vcs.getRemoteDifferenceStrategy();
    if (RemoteDifferenceStrategy.ASK_TREE_PROVIDER.equals(strategy)) {
      return myRemoteRevisionsStateCache.isUpToDate(change, vcs);
    } else {
      return myRemoteRevisionsNumbersCache.isUpToDate(change, vcs);
    }
  }

  @NotNull
  public RemoteStatusChangeNodeDecorator getChangesNodeDecorator() {
    return myChangeDecorator;
  }
}

@@ -181,68 +181,8 @@
 s;%0A%0A
-import com.intellij.openapi.application.ApplicationManager;%0A
 impo
@@ -5078,43 +5078,26 @@
     
-ApplicationManager.getApplication()
+BackgroundTaskUtil
 .exe
@@ -5115,16 +5115,27 @@
 dThread(
+myProject, 
 () -%3E %7B%0A
@@ -5339,16 +5339,24 @@
        %7D
+%0A       
  catch (

abort background task on project dispose