/*
 * 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.codeInsight.daemon.impl;

import com.intellij.codeInsight.navigation.ListBackgroundUpdaterTask;
import com.intellij.find.FindUtil;
import com.intellij.ide.PsiCopyPasteManager;
import com.intellij.ide.util.PsiElementListCellRenderer;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.NavigatablePsiElement;
import com.intellij.psi.PsiElement;
import com.intellij.ui.CollectionListModel;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBList;
import com.intellij.ui.popup.AbstractPopup;
import com.intellij.ui.popup.HintUpdateSupply;
import com.intellij.usages.UsageView;
import com.intellij.util.Alarm;
import com.intellij.util.Consumer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.awt.datatransfer.Transferable;
import java.awt.event.MouseEvent;
import java.util.List;

public class PsiElementListNavigator {
  private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.PsiElementListNavigator");

  private PsiElementListNavigator() {
  }

  public static void openTargets(MouseEvent e, NavigatablePsiElement[] targets, String title, final String findUsagesTitle, ListCellRenderer listRenderer) {
    openTargets(e, targets, title, findUsagesTitle, listRenderer, null);
  }

  public static void openTargets(MouseEvent e,
                                 NavigatablePsiElement[] targets,
                                 String title,
                                 final String findUsagesTitle,
                                 ListCellRenderer listRenderer,
                                 @Nullable ListBackgroundUpdaterTask listUpdaterTask) {
    JBPopup popup = navigateOrCreatePopup(targets, title, findUsagesTitle, listRenderer, listUpdaterTask);
    if (popup != null) {
      if (listUpdaterTask != null) {
        Alarm alarm = new Alarm(popup);
        alarm.addRequest(() -> popup.show(new RelativePoint(e)), 300);
        ProgressManager.getInstance().run(listUpdaterTask);
      }
      else {
        popup.show(new RelativePoint(e));
      }
    }
  }

  public static void openTargets(Editor e, NavigatablePsiElement[] targets, String title, final String findUsagesTitle, ListCellRenderer listRenderer) {
    JBPopup popup = navigateOrCreatePopup(targets, title, findUsagesTitle, listRenderer, null);
    if (popup != null) popup.showInBestPositionFor(e);
  }

  @Nullable
  private static JBPopup navigateOrCreatePopup(final NavigatablePsiElement[] targets,
                                               final String title,
                                               final String findUsagesTitle,
                                               final ListCellRenderer listRenderer,
                                               @Nullable final ListBackgroundUpdaterTask listUpdaterTask) {
    return navigateOrCreatePopup(targets, title, findUsagesTitle, listRenderer, listUpdaterTask, selectedElements -> {
      for (Object element : selectedElements) {
        PsiElement selected = (PsiElement)element;
        LOG.assertTrue(selected.isValid());
        ((NavigatablePsiElement)selected).navigate(true);
      }
    });
  }

  /**
   * listUpdaterTask should be started after alarm is initialized so one-item popup won't blink
   */
  @Nullable
  public static JBPopup navigateOrCreatePopup(@NotNull final NavigatablePsiElement[] targets,
                                              final String title,
                                              final String findUsagesTitle,
                                              final ListCellRenderer listRenderer,
                                              @Nullable final ListBackgroundUpdaterTask listUpdaterTask,
                                              @NotNull final Consumer<Object[]> consumer) {
    if (targets.length == 0) return null;
    if (targets.length == 1 && (listUpdaterTask == null || listUpdaterTask.isFinished())) {
      consumer.consume(targets);
      return null;
    }
    final CollectionListModel<NavigatablePsiElement> model = new CollectionListModel<>(targets);
    final JBList list = new JBList(model);
    HintUpdateSupply.installSimpleHintUpdateSupply(list);

    list.setTransferHandler(new TransferHandler(){
      @Nullable
      @Override
      protected Transferable createTransferable(JComponent c) {
        final Object[] selectedValues = list.getSelectedValues();
        final PsiElement[] copy = new PsiElement[selectedValues.length];
        for (int i = 0; i < selectedValues.length; i++) {
          copy[i] = (PsiElement)selectedValues[i];
        }
        return new PsiCopyPasteManager.MyTransferable(copy);
      }

      @Override
      public int getSourceActions(JComponent c) {
        return COPY;
      }
    });

    list.setCellRenderer(listRenderer);
    list.setFont(EditorUtil.getEditorFont());

    final PopupChooserBuilder builder = new PopupChooserBuilder(list);
    if (listRenderer instanceof PsiElementListCellRenderer) {
      ((PsiElementListCellRenderer)listRenderer).installSpeedSearch(builder);
    }

    PopupChooserBuilder popupChooserBuilder = builder.
      setTitle(title).
      setMovable(true).
      setResizable(true).
      setItemChoosenCallback(() -> {
        int[] ids = list.getSelectedIndices();
        if (ids == null || ids.length == 0) return;
        Object[] selectedElements = list.getSelectedValues();
        consumer.consume(selectedElements);
      }).
      setCancelCallback(() -> {
        HintUpdateSupply.hideHint(list);
        if (listUpdaterTask != null) {
          listUpdaterTask.cancelTask();
        }
        return true;
      });
    final Ref<UsageView> usageView = new Ref<>();
    if (findUsagesTitle != null) {
      popupChooserBuilder = popupChooserBuilder.setCouldPin(popup -> {
        final List<NavigatablePsiElement> items = model.getItems();
        usageView.set(FindUtil.showInUsageView(null, items.toArray(new PsiElement[items.size()]), findUsagesTitle, targets[0].getProject()));
        popup.cancel();
        return false;
      });
    }

    final JBPopup popup = popupChooserBuilder.createPopup();

    builder.getScrollPane().setBorder(null);
    builder.getScrollPane().setViewportBorder(null);

    if (listUpdaterTask != null) {
      listUpdaterTask.init((AbstractPopup)popup, list, usageView);
    }
    return popup;
  }
}
