// Copyright 2000-2018 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.history.integration.ui.models;

import com.intellij.history.core.LocalHistoryFacade;
import com.intellij.history.core.RevisionsCollector;
import com.intellij.history.core.revisions.Difference;
import com.intellij.history.core.revisions.Revision;
import com.intellij.history.core.tree.Entry;
import com.intellij.history.core.tree.RootEntry;
import com.intellij.history.integration.IdeaGateway;
import com.intellij.history.integration.patches.PatchCreator;
import com.intellij.history.integration.revertion.Reverter;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.VcsException;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public abstract class HistoryDialogModel {
  protected final Project myProject;
  protected LocalHistoryFacade myVcs;
  protected VirtualFile myFile;
  protected IdeaGateway myGateway;
  private String myFilter;
  private List<RevisionItem> myRevisionsCache;
  private Revision myCurrentRevisionCache;
  private int myRightRevisionIndex;
  private int myLeftRevisionIndex;
  private Entry[] myLeftEntryCache;
  private Entry[] myRightEntryCache;

  public HistoryDialogModel(Project p, IdeaGateway gw, LocalHistoryFacade vcs, VirtualFile f) {
    myProject = p;
    myVcs = vcs;
    myFile = f;
    myGateway = gw;
  }

  public String getTitle() {
    return FileUtil.toSystemDependentName(myFile.getPath());
  }


  public List<RevisionItem> getRevisions() {
    if (myRevisionsCache == null) {
      Pair<Revision, List<RevisionItem>> revs = calcRevisionsCache();
      myCurrentRevisionCache = revs.first;
      myRevisionsCache = revs.second;
    }
    return myRevisionsCache;
  }

  public Revision getCurrentRevision() {
    getRevisions();
    return myCurrentRevisionCache;
  }

  protected Pair<Revision, List<RevisionItem>> calcRevisionsCache() {
    return ReadAction.compute(() -> {
      myGateway.registerUnsavedDocuments(myVcs);
      String path = myFile.getPath();
      RootEntry root = myGateway.createTransientRootEntry();
      RevisionsCollector collector = new RevisionsCollector(myVcs, root, path, myProject.getLocationHash(), myFilter);

      List<Revision> all = collector.getResult();
      return Pair.create(all.get(0), groupRevisions(all.subList(1, all.size())));
    });
  }

  private static List<RevisionItem> groupRevisions(List<Revision> revs) {
    LinkedList<RevisionItem> result = new LinkedList<>();

    for (Revision each : ContainerUtil.iterateBackward(revs)) {
      if (each.isLabel() && !result.isEmpty()) {
        result.getFirst().labels.addFirst(each);
      } else {
        result.addFirst(new RevisionItem(each));
      }
    }

    return result;
  }

  public void setFilter(@Nullable String filter) {
    myFilter = StringUtil.isEmptyOrSpaces(filter) ? null : filter;
    clearRevisions();
  }

  public void clearRevisions() {
    myRevisionsCache = null;
    resetEntriesCache();
  }

  private void resetEntriesCache() {
    myLeftEntryCache = null;
    myRightEntryCache = null;
  }

  public Revision getLeftRevision() {
    if (getRevisions().isEmpty()) return getCurrentRevision();
    return getRevisions().get(myLeftRevisionIndex).revision;
  }

  public Revision getRightRevision() {
    if (isCurrentRevisionSelected() || getRevisions().isEmpty()) {
      return getCurrentRevision();
    }
    return getRevisions().get(myRightRevisionIndex).revision;
  }

  protected Entry getLeftEntry() {
    if (myLeftEntryCache == null) {
      // array is used because entry itself can be null
      myLeftEntryCache = new Entry[]{getLeftRevision().findEntry()};
    }
    return myLeftEntryCache[0];
  }

  protected Entry getRightEntry() {
    if (myRightEntryCache == null) {
      // array is used because entry itself can be null
      myRightEntryCache = new Entry[]{getRightRevision().findEntry()};
    }
    return myRightEntryCache[0];
  }

  public void selectRevisions(int first, int second) {
    if (first == second) {
      myRightRevisionIndex = -1;
      myLeftRevisionIndex = first == -1 ? 0 : first;
    }
    else {
      myRightRevisionIndex = first;
      myLeftRevisionIndex = second;
    }
    resetEntriesCache();
  }

  public void resetSelection() {
    selectRevisions(0, 0);
  }

  public boolean isCurrentRevisionSelected() {
    return myRightRevisionIndex == -1;
  }

  public List<Change> getChanges() {
    List<Difference> dd = getDifferences();

    List<Change> result = new ArrayList<>();
    for (Difference d : dd) {
      result.add(createChange(d));
    }

    return result;
  }

  protected List<Difference> getDifferences() {
    return getLeftRevision().getDifferencesWith(getRightRevision());
  }

  protected Change createChange(Difference d) {
    return new Change(d.getLeftContentRevision(myGateway), d.getRightContentRevision(myGateway));
  }

  public void createPatch(String path, String basePath, boolean isReverse, @NotNull Charset charset) throws VcsException, IOException {
    PatchCreator.create(myProject, basePath, getChanges(), path, isReverse, null, charset);
  }

  public abstract Reverter createReverter();

  public boolean isRevertEnabled() {
    return isCorrectSelectionForRevertAndPatch();
  }

  public boolean isCreatePatchEnabled() {
    return isCorrectSelectionForRevertAndPatch();
  }

  private boolean isCorrectSelectionForRevertAndPatch() {
    return myLeftRevisionIndex != -1;
  }

  public boolean canPerformCreatePatch() {
    return !getLeftEntry().hasUnavailableContent() && !getRightEntry().hasUnavailableContent();
  }
}
