/*
 * Copyright 2000-2014 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.actions;

import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
import com.intellij.openapi.vcs.AbstractVcs;
import com.intellij.openapi.vcs.ProjectLevelVcsManager;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.diff.DiffProvider;
import com.intellij.openapi.vcs.history.HistoryAsTreeProvider;
import com.intellij.openapi.vcs.history.VcsFileRevision;
import com.intellij.openapi.vcs.history.VcsHistoryProvider;
import com.intellij.openapi.vcs.history.VcsHistoryProviderBackgroundableProxy;
import com.intellij.openapi.vcs.impl.VcsBackgroundableActions;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.*;
import com.intellij.ui.dualView.TreeTableView;
import com.intellij.ui.table.TableView;
import com.intellij.ui.treeStructure.treetable.ListTreeTableModelOnColumns;
import com.intellij.util.Consumer;
import com.intellij.util.TreeItem;
import com.intellij.util.text.DateFormatUtil;
import com.intellij.util.ui.ColumnInfo;
import com.intellij.util.ui.ListTableModel;
import com.intellij.vcsUtil.VcsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import java.awt.*;
import java.util.Date;
import java.util.List;

public class CompareWithSelectedRevisionAction extends AbstractVcsAction {

  private static final ColumnInfo<TreeNodeAdapter,String> BRANCH_COLUMN = new ColumnInfo<TreeNodeAdapter, String>(VcsBundle.message("column.name.revisions.list.branch")){
    @Override
    public String valueOf(final TreeNodeAdapter object) {
      return object.getRevision().getBranchName();
    }
  };

  private static final ColumnInfo<TreeNodeAdapter,String> REVISION_COLUMN = new ColumnInfo<TreeNodeAdapter, String>(VcsBundle.message("column.name.revision.list.revision")){
    @Override
    public String valueOf(final TreeNodeAdapter object) {
      return object.getRevision().getRevisionNumber().asString();
    }
  };

  private static final ColumnInfo<TreeNodeAdapter,String> DATE_COLUMN = new ColumnInfo<TreeNodeAdapter, String>(VcsBundle.message("column.name.revisions.list.filter")){
    @Override
    public String valueOf(final TreeNodeAdapter object) {
      return DateFormatUtil.formatPrettyDateTime(object.getRevision().getRevisionDate());
    }
  };

  private static final ColumnInfo<TreeNodeAdapter,String> AUTHOR_COLUMN = new ColumnInfo<TreeNodeAdapter, String>(VcsBundle.message("column.name.revision.list.author")){
    @Override
    public String valueOf(final TreeNodeAdapter object) {
      return object.getRevision().getAuthor();
    }
  };

  private static final ColumnInfo<VcsFileRevision, String> REVISION_TABLE_COLUMN = new ColumnInfo<VcsFileRevision, String>(VcsBundle.message("column.name.revision.list.revision")) {
    @Override
    public String valueOf(final VcsFileRevision vcsFileRevision) {
      return vcsFileRevision.getRevisionNumber().asString();
    }
  };

  private static final ColumnInfo<VcsFileRevision, String> DATE_TABLE_COLUMN = new ColumnInfo<VcsFileRevision, String>(VcsBundle.message("column.name.revision.list.revision")) {
    @Override
    public String valueOf(final VcsFileRevision vcsFileRevision) {
      final Date date = vcsFileRevision.getRevisionDate();
      return date == null ? "" : DateFormatUtil.formatPrettyDateTime(date);
    }
  };

  private static final ColumnInfo<VcsFileRevision,String> AUTHOR_TABLE_COLUMN = new ColumnInfo<VcsFileRevision, String>(VcsBundle.message("column.name.revision.list.author")){
    @Override
    public String valueOf(final VcsFileRevision vcsFileRevision) {
      return vcsFileRevision.getAuthor();
    }
  };

  private static final ColumnInfo<VcsFileRevision,String> BRANCH_TABLE_COLUMN = new ColumnInfo<VcsFileRevision, String>(VcsBundle.message("column.name.revisions.list.branch")){
    @Override
    public String valueOf(final VcsFileRevision vcsFileRevision) {
      return vcsFileRevision.getBranchName();
    }
  };

  @Override
  public void update(@NotNull VcsContext e, @NotNull Presentation presentation) {
    AbstractShowDiffAction.updateDiffAction(presentation, e, VcsBackgroundableActions.COMPARE_WITH);
  }


  @Override
  protected void actionPerformed(@NotNull VcsContext vcsContext) {
    final VirtualFile file = vcsContext.getSelectedFiles()[0];
    final Project project = vcsContext.getProject();
    final AbstractVcs vcs = ProjectLevelVcsManager.getInstance(project).getVcsFor(file);
    final VcsHistoryProvider vcsHistoryProvider = vcs.getVcsHistoryProvider();

    new VcsHistoryProviderBackgroundableProxy(vcs, vcsHistoryProvider, vcs.getDiffProvider()).
      createSessionFor(vcs.getKeyInstanceMethod(), VcsUtil.getFilePath(file),
                       session -> {
                         if (session == null) return;
                         final List<VcsFileRevision> revisions = session.getRevisionList();
                         final HistoryAsTreeProvider treeHistoryProvider = session.getHistoryAsTreeProvider();
                         if (treeHistoryProvider != null) {
                           showTreePopup(treeHistoryProvider.createTreeOn(revisions), file, project, vcs.getDiffProvider());
                         }
                         else {
                           showListPopup(revisions, project,
                                         revision -> DiffActionExecutor.showDiff(vcs.getDiffProvider(), revision.getRevisionNumber(), file, project,
                                                                                           VcsBackgroundableActions.COMPARE_WITH), true);
                         }
                       }, VcsBackgroundableActions.COMPARE_WITH, false, null);
  }

  private static void showTreePopup(final List<TreeItem<VcsFileRevision>> roots, final VirtualFile file, final Project project, final DiffProvider diffProvider) {
    final TreeTableView treeTable = new TreeTableView(new ListTreeTableModelOnColumns(new TreeNodeAdapter(null, null, roots),
                                                                                      new ColumnInfo[]{BRANCH_COLUMN, REVISION_COLUMN,
                                                                                      DATE_COLUMN, AUTHOR_COLUMN}));
    Runnable runnable = () -> {
      int index = treeTable.getSelectionModel().getMinSelectionIndex();
      if (index == -1) {
        return;
      }
      VcsFileRevision revision = getRevisionAt(treeTable, index);
      if (revision != null) {
        DiffActionExecutor.showDiff(diffProvider, revision.getRevisionNumber(), file, project, VcsBackgroundableActions.COMPARE_WITH);
      }
    };

    treeTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

    new PopupChooserBuilder(treeTable).
      setTitle(VcsBundle.message("lookup.title.vcs.file.revisions")).
      setItemChoosenCallback(runnable).
      setSouthComponent(createCommentsPanel(treeTable)).
      setResizable(true).
      setDimensionServiceKey("Vcs.CompareWithSelectedRevision.Popup").
      createPopup().
      showCenteredInCurrentWindow(project);

    final int lastRow = treeTable.getRowCount() - 1;
    if (lastRow < 0) return;
    treeTable.getSelectionModel().addSelectionInterval(lastRow, lastRow);
    treeTable.scrollRectToVisible(treeTable.getCellRect(lastRow, 0, true));
  }


  @Nullable private static VcsFileRevision getRevisionAt(final TreeTableView treeTable, final int index) {
    final List items = treeTable.getItems();
    if (items.size() <= index) {
      return null;
    } else {
      return ((TreeNodeAdapter)items.get(index)).getRevision();
    }

  }

  private static JPanel createCommentsPanel(final TreeTableView treeTable) {
    JPanel panel = new JPanel(new BorderLayout());
    final JTextArea textArea = createTextArea();
    treeTable.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
      @Override
      public void valueChanged(@NotNull ListSelectionEvent e) {
        final int index = treeTable.getSelectionModel().getMinSelectionIndex();
        if (index == -1) {
          textArea.setText("");
        } else {
          final VcsFileRevision revision = getRevisionAt(treeTable, index);
          if (revision != null) {
            textArea.setText(revision.getCommitMessage());
          } else {
            textArea.setText("");
          }
        }
      }
    });
    final JScrollPane textScrollPane = ScrollPaneFactory.createScrollPane(textArea);
    panel.add(textScrollPane, BorderLayout.CENTER);
    textScrollPane.setBorder(IdeBorderFactory.createTitledBorder(VcsBundle.message("border.selected.revision.commit.message"), false
    ));
    return panel;
  }

  private static JTextArea createTextArea() {
    final JTextArea textArea = new JTextArea();
    textArea.setRows(5);
    textArea.setEditable(false);
    textArea.setWrapStyleWord(true);
    textArea.setLineWrap(true);
    return textArea;
  }

  public static void showListPopup(final List<VcsFileRevision> revisions, final Project project, final Consumer<VcsFileRevision> selectedRevisionConsumer,
                                   final boolean showComments) {
    ColumnInfo[] columns = new ColumnInfo[] { REVISION_TABLE_COLUMN, DATE_TABLE_COLUMN, AUTHOR_TABLE_COLUMN };
    for(VcsFileRevision revision: revisions) {
      if (revision.getBranchName() != null) {
        columns = new ColumnInfo[] { REVISION_TABLE_COLUMN, BRANCH_TABLE_COLUMN, DATE_TABLE_COLUMN, AUTHOR_TABLE_COLUMN };
        break;
      }
    }
    final TableView<VcsFileRevision> table = new TableView<>(new ListTableModel<>(columns, revisions, 0));
    table.setShowHorizontalLines(false);
    table.setTableHeader(null);
    Runnable runnable = () -> {
      VcsFileRevision revision = table.getSelectedObject();
      if (revision != null) {
        selectedRevisionConsumer.consume(revision);
      }
    };

    if (table.getModel().getRowCount() == 0) {
      table.clearSelection();
    }

    new SpeedSearchBase<TableView>(table) {
      @Override
      protected int getSelectedIndex() {
        return table.getSelectedRow();
      }

      @Override
      protected int convertIndexToModel(int viewIndex) {
        return table.convertRowIndexToModel(viewIndex);
      }

      @Override
      protected Object[] getAllElements() {
        return revisions.toArray();
      }

      @Override
      protected String getElementText(Object element) {
        VcsFileRevision revision = (VcsFileRevision) element;
        return revision.getRevisionNumber().asString() + " " + revision.getBranchName() + " " + revision.getAuthor();
      }

      @Override
      protected void selectElement(Object element, String selectedText) {
        VcsFileRevision revision = (VcsFileRevision) element;
        TableUtil.selectRows(myComponent, new int[] {myComponent.convertRowIndexToView(revisions.indexOf(revision))});
        TableUtil.scrollSelectionToVisible(myComponent);
      }
    };

    table.setMinimumSize(new Dimension(300, 50));
    final PopupChooserBuilder builder = new PopupChooserBuilder(table);

    if (showComments) {
      builder.setSouthComponent(createCommentsPanel(table));
    }

    builder.setTitle(VcsBundle.message("lookup.title.vcs.file.revisions")).
        setItemChoosenCallback(runnable).
        setResizable(true).
        setDimensionServiceKey("Vcs.CompareWithSelectedRevision.Popup").setMinSize(new Dimension(300, 300));
    final JBPopup popup = builder.createPopup();
    
    popup.showCenteredInCurrentWindow(project);
  }

  private static JPanel createCommentsPanel(final TableView<VcsFileRevision> table) {
    final JTextArea textArea = createTextArea();
    table.getSelectionModel().addListSelectionListener(new ListSelectionListener() {
      @Override
      public void valueChanged(@NotNull ListSelectionEvent e) {
        final VcsFileRevision revision = table.getSelectedObject();
        if (revision == null) {
          textArea.setText("");
        } else {
          textArea.setText(revision.getCommitMessage());
          textArea.select(0, 0);
        }
      }
    });

    JPanel jPanel = new JPanel(new BorderLayout());
    final JScrollPane textScrollPane = ScrollPaneFactory.createScrollPane(textArea);
    // text on title border has some problems if text font size is bigger than expected.
    final JLabel commentLabel = new JLabel(VcsBundle.message("border.selected.revision.commit.message"));
    jPanel.add(commentLabel, BorderLayout.NORTH);
    commentLabel.setBorder(IdeBorderFactory.createBorder(SideBorder.TOP | SideBorder.LEFT | SideBorder.BOTTOM));
    textScrollPane.setBorder(null);
    jPanel.add(textScrollPane, BorderLayout.CENTER);
    jPanel.setPreferredSize(new Dimension(300, 100));
    return jPanel;
  }

  private static class TreeNodeAdapter extends DefaultMutableTreeNode {
    private final TreeItem<VcsFileRevision> myRevision;

    public TreeNodeAdapter(TreeNodeAdapter parent, TreeItem<VcsFileRevision> revision, List<TreeItem<VcsFileRevision>> children) {
      if (parent != null) {
        parent.add(this);
      }
      myRevision = revision;
      for (TreeItem<VcsFileRevision> treeItem : children) {
        new TreeNodeAdapter(this, treeItem, treeItem.getChildren());
      }
    }

    public VcsFileRevision getRevision() {
      return myRevision.getData();
    }
  }
}
