/*
 * 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 com.google.common.collect.Sets;
import consulo.ide.impl.idea.openapi.util.Comparing;
import consulo.util.lang.Pair;
import consulo.versionControlSystem.FilePath;
import consulo.versionControlSystem.change.BaseRevision;
import consulo.versionControlSystem.change.Change;
import consulo.versionControlSystem.change.ContentRevision;
import consulo.virtualFileSystem.status.FileStatus;
import consulo.versionControlSystem.VcsKey;
import consulo.versionControlSystem.history.VcsRevisionNumber;
import consulo.virtualFileSystem.VirtualFile;
import consulo.ide.impl.idea.util.BeforeAfter;
import consulo.versionControlSystem.util.VcsUtil;
import consulo.logging.Logger;

import jakarta.annotation.Nonnull;
import jakarta.annotation.Nullable;

import java.util.*;

import static consulo.ide.impl.idea.util.containers.ContainerUtil.newHashSet;

public class ChangeListsIndexes {
  private static final Logger LOG = Logger.getInstance(ChangeListsIndexes.class);
  private final TreeMap<FilePath, FileStatus> myFileToStatus;
  private final Map<FilePath, Pair<VcsKey, VcsRevisionNumber>> myFileToVcs;

  ChangeListsIndexes() {
    myFileToStatus = new TreeMap<>(HierarchicalFilePathComparator.SYSTEM_CASE_SENSITIVE);
    myFileToVcs = new HashMap<>();
  }

  ChangeListsIndexes(final ChangeListsIndexes idx) {
    myFileToStatus = new TreeMap<>(idx.myFileToStatus);
    myFileToVcs = new HashMap<>(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));
    if (LOG.isDebugEnabled()) {
      LOG.debug("Set status " + status + " for " + file);
    }
  }

  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(@Nonnull 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());
    }
  }

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

  @Nullable
  private VcsKey getVcsForRevision(@jakarta.annotation.Nullable ContentRevision revision) {
    if (revision != null) {
      Pair<VcsKey, VcsRevisionNumber> pair = myFileToVcs.get(revision.getFile());
      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<>(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<>();
    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;
  }

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