package jetbrains.mps.vcs.diff.ui;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import com.intellij.openapi.ui.FrameWrapper;
import com.intellij.openapi.actionSystem.DataProvider;
import jetbrains.mps.project.MPSProject;
import com.intellij.diff.DiffRequestPanel;
import org.jetbrains.mps.openapi.model.SNodeId;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vcs.AbstractVcs;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import java.util.ArrayList;
import com.intellij.openapi.vcs.history.CurrentRevision;
import com.intellij.util.ui.ListTableModel;
import com.intellij.ui.table.TableView;
import com.intellij.openapi.ui.Splitter;
import javax.swing.JLabel;
import com.intellij.ui.components.JBLabel;
import com.intellij.util.ui.AnimatedIcon;
import com.intellij.util.ui.AsyncProcessIcon;
import javax.swing.JEditorPane;
import com.intellij.util.ui.update.MergingUpdateQueue;
import com.intellij.openapi.vcs.history.VcsHistorySession;
import com.intellij.openapi.vcs.history.VcsHistoryProvider;
import com.intellij.util.ObjectUtils;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.JBDimension;
import com.intellij.ui.BrowserHyperlinkListener;
import javax.swing.JRootPane;
import javax.swing.RootPaneContainer;
import com.intellij.openapi.vcs.history.VcsDependentHistoryComponents;
import com.intellij.util.ui.ColumnInfo;
import com.intellij.openapi.vcs.history.FileHistoryPanelImpl;
import com.intellij.util.ArrayUtil;
import com.intellij.openapi.vcs.changes.issueLinks.TableLinkMouseListener;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.diff.DiffManager;
import com.intellij.openapi.vcs.history.VcsRevisionNumber;
import com.intellij.ui.JBSplitter;
import javax.swing.event.ListSelectionListener;
import javax.swing.event.ListSelectionEvent;
import com.intellij.openapi.vcs.changes.issueLinks.IssueLinkHtmlRenderer;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.vcs.annotate.ShowAllAffectedGenericAction;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.vcs.VcsActions;
import com.intellij.ui.PopupHandler;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnAction;
import java.util.Collection;
import jetbrains.mps.vcs.history.CommitsGraph;
import com.intellij.openapi.progress.util.BackgroundTaskUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.openapi.vcs.changes.ui.ChangesViewContentManager;
import com.intellij.openapi.ui.MessageType;
import com.intellij.util.ui.update.Update;
import java.util.Collections;
import com.intellij.diff.requests.NoDiffRequest;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import com.intellij.diff.contents.DiffContent;
import jetbrains.mps.internal.collections.runtime.NotNullWhereFilter;
import com.intellij.diff.requests.SimpleDiffRequest;
import jetbrains.mps.vcs.platform.integration.ModelDiffViewer;
import com.intellij.diff.requests.LoadingDiffRequest;
import com.intellij.diff.requests.MessageDiffRequest;
import javax.swing.JComponent;
import javax.swing.JPanel;
import java.awt.BorderLayout;
import com.intellij.ui.ScrollPaneFactory;
import java.awt.FlowLayout;
import org.jetbrains.annotations.NonNls;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import jetbrains.mps.ide.actions.MPSCommonDataKeys;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import org.jetbrains.annotations.Nullable;
import com.intellij.diff.contents.EmptyContent;
import org.jetbrains.mps.openapi.model.SModel;
import jetbrains.mps.vcspersistence.VCSPersistenceUtil;
import jetbrains.mps.vfs.tracking.ModelDiffContent;
import com.intellij.openapi.util.Disposer;

/**
 * Most of this class copied from IDEA's VcsSelectionHistoryDialog
 */
@GeneratedClass(node = "r:df1b052a-af27-4b87-80fc-1492fa2192be(jetbrains.mps.vcs.diff.ui)/6427926084137200503", model = "r:df1b052a-af27-4b87-80fc-1492fa2192be(jetbrains.mps.vcs.diff.ui)")
public final class RootHistoryDialog extends FrameWrapper implements DataProvider {
  private final MPSProject myMPSProject;
  private final DiffRequestPanel myDiffPanel;
  private SNodeId myRoot;
  private final VirtualFile myActualFile;
  private final String myHelpId;
  private AbstractVcs myActiveVcs;

  private static final float DIFF_SPLITTER_PROPORTION = 0.5f;
  private static final float COMMENTS_SPLITTER_PROPORTION = 0.8f;
  private static final String DIFF_SPLITTER_PROPORTION_KEY = "file.history.selection.diff.splitter.proportion";
  private static final String COMMENTS_SPLITTER_PROPORTION_KEY = "file.history.selection.comments.splitter.proportion";

