/*
 * Copyright 2000-2016 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.checkin;

import com.intellij.ide.IdeBundle;
import com.intellij.ide.todo.*;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.ActionPopupMenu;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ApplicationNamesInfo;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vcs.CheckinProjectPanel;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.openapi.vcs.VcsConfiguration;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.CommitExecutor;
import com.intellij.openapi.vcs.ui.RefreshableOnComponent;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.components.labels.LinkLabel;
import com.intellij.ui.components.labels.LinkListener;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.util.Consumer;
import com.intellij.util.PairConsumer;
import com.intellij.util.text.DateFormatUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.tree.DefaultTreeModel;
import java.awt.*;
import java.util.Collection;

import static com.intellij.CommonBundle.getCancelButtonText;
import static com.intellij.openapi.ui.Messages.*;
import static com.intellij.util.ui.UIUtil.getWarningIcon;

/**
 * @author irengrig
 *         Date: 2/17/11
 *         Time: 5:54 PM
 */
public class TodoCheckinHandler extends CheckinHandler {
  private final Project myProject;
  private final CheckinProjectPanel myCheckinProjectPanel;
  private final VcsConfiguration myConfiguration;
  private TodoFilter myTodoFilter;

  public TodoCheckinHandler(CheckinProjectPanel checkinProjectPanel) {
    myProject = checkinProjectPanel.getProject();
    myCheckinProjectPanel = checkinProjectPanel;
    myConfiguration = VcsConfiguration.getInstance(myProject);
  }

