/*
 * Copyright 2000-2009 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.ui;

import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ArrayUtilRt;
import com.intellij.util.ObjectUtils;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Convertor;
import com.intellij.util.ui.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.util.ArrayList;
import java.util.List;
import java.util.ListIterator;

import static javax.swing.tree.TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION;

public class TreeSpeedSearch extends SpeedSearchBase<JTree> {
  private boolean myCanExpand;

  private static final Convertor<TreePath, String> TO_STRING = object -> {
    DefaultMutableTreeNode node = (DefaultMutableTreeNode)object.getLastPathComponent();
    return node.toString();
  };
  private final Convertor<TreePath, String> myToStringConvertor;
  public static final Convertor<TreePath, String> NODE_DESCRIPTOR_TOSTRING = path -> {
    final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();
    final Object userObject = node.getUserObject();
    if (userObject instanceof NodeDescriptor) {
      NodeDescriptor descr = (NodeDescriptor)userObject;
      return descr.toString();
    }
    return TO_STRING.convert(path);
  };

  public TreeSpeedSearch(JTree tree, Convertor<TreePath, String> toStringConvertor) {
    this(tree, toStringConvertor, false);
  }

  public TreeSpeedSearch(JTree tree) {
    this(tree, TO_STRING);
  }

  public TreeSpeedSearch(Tree tree, Convertor<TreePath, String> toString) {
    this(tree, toString, false);
  }

  public TreeSpeedSearch(Tree tree, Convertor<TreePath, String> toString, boolean canExpand) {
    this((JTree)tree, toString, canExpand);
  }

  public TreeSpeedSearch(JTree tree, Convertor<TreePath, String> toString, boolean canExpand) {
    super(tree);
    setComparator(new SpeedSearchComparator(false, true));
    myToStringConvertor = toString;
    myCanExpand = canExpand;

    new MySelectAllAction(tree, this).registerCustomShortcutSet(tree, null);
  }

  @Override
  protected void selectElement(Object element, String selectedText) {
    TreeUtil.selectPath(myComponent, (TreePath)element);
  }

  @Override
  protected int getSelectedIndex() {
    if (myCanExpand) {
      return ArrayUtilRt.find(getAllElements(), myComponent.getSelectionPath());
    }
    int[] selectionRows = myComponent.getSelectionRows();
    return selectionRows == null || selectionRows.length == 0 ? -1 : selectionRows[0];
  }

  @Override
  protected Object[] getAllElements() {
    if (myCanExpand) {
      final Object root = myComponent.getModel().getRoot();
      if (root instanceof DefaultMutableTreeNode || root instanceof PathAwareTreeNode) {
        final List<TreePath> paths = new ArrayList<>();
        TreeUtil.traverseDepth((TreeNode)root, node -> {
          if (node instanceof DefaultMutableTreeNode) {
            paths.add(new TreePath(((DefaultMutableTreeNode)node).getPath()));
          }
          else if (node instanceof PathAwareTreeNode) {
            paths.add(((PathAwareTreeNode)node).getPath());
          }
          return true;
        });
        return paths.toArray(new TreePath[paths.size()]);
      }
    }
    TreePath[] paths = new TreePath[myComponent.getRowCount()];
    for (int i = 0; i < paths.length; i++) {
      paths[i] = myComponent.getPathForRow(i);
    }
    return paths;

  }

  @Override
  protected String getElementText(Object element) {
    TreePath path = (TreePath)element;
    String string = myToStringConvertor.convert(path);
    if (string == null) return TO_STRING.convert(path);
    return string;
  }

  public interface PathAwareTreeNode extends TreeNode {
    TreePath getPath();
  }

  @NotNull
  private List<TreePath> findAllFilteredElements(String s) {
    List<TreePath> paths = new ArrayList<>();
    String _s = s.trim();

    ListIterator<Object> it = getElementIterator(0);
    while (it.hasNext()) {
      Object element = it.next();
      if (isMatchingElement(element, _s)) paths.add((TreePath)element);
    }
    return paths;
  }

  private static class MySelectAllAction extends DumbAwareAction {
    @NotNull private final JTree myTree;
    @NotNull private final TreeSpeedSearch mySearch;

    public MySelectAllAction(@NotNull JTree tree, @NotNull TreeSpeedSearch search) {
      myTree = tree;
      mySearch = search;
      copyShortcutFrom(ActionManager.getInstance().getAction(IdeActions.ACTION_SELECT_ALL));
      setEnabledInModalContext(true);
    }

    @Override
    public void update(AnActionEvent e) {
      e.getPresentation().setEnabled(mySearch.isPopupActive() &&
                                     myTree.getSelectionModel().getSelectionMode() == DISCONTIGUOUS_TREE_SELECTION);
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      TreeSelectionModel sm = myTree.getSelectionModel();

      String query = mySearch.getEnteredPrefix();
      if (query == null) return;

      List<TreePath> filtered = mySearch.findAllFilteredElements(query);
      if (filtered.isEmpty()) return;

      boolean alreadySelected = sm.getSelectionCount() == filtered.size() &&
                                ContainerUtil.and(filtered, (path) -> sm.isPathSelected(path));

      if (alreadySelected) {
        TreePath anchor = myTree.getAnchorSelectionPath();

        sm.setSelectionPath(anchor);
        myTree.setAnchorSelectionPath(anchor);

        mySearch.findAndSelectElement(query);
      }
      else {
        TreePath currentElement = (TreePath)mySearch.findElement(query);
        TreePath anchor = ObjectUtils.chooseNotNull(currentElement, filtered.get(0));

        sm.setSelectionPaths(ArrayUtil.toObjectArray(filtered, TreePath.class));
        myTree.setAnchorSelectionPath(anchor);
      }
    }
  }
}

@@ -1738,84 +1738,20 @@
 G = 
-object -%3E %7B%0A    DefaultMutableTreeNode node = (DefaultMutableTreeNode)object
+path -%3E path
 .get
@@ -1773,25 +1773,8 @@
 nt()
-;%0A    return node
 .toS
@@ -1782,21 +1782,16 @@
 ring();%0A
-  %7D;%0A
   privat
@@ -1942,146 +1942,78 @@
     
-final DefaultMutableTreeNode node = (DefaultMutableTreeNode)path.getLastPathComponent();%0A    final Object userObject = node.getUserObject(
+Object userObject = TreeUtil.getUserObject(path.getLastPathComponent()
 );%0A 

fix direct cast in tree speed search when it is not needed