/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package consulo.ide.impl.idea.openapi.vcs.changes;

import consulo.project.Project;
import consulo.util.lang.Pair;
import consulo.versionControlSystem.*;
import consulo.versionControlSystem.base.FilePathImpl;
import consulo.versionControlSystem.change.Change;
import consulo.versionControlSystem.change.ChangesUtil;
import consulo.versionControlSystem.root.VcsRoot;
import consulo.virtualFileSystem.VirtualFile;
import consulo.util.collection.MultiMap;
import jakarta.annotation.Nullable;

import java.io.File;
import java.util.*;

public class RemoteRevisionsStateCache implements ChangesOnServerTracker {
  private final static long DISCRETE = 3600000;
  // true -> changed
  private final Map<String, Pair<Boolean, VcsRoot>> myChanged;

  private final MultiMap<VcsRoot, String> myQueries;
  private final Map<VcsRoot, Long> myTs;
  private final Object myLock;
  private final ProjectLevelVcsManager myVcsManager;
  private final VcsConfiguration myVcsConfiguration;

  RemoteRevisionsStateCache(final Project project) {
    myVcsManager = ProjectLevelVcsManager.getInstance(project);
    myChanged = new HashMap<String, Pair<Boolean, VcsRoot>>();
    myQueries = new MultiMap<VcsRoot, String>();
    myTs = new HashMap<VcsRoot, Long>();
    myLock = new Object();
    myVcsConfiguration = VcsConfiguration.getInstance(project);
  }

  public void invalidate(final Collection<String> paths) {
    synchronized (myLock) {
      for (String path : paths) {
        myChanged.remove(path);
      }
    }
  }

  @Nullable
  private VirtualFile getRootForPath(final String s) {
    return myVcsManager.getVcsRootFor(new FilePathImpl(new File(s), false));
  }
  
  public boolean isUpToDate(final Change change) {
    final List<File> files = ChangesUtil.getIoFilesFromChanges(Collections.singletonList(change));
    synchronized (myLock) {
      for (File file : files) {
        final String path = file.getAbsolutePath();
        final Pair<Boolean, VcsRoot> data = myChanged.get(path);
        if (data != null && Boolean.TRUE.equals(data.getFirst())) return false;
      }
    }
    return true;
  }

  public void plus(final Pair<String, AbstractVcs> pair) {
    final VirtualFile root = getRootForPath(pair.getFirst());
    if (root == null) return;
    synchronized (myLock) {
      myQueries.putValue(new VcsRoot(pair.getSecond(), root), pair.getFirst());
    }
  }

  public void minus(Pair<String, AbstractVcs> pair) {
    final VirtualFile root = getRootForPath(pair.getFirst());
    if (root == null) return;
    synchronized (myLock) {
      final VcsRoot key = new VcsRoot(pair.getSecond(), root);
      if (myQueries.containsKey(key)) {
        myQueries.removeValue(key, pair.getFirst());
      }
      myChanged.remove(pair.getFirst());
    }
  }

  public void directoryMappingChanged() {
    // todo will work?
    synchronized (myLock) {
      myChanged.clear();
      myTs.clear();
    }
  }

  public boolean updateStep() {
    final MultiMap<VcsRoot, String> dirty = new MultiMap<VcsRoot, String>();
    final long oldPoint = System.currentTimeMillis() - (myVcsConfiguration.CHANGED_ON_SERVER_INTERVAL > 0 ?
                                                        myVcsConfiguration.CHANGED_ON_SERVER_INTERVAL * 60000 : DISCRETE);

    synchronized (myLock) {
      for (VcsRoot root : myQueries.keySet()) {
        final Collection<String> collection = myQueries.get(root);
        for (String s : collection) {
          dirty.putValue(root, s);
        }
      }
      myQueries.clear();

      final Set<VcsRoot> roots = new HashSet<VcsRoot>();
      for (Map.Entry<VcsRoot, Long> entry : myTs.entrySet()) {
        if (! dirty.get(entry.getKey()).isEmpty()) continue;

        final Long ts = entry.getValue();
        if ((ts == null) || (oldPoint > ts)) {
          roots.add(entry.getKey());
        }
      }
      for (Map.Entry<String, Pair<Boolean, VcsRoot>> entry : myChanged.entrySet()) {
        final VcsRoot vcsRoot = entry.getValue().getSecond();
        if ((! dirty.get(vcsRoot).isEmpty()) || roots.contains(vcsRoot)) {
          dirty.putValue(vcsRoot, entry.getKey());
        }
      }
    }

    if (dirty.isEmpty()) return false;

    final Map<String, Pair<Boolean, VcsRoot>> results = new HashMap<String, Pair<Boolean, VcsRoot>>();
    for (VcsRoot vcsRoot : dirty.keySet()) {
      // todo - actually it means nothing since the only known VCS to use this scheme is Git and now it always allow
      // todo - background operations. when it changes, develop more flexible behavior here
      if (! vcsRoot.getVcs().isVcsBackgroundOperationsAllowed(vcsRoot.getPath())) continue;
      final TreeDiffProvider provider = vcsRoot.getVcs().getTreeDiffProvider();
      if (provider == null) continue;

      final Collection<String> paths = dirty.get(vcsRoot);
      final Collection<String> remotelyChanged = provider.getRemotelyChanged(vcsRoot.getPath(), paths);
      for (String path : paths) {
        results.put(path, new Pair<Boolean, VcsRoot>(remotelyChanged.contains(path), vcsRoot));
      }
    }

    final long curTime = System.currentTimeMillis();
    synchronized (myLock) {
      myChanged.putAll(results);
      for (VcsRoot vcsRoot : dirty.keySet()) {
        myTs.put(vcsRoot, curTime);
      }
    }

    return true;
  }
}
