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

import com.google.common.collect.Sets;
import com.intellij.openapi.util.Comparing;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.FileStatus;
import com.intellij.openapi.vcs.VcsKey;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.BeforeAfter;
import com.intellij.vcsUtil.VcsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.*;

import static com.intellij.util.containers.ContainerUtil.newHashSet;

public class ChangeListsIndexes {
  private final TreeMap<FilePath, FileStatus> myFileToStatus;
  private final Map<FilePath, Pair<VcsKey, VcsRevisionNumber>> myFileToVcs;

  ChangeListsIndexes() {
    myFileToStatus = new TreeMap<FilePath, FileStatus>(HierarchicalFilePathComparator.SYSTEM_CASE_SENSITIVE);
    myFileToVcs = new HashMap<FilePath, Pair<VcsKey, VcsRevisionNumber>>();
  }

  ChangeListsIndexes(final ChangeListsIndexes idx) {
    myFileToStatus = new TreeMap<FilePath, FileStatus>(idx.myFileToStatus);
    myFileToVcs = new HashMap<FilePath, Pair<VcsKey, VcsRevisionNumber>>(idx.myFileToVcs);
  }

  void add(final FilePath file, final FileStatus status, final VcsKey key, VcsRevisionNumber number) {
    myFileToStatus.put(file, status);
    myFileToVcs.put(file, Pair.create(key, number));
  }

  void remove(final FilePath file) {
    myFileToStatus.remove(file);
    myFileToVcs.remove(file);
  }

  public FileStatus getStatus(final VirtualFile file) {
    return myFileToStatus.get(VcsUtil.getFilePath(file));
  }
  
  public FileStatus getStatus(@NotNull FilePath file) {
    return myFileToStatus.get(file);
  }

  public void changeAdded(final Change change, final VcsKey key) {
    addChangeToIdx(change, key);
  }

  public void changeRemoved(final Change change) {
    final ContentRevision afterRevision = change.getAfterRevision();
    final ContentRevision beforeRevision = change.getBeforeRevision();

    if (afterRevision != null) {
      remove(afterRevision.getFile());
    }
    if (beforeRevision != null) {
      remove(beforeRevision.getFile());
    }
  }

  public VcsKey getVcsFor(final Change change) {
    VcsKey key = getVcsForRevision(change.getAfterRevision());
    if (key != null) return key;
    return getVcsForRevision(change.getBeforeRevision());
  }

  @Nullable
  private VcsKey getVcsForRevision(final ContentRevision revision) {
    if (revision != null) {
      final FilePath fileKey = revision.getFile();
      final Pair<VcsKey, VcsRevisionNumber> pair = myFileToVcs.get(fileKey);
      return pair == null ? null : pair.getFirst();
    }
    return null;
  }

  private void addChangeToIdx(final Change change, final VcsKey key) {
    final ContentRevision afterRevision = change.getAfterRevision();
    final ContentRevision beforeRevision = change.getBeforeRevision();
    if (afterRevision != null) {
      add(afterRevision.getFile(), change.getFileStatus(), key, beforeRevision == null ? VcsRevisionNumber.NULL : beforeRevision.getRevisionNumber());
    }
    if (beforeRevision != null) {
      if (afterRevision != null) {
        if (! Comparing.equal(beforeRevision.getFile(), afterRevision.getFile())) {
          add(beforeRevision.getFile(), FileStatus.DELETED, key, beforeRevision.getRevisionNumber());
        }
      } else {
        add(beforeRevision.getFile(), change.getFileStatus(), key, beforeRevision.getRevisionNumber());
      }
    }
  }

  /**
   * this method is called after each local changes refresh and collects all:
   * - paths that are new in local changes
   * - paths that are no more changed locally
   * - paths that were and are changed, but base revision has changed (ex. external update)
   * (for RemoteRevisionsCache and annotation listener)
   */
  public void getDelta(final ChangeListsIndexes newIndexes,
                       final Set<BaseRevision> toRemove,
                       Set<BaseRevision> toAdd,
                       Set<BeforeAfter<BaseRevision>> toModify) {
    // this is old
    final Set<FilePath> oldKeySet = newHashSet(myFileToVcs.keySet());
    final Set<FilePath> toRemoveSet = newHashSet(oldKeySet);
    final Set<FilePath> newKeySet = newIndexes.myFileToVcs.keySet();
    final Set<FilePath> toAddSet = newHashSet(newKeySet);
    toRemoveSet.removeAll(newKeySet);
    toAddSet.removeAll(oldKeySet);
    // those that modified
    oldKeySet.removeAll(toRemoveSet);

    for (FilePath s : toRemoveSet) {
      final Pair<VcsKey, VcsRevisionNumber> pair = myFileToVcs.get(s);
      toRemove.add(fromPairAndPath(s, pair));
    }
    for (FilePath s : toAddSet) {
      final Pair<VcsKey, VcsRevisionNumber> pair = newIndexes.myFileToVcs.get(s);
      toAdd.add(fromPairAndPath(s, pair));
    }
    for (FilePath s : oldKeySet) {
      final Pair<VcsKey, VcsRevisionNumber> old = myFileToVcs.get(s);
      final Pair<VcsKey, VcsRevisionNumber> newOne = newIndexes.myFileToVcs.get(s);
      assert old != null && newOne != null;
      if (! old.equals(newOne)) {
        toModify.add(new BeforeAfter<BaseRevision>(fromPairAndPath(s, old), fromPairAndPath(s, newOne)));
      }
    }
  }

  private static BaseRevision fromPairAndPath(FilePath s, Pair<VcsKey, VcsRevisionNumber> pair) {
    return new BaseRevision(pair.getFirst(), pair.getSecond(), s);
  }

  public List<BaseRevision> getAffectedFilesUnderVcs() {
    final List<BaseRevision> result = new ArrayList<BaseRevision>();
    for (Map.Entry<FilePath, Pair<VcsKey, VcsRevisionNumber>> entry : myFileToVcs.entrySet()) {
      final Pair<VcsKey, VcsRevisionNumber> value = entry.getValue();
      result.add(fromPairAndPath(entry.getKey(), value));
    }
    return result;
  }

  @NotNull
  public NavigableSet<FilePath> getAffectedPaths() {
    return Sets.unmodifiableNavigableSet(myFileToStatus.navigableKeySet());
  }
}

@@ -676,16 +676,63 @@
 t.Sets;%0A
+import com.intellij.openapi.diagnostic.Logger;%0A
 import c
@@ -767,16 +767,16 @@
 paring;%0A
-
 import c
@@ -1322,16 +1322,128 @@
 dexes %7B%0A
+  private static final Logger LOG = Logger.getInstance(%22#com.intellij.openapi.vcs.changes.ChangeListsIndexes%22);%0A
   privat
@@ -2147,32 +2147,32 @@
 (file, status);%0A
-
     myFileToVcs.
@@ -2204,24 +2204,120 @@
 , number));%0A
+    if (LOG.isDebugEnabled()) %7B%0A      LOG.debug(%22Set status %22 + status + %22 for %22 + file);%0A    %7D%0A
   %7D%0A%0A  void 

debug logging for PerforceChangeProviderTest.testBrokenSymlink