/*
 * Copyright 2003-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 jetbrains.mps.ide.util;

import com.intellij.ide.IdeBundle;
import com.intellij.ide.util.PropertiesComponent;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.CustomShortcutSet;
import com.intellij.openapi.actionSystem.DefaultActionGroup;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.actionSystem.ToggleAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.VerticalFlowLayout;
import com.intellij.openapi.util.IconLoader;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.Ref;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.ui.ColoredTreeCellRenderer;
import com.intellij.ui.ScrollPaneFactory;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.ui.SpeedSearchComparator;
import com.intellij.ui.TreeSpeedSearch;
import com.intellij.ui.speedSearch.SpeedSearchUtil;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.PlatformIcons;
import com.intellij.util.containers.Convertor;
import com.intellij.util.containers.FactoryMap;
import com.intellij.util.containers.HashMap;
import com.intellij.util.ui.UIUtil;
import com.intellij.util.ui.tree.TreeUtil;
import jetbrains.mps.ide.icons.IconManager;
import jetbrains.mps.ide.project.ProjectHelper;
import jetbrains.mps.smodel.SNodeUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.mps.openapi.model.SNode;
import org.jetbrains.mps.openapi.model.SNodeReference;
import org.jetbrains.mps.openapi.module.SRepository;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.Icon;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.KeyStroke;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Enumeration;
import java.util.LinkedHashSet;
import java.util.List;


public class GroupedNodesChooser extends DialogWrapper {
  protected Tree myTree;
  private DefaultTreeModel myTreeModel;
  protected JComponent[] myOptionControls;

  private final ArrayList<MemberNode> mySelectedNodes = new ArrayList<MemberNode>();

  private boolean mySorted = false;
  private boolean myShowContainers = true;
  private boolean myAllowEmptySelection = false;
  private boolean myAllowMultiSelection;
  protected final Project myProject;

  private SNodeReference[] myElements;
  private final HashMap<MemberNode, ParentNode> myNodeToParentMap = new HashMap<MemberNode, ParentNode>();
  private final HashMap<SNodeReference, MemberNode> myElementToNodeMap = new HashMap<SNodeReference, MemberNode>();
  private final ArrayList<ContainerNode> myContainerNodes = new ArrayList<ContainerNode>();
  private LinkedHashSet<SNodeReference> mySelectedElements;

  @NonNls
  private static final String PROP_SORTED = "MPS.NodesChooser.sorted";
  @NonNls
  private static final String PROP_SHOWCONTAINERS = "MPS.NodesChooser.showContainers";

  public GroupedNodesChooser(SNodeReference[] elements,
                             boolean allowEmptySelection,
                             boolean allowMultiSelection,
                             @NotNull Project project
  ) {
    super(project, true);
    myAllowEmptySelection = allowEmptySelection;
    myAllowMultiSelection = allowMultiSelection;
    myProject = project;
    myTree = new Tree(new DefaultTreeModel(new DefaultMutableTreeNode()));
    myOptionControls = null;
    resetElements(elements);
    init();
  }

  public void resetElements(SNodeReference[] elements) {
    myElements = elements;
    mySelectedNodes.clear();
    myNodeToParentMap.clear();
    myElementToNodeMap.clear();
    myContainerNodes.clear();

    ProjectHelper.getModelAccess(myProject).runReadAction(() -> myTreeModel = buildModel());

    myTree.setModel(myTreeModel);
    myTree.setRootVisible(false);

    doSort();

    TreeUtil.expandAll(myTree);
    initOptions();

    myTree.doLayout();
    setOKActionEnabled(myElements != null && myElements.length > 0);
  }

  protected void initOptions() {
    myOptionControls = new JComponent[0];
  }

  /**
   * should be invoked in read action
   */
  private DefaultTreeModel buildModel() {
    final DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode();
    final Ref<Integer> count = new Ref<Integer>(0);
    final SRepository projectRepo = ProjectHelper.getProjectRepository(myProject);
    final FactoryMap<Object, ParentNode> map = new FactoryMap<Object, ParentNode>() {
      @Override
      protected ParentNode create(final Object key) {
        if (key instanceof SNodeReference) {
          SNode el = ((SNodeReference) key).resolve(projectRepo);
          if (el != null) {
            final ContainerNode containerNode = new ContainerNode(rootNode, (SNodeReference) key, getText(el), getIcon(el), count);
            myContainerNodes.add(containerNode);
            return containerNode;
          }
          return new ParentNode(rootNode, null, "<unknown>", null, count);
        }
        if (key instanceof String) {
          return new ParentNode(rootNode, null, (String) key, null, count);
        }
        throw new IllegalArgumentException();
      }
    };

    for (SNodeReference object : myElements) {
      SNode node = object.resolve(projectRepo);
      Object group = getGroupNode(node);
      if (group == null) group = getGroupTitle(node);
      final ParentNode parentNode = map.get(group);
      final MemberNode elementNode = new MemberNode(parentNode, object, getText(node), getIcon(node), count);
      myNodeToParentMap.put(elementNode, parentNode);
      myElementToNodeMap.put(object, elementNode);
    }
    return new DefaultTreeModel(rootNode);
  }

  protected Icon getIcon(SNode node) {
    return IconManager.getIconFor(node);
  }

  protected String getText(SNode node) {
    return SNodeUtil.getPresentation(node);
  }

  @Nullable
  protected SNodeReference getGroupNode(SNode node) {
    SNode parent = node.getParent();
    return parent != null ? new jetbrains.mps.smodel.SNodePointer(parent) : null;
  }

  @NotNull
  protected String getGroupTitle(SNode node) {
    return "Others";
  }

  public void selectElements(SNodeReference[] elements) {
    ArrayList<TreePath> selectionPaths = new ArrayList<TreePath>();
    for (SNodeReference element : elements) {
      MemberNode treeNode = myElementToNodeMap.get(element);
      if (treeNode != null) {
        selectionPaths.add(new TreePath(treeNode.getPath()));
      }
    }
    myTree.setSelectionPaths(selectionPaths.toArray(new TreePath[selectionPaths.size()]));
  }


  @Override
  protected Action[] createActions() {
    if (myAllowEmptySelection) {
      return new Action[]{getOKAction(), new SelectNoneAction(), getCancelAction()};
    } else {
      return new Action[]{getOKAction(), getCancelAction()};
    }
  }

  @Override
  protected void doHelpAction() {
  }

  protected void customizeOptionsPanel() {
  }

  @Override
  protected JComponent createSouthPanel() {
    JPanel panel = new JPanel(new GridBagLayout());

    customizeOptionsPanel();
    JPanel optionsPanel = new JPanel(new VerticalFlowLayout());
    for (final JComponent component : myOptionControls) {
      optionsPanel.add(component);
    }

    panel.add(
      optionsPanel,
      new GridBagConstraints(0, 0, 1, 1, 1, 0, GridBagConstraints.WEST, GridBagConstraints.NONE,
        new Insets(0, 0, 0, 5), 0, 0)
    );

    if (myElements == null || myElements.length == 0) {
      setOKActionEnabled(false);
    }
    panel.add(
      super.createSouthPanel(),
      new GridBagConstraints(1, 0, 1, 1, 0, 0, GridBagConstraints.SOUTH, GridBagConstraints.NONE,
        new Insets(0, 0, 0, 0), 0, 0)
    );
    return panel;
  }

  @Override
  protected JComponent createCenterPanel() {
    JPanel panel = new JPanel(new BorderLayout());

    // Toolbar

    DefaultActionGroup group = new DefaultActionGroup();

    fillToolbarActions(group);

    group.addSeparator();

    ExpandAllAction expandAllAction = new ExpandAllAction();
    expandAllAction.registerCustomShortcutSet(
      new CustomShortcutSet(
        KeyStroke.getKeyStroke(KeyEvent.VK_EQUALS, SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK)),
      myTree);
    group.add(expandAllAction);

    CollapseAllAction collapseAllAction = new CollapseAllAction();
    collapseAllAction.registerCustomShortcutSet(
      new CustomShortcutSet(
        KeyStroke.getKeyStroke(KeyEvent.VK_MINUS, SystemInfo.isMac ? InputEvent.META_MASK : InputEvent.CTRL_MASK)),
      myTree);
    group.add(collapseAllAction);

    panel.add(ActionManager.getInstance().createActionToolbar(ActionPlaces.UNKNOWN, group, true).getComponent(),
      BorderLayout.NORTH);

    // Tree

    myTree.setCellRenderer(new ColoredTreeCellRenderer() {
      @Override
      public void customizeCellRenderer(JTree tree, Object value, boolean selected, boolean expanded,
                                        boolean leaf, int row, boolean hasFocus) {
        if (value instanceof ElementNode) {
          ((ElementNode) value).renderTreeNode(this, tree);
        }
      }
    }
    );
    UIUtil.setLineStyleAngled(myTree);
    myTree.setRootVisible(false);
    myTree.setShowsRootHandles(true);
    myTree.addKeyListener(new TreeKeyListener());
    myTree.addTreeSelectionListener(new MyTreeSelectionListener());

    if (!myAllowMultiSelection) {
      myTree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
    }

    if (getRootNode().getChildCount() > 0) {
      myTree.expandRow(0);
      myTree.setSelectionRow(1);
    }
    TreeUtil.expandAll(myTree);
    final TreeSpeedSearch treeSpeedSearch = new TreeSpeedSearch(myTree, new Convertor<TreePath, String>() {
      @Override
      @Nullable
      public String convert(TreePath path) {
        final ElementNode lastPathComponent = (ElementNode) path.getLastPathComponent();
        if (lastPathComponent == null) return null;
        return lastPathComponent.getText();
      }
    });
    treeSpeedSearch.setComparator(new SpeedSearchComparator(false));

    treeSpeedSearch.addChangeListener(new PropertyChangeListener() {
      @Override
      public void propertyChange(PropertyChangeEvent evt) {
        myTree.repaint(); // to update match highlighting
      }
    });

    myTree.addMouseListener(
      new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
          if (e.getClickCount() == 2) {
            if (myTree.getPathForLocation(e.getX(), e.getY()) != null) {
              doOKAction();
            }
          }
        }
      }
    );
    TreeUtil.installActions(myTree);
    JScrollPane scrollPane = ScrollPaneFactory.createScrollPane(myTree);
    scrollPane.setPreferredSize(new Dimension(350, 450));
    panel.add(scrollPane, BorderLayout.CENTER);

    return panel;
  }

  protected void fillToolbarActions(DefaultActionGroup group) {
    SortEmAction sortAction = new SortEmAction();
    sortAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_A, InputEvent.ALT_MASK)), myTree);
    setSorted(PropertiesComponent.getInstance().isTrueValue(PROP_SORTED));
    group.add(sortAction);

    ShowContainersAction showContainersAction = getShowContainersAction();
    showContainersAction.registerCustomShortcutSet(new CustomShortcutSet(KeyStroke.getKeyStroke(KeyEvent.VK_C, InputEvent.ALT_MASK)), myTree);
    setShowContainers(PropertiesComponent.getInstance().isTrueValue(PROP_SHOWCONTAINERS));
    group.add(showContainersAction);
  }

  @Override
  protected String getDimensionServiceKey() {
    return "#jetbrains.mps.ide.util.NodesChooser";
  }

  @Override
  public JComponent getPreferredFocusedComponent() {
    return myTree;
  }

  public JComponent[] getOptionControls() {
    return myOptionControls;
  }

  @Nullable
  private LinkedHashSet<SNodeReference> getSelectedElementsList() {
    return getExitCode() == OK_EXIT_CODE ? mySelectedElements : null;
  }

  @Nullable
  public List<SNodeReference> getSelectedElements() {
    final LinkedHashSet<SNodeReference> list = getSelectedElementsList();
    return list == null ? null : new ArrayList<SNodeReference>(list);
  }

  protected final boolean areElementsSelected() {
    return mySelectedElements != null && !mySelectedElements.isEmpty();
  }

  private boolean isSorted() {
    return mySorted;
  }

  private void setSorted(boolean sorted) {
    if (mySorted == sorted) return;
    mySorted = sorted;
    doSort();
  }

  private void doSort() {
    Pair<ElementNode, List<ElementNode>> pair = storeSelection();

    Enumeration<ParentNode> children = getRootNodeChildren();
    while (children.hasMoreElements()) {
      ParentNode classNode = children.nextElement();
      sortNode(classNode, mySorted);
      myTreeModel.nodeStructureChanged(classNode);
    }

    restoreSelection(pair);
  }

  private static void sortNode(ParentNode node, boolean sorted) {
    ArrayList<MemberNode> arrayList = new ArrayList<MemberNode>();
    Enumeration<MemberNode> children = node.children();
    while (children.hasMoreElements()) {
      arrayList.add(children.nextElement());
    }

    Collections.sort(arrayList, sorted ? new AlphaComparator() : new OrderComparator());

    replaceChildren(node, arrayList);
  }

  private static void replaceChildren(final DefaultMutableTreeNode node, final Collection<? extends ElementNode> arrayList) {
    node.removeAllChildren();
    for (ElementNode child : arrayList) {
      node.add(child);
    }
  }

  private void setShowContainers(boolean showContainers) {
    myShowContainers = showContainers;

    Pair<ElementNode, List<ElementNode>> selection = storeSelection();

    DefaultMutableTreeNode root = getRootNode();
    if (!myShowContainers || myContainerNodes.isEmpty()) {
      List<ParentNode> otherObjects = new ArrayList<ParentNode>();
      Enumeration<ParentNode> children = getRootNodeChildren();
      ParentNode newRoot = new ParentNode(null, null, getAllContainersNodeName(), null, new Ref<Integer>(0));
      while (children.hasMoreElements()) {
        final ParentNode nextElement = children.nextElement();
        if (nextElement instanceof ContainerNode) {
          final ContainerNode containerNode = (ContainerNode) nextElement;
          Enumeration<MemberNode> memberNodes = containerNode.children();
          List<MemberNode> memberNodesList = new ArrayList<MemberNode>();
          while (memberNodes.hasMoreElements()) {
            memberNodesList.add(memberNodes.nextElement());
          }
          for (MemberNode memberNode : memberNodesList) {
            newRoot.add(memberNode);
          }
        } else {
          otherObjects.add(nextElement);
        }
      }
      replaceChildren(root, otherObjects);
      sortNode(newRoot, mySorted);
      if (newRoot.children().hasMoreElements()) root.add(newRoot);
    } else {
      Enumeration<ParentNode> children = getRootNodeChildren();
      if (children.hasMoreElements()) {
        ParentNode allClassesNode = children.nextElement();
        Enumeration<MemberNode> memberNodes = allClassesNode.children();
        ArrayList<MemberNode> arrayList = new ArrayList<MemberNode>();
        while (memberNodes.hasMoreElements()) {
          arrayList.add(memberNodes.nextElement());
        }
        for (MemberNode memberNode : arrayList) {
          myNodeToParentMap.get(memberNode).add(memberNode);
        }
      }
      replaceChildren(root, myContainerNodes);
    }
    myTreeModel.nodeStructureChanged(root);

    TreeUtil.expandAll(myTree);

    restoreSelection(selection);
  }

  protected String getAllContainersNodeName() {
    return "All elements";
  }

  private Enumeration<ParentNode> getRootNodeChildren() {
    return getRootNode().children();
  }

  private DefaultMutableTreeNode getRootNode() {
    return (DefaultMutableTreeNode) myTreeModel.getRoot();
  }

  private Pair<ElementNode, List<ElementNode>> storeSelection() {
    List<ElementNode> selectedNodes = new ArrayList<ElementNode>();
    TreePath[] paths = myTree.getSelectionPaths();
    if (paths != null) {
      for (TreePath path : paths) {
        selectedNodes.add((ElementNode) path.getLastPathComponent());
      }
    }
    TreePath leadSelectionPath = myTree.getLeadSelectionPath();
    return Pair.create(leadSelectionPath != null ? (ElementNode) leadSelectionPath.getLastPathComponent() : null, selectedNodes);
  }


  private void restoreSelection(Pair<ElementNode, List<ElementNode>> pair) {
    List<ElementNode> selectedNodes = pair.second;

    DefaultMutableTreeNode root = getRootNode();

    ArrayList<TreePath> toSelect = new ArrayList<TreePath>();
    for (ElementNode node : selectedNodes) {
      if (root.isNodeDescendant(node)) {
        toSelect.add(new TreePath(node.getPath()));
      }
    }

    if (!toSelect.isEmpty()) {
      myTree.setSelectionPaths(toSelect.toArray(new TreePath[toSelect.size()]));
    }

    ElementNode leadNode = pair.first;
    if (leadNode != null) {
      myTree.setLeadSelectionPath(new TreePath(leadNode.getPath()));
    }
  }

  @Override
  public void dispose() {
    PropertiesComponent instance = PropertiesComponent.getInstance();
    instance.setValue(PROP_SORTED, Boolean.toString(isSorted()));
    instance.setValue(PROP_SHOWCONTAINERS, Boolean.toString(myShowContainers));

    final Container contentPane = getContentPane();
    if (contentPane != null) {
      contentPane.removeAll();
    }
    mySelectedNodes.clear();
    myElements = null;
    super.dispose();
  }

  private class MyTreeSelectionListener implements TreeSelectionListener {
    @Override
    public void valueChanged(TreeSelectionEvent e) {
      TreePath[] paths = e.getPaths();
      if (paths == null) return;
      for (int i = 0; i < paths.length; i++) {
        Object node = paths[i].getLastPathComponent();
        if (node instanceof MemberNode) {
          final MemberNode memberNode = (MemberNode) node;
          if (e.isAddedPath(i)) {
            if (!mySelectedNodes.contains(memberNode)) {
              mySelectedNodes.add(memberNode);
            }
          } else {
            mySelectedNodes.remove(memberNode);
          }
        }
      }
      mySelectedElements = new LinkedHashSet<SNodeReference>();
      for (MemberNode selectedNode : mySelectedNodes) {
        mySelectedElements.add(selectedNode.getElement());
      }
    }
  }

  private abstract static class ElementNode extends DefaultMutableTreeNode {
    private final int myOrder;
    private final SNodeReference myElement;
    private final String myText;
    private Icon myIcon;

    public ElementNode(@Nullable DefaultMutableTreeNode parent, SNodeReference nodePointer, String text, Icon icon, Ref<Integer> order) {
      myIcon = icon;
      myOrder = order.get();
      order.set(myOrder + 1);
      myText = text;
      myElement = nodePointer;
      if (parent != null) {
        parent.add(this);
      }
    }

    public SNodeReference getElement() {
      return myElement;
    }

    public int getOrder() {
      return myOrder;
    }

    public String getText() {
      return myText;
    }

    public void renderTreeNode(ColoredTreeCellRenderer coloredTreeCellRenderer, JTree tree) {
      SpeedSearchUtil.appendFragmentsForSpeedSearch(tree, getText(), getTextAttributes(tree), false, coloredTreeCellRenderer);
      coloredTreeCellRenderer.setIcon(myIcon);
    }

    protected SimpleTextAttributes getTextAttributes(JTree tree) {
      return new SimpleTextAttributes(SimpleTextAttributes.STYLE_PLAIN, tree.getForeground());
    }
  }

  private static class MemberNode extends ElementNode {
    public MemberNode(ParentNode parent, SNodeReference element, String text, Icon icon, Ref<Integer> order) {
      super(parent, element, text, icon, order);
    }
  }

  private static class ParentNode extends ElementNode {
    public ParentNode(@Nullable DefaultMutableTreeNode parent, SNodeReference element, String text, Icon icon, Ref<Integer> order) {
      super(parent, null, text, icon, order);
    }
  }

  private static class ContainerNode extends ParentNode {
    public ContainerNode(DefaultMutableTreeNode parent, SNodeReference element, String text, Icon icon, Ref<Integer> order) {
      super(parent, element, text, icon, order);
    }
  }

  private class SelectNoneAction extends AbstractAction {
    public SelectNoneAction() {
      super(IdeBundle.message("action.select.none"));
    }

    @Override
    public void actionPerformed(ActionEvent e) {
      myTree.clearSelection();
      doOKAction();
    }
  }

  private class TreeKeyListener extends KeyAdapter {
    @Override
    public void keyPressed(KeyEvent e) {
      TreePath path = myTree.getLeadSelectionPath();
      if (path == null) return;
      final Object lastComponent = path.getLastPathComponent();
      if (e.getKeyCode() == KeyEvent.VK_ENTER) {
        if (lastComponent instanceof ParentNode) return;
        doOKAction();
        e.consume();
      } else if (e.getKeyCode() == KeyEvent.VK_INSERT) {
        if (lastComponent instanceof ElementNode) {
          final ElementNode node = (ElementNode) lastComponent;
          if (!mySelectedNodes.contains(node)) {
            if (node.getNextNode() != null) {
              myTree.setSelectionPath(new TreePath(node.getNextNode().getPath()));
            }
          } else {
            if (node.getNextNode() != null) {
              myTree.removeSelectionPath(new TreePath(node.getPath()));
              myTree.setSelectionPath(new TreePath(node.getNextNode().getPath()));
              myTree.repaint();
            }
          }
          e.consume();
        }
      }
    }
  }

  private class SortEmAction extends ToggleAction {
    public SortEmAction() {
      super(IdeBundle.message("action.sort.alphabetically"),
        IdeBundle.message("action.sort.alphabetically"), IconLoader.getIcon("/objectBrowser/sorted.png"));
    }

    @Override
    public boolean isSelected(AnActionEvent event) {
      return isSorted();
    }

    @Override
    public void setSelected(AnActionEvent event, boolean flag) {
      setSorted(flag);
    }
  }

  protected ShowContainersAction getShowContainersAction() {
    return new ShowContainersAction("Show Groups", PlatformIcons.CLASS_ICON);
  }

  protected class ShowContainersAction extends ToggleAction {
    public ShowContainersAction(final String text, final Icon icon) {
      super(text, text, icon);
    }

    @Override
    public boolean isSelected(AnActionEvent event) {
      return myShowContainers;
    }

    @Override
    public void setSelected(AnActionEvent event, boolean flag) {
      setShowContainers(flag);
    }

    @Override
    public void update(AnActionEvent e) {
      super.update(e);
      Presentation presentation = e.getPresentation();
      presentation.setEnabled(myContainerNodes.size() > 1);
    }
  }

  private class ExpandAllAction extends AnAction {
    public ExpandAllAction() {
      super(IdeBundle.message("action.expand.all"), IdeBundle.message("action.expand.all"),
        IconLoader.getIcon("/actions/expandall.png"));
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      TreeUtil.expandAll(myTree);
    }
  }

  private class CollapseAllAction extends AnAction {
    public CollapseAllAction() {
      super(IdeBundle.message("action.collapse.all"), IdeBundle.message("action.collapse.all"),
        IconLoader.getIcon("/actions/collapseall.png"));
    }

    @Override
    public void actionPerformed(AnActionEvent e) {
      TreeUtil.collapseAll(myTree, 1);
    }
  }

  private static class AlphaComparator implements Comparator<ElementNode> {
    @Override
    public int compare(ElementNode n1, ElementNode n2) {
      return n1.getText().compareToIgnoreCase(n2.getText());
    }
  }

  private static class OrderComparator implements Comparator<ElementNode> {
    @Override
    public int compare(ElementNode n1, ElementNode n2) {
      return n1.getOrder() - n2.getOrder();
    }
  }
}