  @Override
  public RefreshableOnComponent getBeforeCheckinConfigurationPanel() {
    JCheckBox checkBox = new JCheckBox(VcsBundle.message("before.checkin.new.todo.check", ""));
    return new RefreshableOnComponent() {
      @Override
      public JComponent getComponent() {
        JPanel panel = new JPanel(new BorderLayout(4, 0));
        panel.add(checkBox, BorderLayout.WEST);
        setFilterText(myConfiguration.myTodoPanelSettings.todoFilterName);
        if (myConfiguration.myTodoPanelSettings.todoFilterName != null) {
          myTodoFilter = TodoConfiguration.getInstance().getTodoFilter(myConfiguration.myTodoPanelSettings.todoFilterName);
        }

        Consumer<TodoFilter> consumer = todoFilter -> {
          myTodoFilter = todoFilter;
          String name = todoFilter == null ? null : todoFilter.getName();
          myConfiguration.myTodoPanelSettings.todoFilterName = name;
          setFilterText(name);
        };
        LinkLabel linkLabel = new LinkLabel("Configure", null);
        linkLabel.setListener(new LinkListener() {
          @Override
          public void linkSelected(LinkLabel aSource, Object aLinkData) {
            DefaultActionGroup group = SetTodoFilterAction.createPopupActionGroup(myProject, myConfiguration.myTodoPanelSettings, consumer);
            ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.TODO_VIEW_TOOLBAR, group);
            popupMenu.getComponent().show(linkLabel, 0, linkLabel.getHeight());
          }
        }, null);
        panel.add(linkLabel, BorderLayout.CENTER);

        CheckinHandlerUtil.disableWhenDumb(myProject, checkBox, "TODO check is impossible until indices are up-to-date");
        return panel;
      }

      private void setFilterText(String filterName) {
        if (filterName == null) {
          checkBox.setText(VcsBundle.message("before.checkin.new.todo.check", IdeBundle.message("action.todo.show.all")));
        } else {
          checkBox.setText(VcsBundle.message("before.checkin.new.todo.check", "Filter: " + filterName));
        }
      }

      @Override
      public void refresh() {
      }

      @Override
      public void saveState() {
        myConfiguration.CHECK_NEW_TODO = checkBox.isSelected();
      }

      @Override
      public void restoreState() {
        checkBox.setSelected(myConfiguration.CHECK_NEW_TODO);
      }
    };
  }

  @Override
  public ReturnResult beforeCheckin(@Nullable CommitExecutor executor, PairConsumer<Object, Object> additionalDataConsumer) {
    if (! myConfiguration.CHECK_NEW_TODO) return ReturnResult.COMMIT;
    if (DumbService.getInstance(myProject).isDumb()) {
      String todoName = VcsBundle.message("before.checkin.new.todo.check.title");
      if (Messages.showOkCancelDialog(myProject,
                              todoName +
                              " can't be performed while " + ApplicationNamesInfo.getInstance().getFullProductName() + " updates the indices in background.\n" +
                              "You can commit the changes without running checks, or you can wait until indices are built.",
                              todoName + " is not possible right now",
                              "&Wait", "&Commit", null) == Messages.OK) {
        return ReturnResult.CANCEL;
      }
      return ReturnResult.COMMIT;
    }
    Collection<Change> changes = myCheckinProjectPanel.getSelectedChanges();
    TodoCheckinHandlerWorker worker = new TodoCheckinHandlerWorker(myProject, changes, myTodoFilter, true);

    Ref<Boolean> completed = Ref.create(Boolean.FALSE);
    ProgressManager.getInstance().run(new Task.Modal(myProject, "Looking for New and Edited TODO Items...", true) {
      @Override
      public void run(@NotNull ProgressIndicator indicator) {
        indicator.setIndeterminate(true);
        worker.execute();
      }

      @Override
      public void onSuccess() {
        completed.set(Boolean.TRUE);
      }
    });
    if (completed.get() && (worker.getAddedOrEditedTodos().isEmpty() && worker.getInChangedTodos().isEmpty() &&
        worker.getSkipped().isEmpty())) return ReturnResult.COMMIT;
    if (!completed.get()) return ReturnResult.CANCEL;
    return showResults(worker, executor);
  }

  private ReturnResult showResults(TodoCheckinHandlerWorker worker, CommitExecutor executor) {
    String commitButtonText = executor != null ? executor.getActionText() : myCheckinProjectPanel.getCommitActionName();
    commitButtonText = StringUtil.trimEnd(commitButtonText, "...");

    String text = createMessage(worker);
    boolean thereAreTodoFound = worker.getAddedOrEditedTodos().size() + worker.getInChangedTodos().size() > 0;
    String title = "TODO";
    if (thereAreTodoFound) {
      return askReviewOrCommit(worker, commitButtonText, text, title);
    }
    else if (YES == showYesNoDialog(myProject, text, title, commitButtonText, getCancelButtonText(), getWarningIcon())) {
      return ReturnResult.COMMIT;
    }
    return ReturnResult.CANCEL;
  }

  @NotNull
  private ReturnResult askReviewOrCommit(@NotNull TodoCheckinHandlerWorker worker,
                                         @NotNull String commitButton,
                                         @NotNull String text,
                                         @NotNull String title) {
    String yesButton = VcsBundle.message("todo.in.new.review.button");
    switch (showYesNoCancelDialog(myProject, text, title, yesButton, commitButton, getCancelButtonText(), getWarningIcon())) {
      case YES:
        showTodo(worker);
        return ReturnResult.CLOSE_WINDOW;
      case NO:
        return ReturnResult.COMMIT;
    }
    return ReturnResult.CANCEL;
  }

  private void showTodo(TodoCheckinHandlerWorker worker) {
    String title = "For commit (" + DateFormatUtil.formatDateTime(System.currentTimeMillis()) + ")";
    ServiceManager.getService(myProject, TodoView.class).addCustomTodoView(new TodoTreeBuilderFactory() {
      @Override
      public TodoTreeBuilder createTreeBuilder(JTree tree, DefaultTreeModel treeModel, Project project) {
        return new CustomChangelistTodosTreeBuilder(tree, treeModel, myProject, title, worker.inOneList());
      }
    }, title, new TodoPanelSettings(myConfiguration.myTodoPanelSettings));

    ApplicationManager.getApplication().invokeLater(() -> {
      ToolWindowManager manager = ToolWindowManager.getInstance(myProject);
      if (manager != null) {
        ToolWindow window = manager.getToolWindow("TODO");
        if (window != null) {
          window.show(() -> {
            ContentManager cm = window.getContentManager();
            Content[] contents = cm.getContents();
            if (contents.length > 0) {
              cm.setSelectedContent(contents[contents.length - 1], true);
            }
          });
        }
      }
    }, ModalityState.NON_MODAL, myProject.getDisposed());
  }

  private static String createMessage(TodoCheckinHandlerWorker worker) {
    int added = worker.getAddedOrEditedTodos().size();
    int changed = worker.getInChangedTodos().size();
    int skipped = worker.getSkipped().size();
    if (added == 0 && changed == 0) {
      return VcsBundle.message("todo.handler.only.skipped", skipped);
    } else if (changed == 0) {
      return VcsBundle.message("todo.handler.only.added", added, skipped);
    } else if (added == 0) {
      return VcsBundle.message("todo.handler.only.in.changed", changed, skipped);
    } else {
      return VcsBundle.message("todo.handler.only.both", added, changed, skipped);
    }
  }
}