  @NotNull
  private final List<VcsFileRevision> myRevisions = new ArrayList<VcsFileRevision>();
  private final CurrentRevision myLocalRevision;

  private final ListTableModel<VcsFileRevision> myListModel;
  private final TableView<VcsFileRevision> myList;
  private final Splitter mySplitter;
  private final JLabel myStatusLabel = new JBLabel();
  private final AnimatedIcon myStatusSpinner = new AsyncProcessIcon(getClass().getSimpleName());
  private final JEditorPane myComments;
  private final MergingUpdateQueue myUpdateQueue;
  private RevisionsExtractor myRevisionsExtractor;
  private final boolean myCompareModels;
  private boolean myIsDuringUpdate = false;


  public RootHistoryDialog(MPSProject project, VirtualFile actualFile, AbstractVcs vcs, VcsHistorySession session, boolean compareModels) {
    super(project.getProject(), "MPS.RootHistoryDialog", false);
    // no idea why VcsSelectionHistoryDialog uses isDialog == false
    myMPSProject = project;
    myActualFile = actualFile;
    myActiveVcs = vcs;
    myCompareModels = compareModels;
    VcsHistoryProvider vcsHistoryProvider = vcs.getVcsHistoryProvider();

    // copied from VcsSelectionHistoryDialog
    myHelpId = ObjectUtils.notNull(vcsHistoryProvider.getHelpId(), "reference.dialogs.vcs.selection.history");
    myComments = new JEditorPane(UIUtil.HTML_MIME, "");
    myComments.setPreferredSize(new JBDimension(150, 100));
    myComments.setEditable(false);
    myComments.addHyperlinkListener(BrowserHyperlinkListener.INSTANCE);
    JRootPane rootPane = ((RootPaneContainer) getFrame()).getRootPane();
    final VcsDependentHistoryComponents components = vcsHistoryProvider.getUICustomization(session, rootPane);
    ColumnInfo[] defaultColumns = new ColumnInfo[]{new FileHistoryPanelImpl.RevisionColumnInfo(null), new FileHistoryPanelImpl.DateColumnInfo(), new FileHistoryPanelImpl.AuthorColumnInfo(), new FileHistoryPanelImpl.MessageColumnInfo(project.getProject())};
    ColumnInfo[] additionalColumns = ObjectUtils.notNull(components.getColumns(), ColumnInfo.EMPTY_ARRAY);
    myListModel = new ListTableModel(ArrayUtil.mergeArrays(defaultColumns, additionalColumns));
    myListModel.setSortable(false);
    myList = new TableView(myListModel);
    new TableLinkMouseListener().installOn(myList);
    myList.getEmptyText().setText(VcsBundle.message("history.empty"));

    myDiffPanel = DiffManager.getInstance().createRequestPanel(project.getProject(), this, getFrame());
    myUpdateQueue = new MergingUpdateQueue(getClass().getSimpleName(), 300, true, myList, this);


    myLocalRevision = new CurrentRevision(actualFile, new VcsRevisionNumber() {
      @Override
      public int compareTo(VcsRevisionNumber p0) {
        return 0;
      }

      @NotNull
      @Override
      public String asString() {
        return "Local Changes";
      }
    });
    myRevisions.add(myLocalRevision);
    myRevisions.addAll(session.getRevisionList());

    mySplitter = new JBSplitter(true, DIFF_SPLITTER_PROPORTION_KEY, DIFF_SPLITTER_PROPORTION);
    mySplitter.setFirstComponent(myDiffPanel.getComponent());
    mySplitter.setSecondComponent(createBottomPanel(components.getDetailsComponent()));

    final ListSelectionListener selectionListener = new ListSelectionListener() {
      @Override
      public void valueChanged(ListSelectionEvent e) {
        final VcsFileRevision revision;
        if (myList.getSelectedRowCount() == 1 && !(myList.isEmpty())) {
          revision = myList.getItems().get(myList.getSelectedRow());
          String message = IssueLinkHtmlRenderer.formatTextIntoHtml(myMPSProject.getProject(), revision.getCommitMessage());
          myComments.setText(message);
          myComments.setCaretPosition(0);
        } else {
          revision = null;
          myComments.setText("");
        }
        if (components.getRevisionListener() != null) {
          components.getRevisionListener().consume(revision);
        }
        if (!(e.getValueIsAdjusting())) {
          updateDiff();
        }
      }
    };
    myList.getSelectionModel().addListSelectionListener(selectionListener);
    final DefaultActionGroup popupActions = new DefaultActionGroup();
    popupActions.add(ShowAllAffectedGenericAction.getInstance());
    popupActions.add(ActionManager.getInstance().getAction(VcsActions.ACTION_COPY_REVISION_NUMBER));
    PopupHandler.installPopupHandler(myList, popupActions, ActionPlaces.UPDATE_POPUP, ActionManager.getInstance());
    for (AnAction action : popupActions.getChildren(null)) {
      action.registerCustomShortcutSet(action.getShortcutSet(), mySplitter);
    }

    setTitle(myActualFile.getName());
    setComponent(mySplitter);
    setPreferredFocusedComponent(myList);
    closeOnEsc();
    setOnCloseHandler(() -> check_s4rg5p_a0a0a84a72(myRevisionsExtractor));
  }

  public void show(Collection<SNodeId> selection) {
    // for the moment, I support single node scenario. Moreover, root node, as DiffModelViewer (along with ChangeSet) is incapable to show changes for anything but root
    myRoot = selection.iterator().next();
    try {
      myRevisionsExtractor = createHistoryExtractor();
    } catch (CommitsGraph.BuildException e) {
      showWarning("Root history is not available until indices are built");
      return;
    }
    BackgroundTaskUtil.executeOnPooledThread(this, myRevisionsExtractor);
    updateRevisionList();
    show();
  }

  private void showWarning(final String warning) {
    ApplicationManager.getApplication().invokeLater(() -> ToolWindowManager.getInstance(myActiveVcs.getProject()).notifyByBalloon(ChangesViewContentManager.TOOLWINDOW_ID, MessageType.WARNING, warning));
  }

  private RevisionsExtractor createHistoryExtractor() throws CommitsGraph.BuildException {
    if (myCompareModels) {
      return new RootModelHistoryExtractor(myMPSProject, myRevisions, myRoot, myActualFile, getUpdateListener());
    } else {
      return new RootFileHistoryExtractor(myRevisions, myRoot, getUpdateListener());
    }
  }

  private Runnable getUpdateListener() {
    return () -> {
      myUpdateQueue.queue(new Update(this) {
        @Override
        public void run() {
          updateStatusLine();
          updateRevisionList();
        }
      });
    };
  }

  /*package*/ void updateStatusLine() {
    if (myRevisionsExtractor.isLoading()) {
      myStatusLabel.setText(String.format("Analyzing revision %d/%d ...", myRevisionsExtractor.getProcessedRevisionsNumber(), myRevisionsExtractor.getTotalRevisionsNumber()));
      myStatusSpinner.resume();
      myStatusSpinner.setVisible(true);
    } else {
      myStatusLabel.setText("");
      myStatusSpinner.suspend();
      myStatusSpinner.setVisible(false);
    }
  }

  private void updateRevisionList() {
    try {
      if (myIsDuringUpdate) {
        return;
      }
      myIsDuringUpdate = true;
      if (myListModel.getItems().size() == myRevisionsExtractor.getRevisions().size()) {
        return;
      }

      final List<VcsFileRevision> oldSelection = myList.getSelectedObjects();
      myListModel.setItems(myRevisionsExtractor.getRevisions());

      if (oldSelection.isEmpty()) {
        if (myListModel.getRowCount() > 0) {
          myList.setSelection(Collections.singleton(myListModel.getItem(0)));
        }
      } else {
        myList.setSelection(oldSelection);
      }
    } finally {
      myIsDuringUpdate = false;
    }
    updateDiff();
  }


  private void updateDiff() {
    if (isDisposed() || myIsDuringUpdate) {
      return;
    }
    if (myList.getSelectedRowCount() == 0) {
      myDiffPanel.setRequest(NoDiffRequest.INSTANCE);
      return;
    }
    List<VcsFileRevision> selection = myList.getSelectedObjects();
    VcsFileRevision revision = ListSequence.fromList(selection).first();
    List<VcsFileRevision> parents = myRevisionsExtractor.getRevisionParents(revision);
    VcsFileRevision oldRevision1 = (ListSequence.fromList(parents).count() > 0 ? ListSequence.fromList(parents).getElement(0) : null);
    List<DiffContent> contents = ListSequence.fromList(new ArrayList<DiffContent>());
    List<String> titles = ListSequence.fromList(new ArrayList<String>());
    ListSequence.fromList(contents).addElement(createDiffContent(oldRevision1));
    ListSequence.fromList(titles).addElement(createTitle(oldRevision1));
    ListSequence.fromList(contents).addElement(createDiffContent(revision));
    ListSequence.fromList(titles).addElement(createTitle(revision));
    if (ListSequence.fromList(parents).count() == 2) {
      VcsFileRevision oldRevision2 = ListSequence.fromList(parents).getElement(1);
      ListSequence.fromList(contents).addElement(createDiffContent(oldRevision2));
      ListSequence.fromList(titles).addElement(createTitle(oldRevision2));
    }
    if (ListSequence.fromList(contents).count() == ListSequence.fromList(contents).where(new NotNullWhereFilter()).count()) {
      SimpleDiffRequest rq = new SimpleDiffRequest(null, contents, titles);
      ModelDiffViewer.DIFF_SHOW_ROOTID.set(rq, myRoot);
      ModelDiffViewer.DIFF_SHOW_TREE.set(rq, false);
      myDiffPanel.setRequest(rq, titles);
      return;
    }
    if (myRevisionsExtractor.isLoading()) {
      myDiffPanel.setRequest(new LoadingDiffRequest());
    } else {
      // FIXME throw an exception from createDiffContent, catch it here and present to user through MessageDiffRequest (there's no DiffContent that could do that instead)
      myDiffPanel.setRequest(new MessageDiffRequest("Error", "Failed to build diff for selected revisions"));
    }
  }

  private JComponent createBottomPanel(final JComponent addComp) {
    JBSplitter splitter = new JBSplitter(true, COMMENTS_SPLITTER_PROPORTION_KEY, COMMENTS_SPLITTER_PROPORTION);
    splitter.setDividerWidth(4);

    JPanel tablePanel = new JPanel(new BorderLayout());
    tablePanel.add(ScrollPaneFactory.createScrollPane(myList), BorderLayout.CENTER);

    JPanel statusPanel = new JPanel(new FlowLayout());
    statusPanel.add(myStatusSpinner);
    statusPanel.add(myStatusLabel);

    JPanel separatorPanel = new JPanel(new BorderLayout());
    separatorPanel.add(statusPanel, BorderLayout.EAST);
    tablePanel.add(separatorPanel, BorderLayout.NORTH);

    splitter.setFirstComponent(tablePanel);
    splitter.setSecondComponent(createComments(addComp));
    return splitter;
  }

  private JComponent createComments(final JComponent addComp) {
    JPanel panel = new JPanel(new BorderLayout(4, 4));
    panel.add(new JLabel("Commit Message:"), BorderLayout.NORTH);
    panel.add(ScrollPaneFactory.createScrollPane(myComments), BorderLayout.CENTER);
    final Splitter splitter = new Splitter(false);
    splitter.setFirstComponent(panel);
    splitter.setSecondComponent(addComp);
    return splitter;
  }

  @Override
  public Object getData(@NotNull @NonNls String dataId) {
    if (CommonDataKeys.PROJECT.is(dataId)) {
      return myMPSProject.getProject();
    } else if (MPSCommonDataKeys.MPS_PROJECT.is(dataId)) {
      return myMPSProject;
    } else if (VcsDataKeys.VCS.is(dataId)) {
      return myActiveVcs.getKeyInstanceMethod();
    } else
    if (VcsDataKeys.VCS_VIRTUAL_FILE.is(dataId)) {
      return myActualFile;
    } else
    if (VcsDataKeys.VCS_FILE_REVISION.is(dataId)) {
      VcsFileRevision selectedObject = myList.getSelectedObject();
      return (selectedObject instanceof CurrentRevision ? null : selectedObject);
    } else
    if (VcsDataKeys.VCS_FILE_REVISIONS.is(dataId)) {
      List<VcsFileRevision> revisions = ContainerUtil.filter(myList.getSelectedObjects(), Conditions.notEqualTo(myLocalRevision));
      return revisions.toArray(new VcsFileRevision[0]);
    } else
    if (PlatformDataKeys.HELP_ID.is(dataId)) {
      return myHelpId;
    }
    return null;
  }

  @Nullable
  private String createTitle(@Nullable VcsFileRevision revision) {
    return (revision == null ? null : revision.getRevisionNumber().asString());
  }

  @Nullable
  private DiffContent createDiffContent(@Nullable VcsFileRevision revision) {
    if (revision == null) {
      return new EmptyContent();
    }
    SModel loaded;
    try {
      loaded = VCSPersistenceUtil.loadModel(revision.loadContent(), myActualFile.getExtension());
    } catch (Exception ex) {
      return null;
    }
    // ModelDiffViewer doesn't tolerate reusable detached models, it registers and disposes such models solely on its own discretion
    return (loaded == null ? new EmptyContent() : new ModelDiffContent(loaded, myActualFile.getFileType()));
  }

  @Override
  public void dispose() {
    Disposer.dispose(myDiffPanel);
    super.dispose();
  }
  private static boolean check_s4rg5p_a0a0a84a72(RevisionsExtractor checkedDotOperand) {
    if (null != checkedDotOperand) {
      return checkedDotOperand.stop();
    }
    return false;
  }
}
