/*
 * 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.xdebugger.impl.ui.tree;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.xdebugger.impl.ui.tree.nodes.RestorableStateNode;
import com.intellij.xdebugger.impl.ui.tree.nodes.XDebuggerTreeNode;
import gnu.trove.THashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.tree.TreePath;
import java.awt.*;
import java.util.List;
import java.util.Map;

/**
 * @author nik
 */
public class XDebuggerTreeState {
  private final NodeInfo myRootInfo;
  private Rectangle myLastVisibleNodeRect;

  private XDebuggerTreeState(@NotNull XDebuggerTree tree) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    XDebuggerTreeNode root = tree.getRoot();
    myRootInfo = root != null ? new NodeInfo("", "", tree.isPathSelected(root.getPath())) : null;
    if (root != null) {
      addChildren(tree, myRootInfo, root);
    }
  }

  public XDebuggerTreeRestorer restoreState(@NotNull XDebuggerTree tree) {
    ApplicationManager.getApplication().assertIsDispatchThread();
    XDebuggerTreeRestorer restorer = null;
    if (myRootInfo != null) {
      restorer = new XDebuggerTreeRestorer(tree, myLastVisibleNodeRect);
      restorer.restore(tree.getRoot(), myRootInfo);
    }
    return restorer;
  }

  public static XDebuggerTreeState saveState(@NotNull XDebuggerTree tree) {
    return new XDebuggerTreeState(tree);
  }

  private void addChildren(final XDebuggerTree tree, final NodeInfo nodeInfo, final XDebuggerTreeNode treeNode) {
    if (tree.isExpanded(treeNode.getPath())) {
      List<? extends XDebuggerTreeNode> children = treeNode.getLoadedChildren();
      nodeInfo.myExpanded = true;
      for (XDebuggerTreeNode child : children) {
        TreePath path = child.getPath();
        Rectangle bounds = tree.getPathBounds(path);
        if (bounds != null) {
          Rectangle treeVisibleRect =
            tree.getParent() instanceof JViewport ? ((JViewport)tree.getParent()).getViewRect() : tree.getVisibleRect();
          if (treeVisibleRect.contains(bounds)) {
            myLastVisibleNodeRect = bounds;
          }
        }
        NodeInfo childInfo = createNode(child, tree.isPathSelected(path));
        if (childInfo != null) {
          nodeInfo.addChild(childInfo);
          addChildren(tree, childInfo, child);
        }
      }
    }
  }

  @Nullable
  private static NodeInfo createNode(final XDebuggerTreeNode node, boolean selected) {
    if (node instanceof RestorableStateNode) {
      RestorableStateNode valueNode = (RestorableStateNode)node;
      if (valueNode.isComputed()) {
        return new NodeInfo(valueNode.getName(), valueNode.getRawValue(), selected);
      }
    }
    return null;
  }

  public static class NodeInfo {
    private final String myName;
    private final String myValue;
    private boolean myExpanded;
    private final boolean mySelected;
    private Map<String, NodeInfo> myChildren;

    public NodeInfo(final String name, final String value, boolean selected) {
      myName = name;
      myValue = value;
      mySelected = selected;
    }

    public void addChild(@NotNull NodeInfo child) {
      if (myChildren == null) {
        myChildren = new THashMap<String, NodeInfo>();
      }
      myChildren.put(child.myName, child);
    }

    public boolean isExpanded() {
      return myExpanded;
    }

    public boolean isSelected() {
      return mySelected;
    }

    public String getValue() {
      return myValue;
    }

    @Nullable
    public NodeInfo removeChild(String name) {
      return myChildren != null ? myChildren.remove(name) : null;
    }
  }
}

@@ -700,16 +700,113 @@
 anager;%0A
+import com.intellij.util.containers.ContainerUtil;%0Aimport com.intellij.util.containers.MultiMap;%0A
 import c
@@ -863,24 +863,24 @@
 eStateNode;%0A
-
 import com.i
@@ -939,35 +939,8 @@
 de;%0A
-import gnu.trove.THashMap;%0A
 impo
@@ -1113,20 +1113,26 @@
 va.util.
-List
+Collection
 ;%0Aimport
@@ -1142,19 +1142,20 @@
 va.util.
-Map
+List
 ;%0A%0A/**%0A 
@@ -3625,16 +3625,21 @@
 private 
+Multi
 Map%3CStri
@@ -3663,16 +3663,70 @@
 hildren;
+ // MultiMap to allow several nodes with the same name
 %0A%0A    pu
@@ -3991,33 +3991,17 @@
 new 
-THashMap%3CString, NodeInfo
+MultiMap%3C
 %3E();
@@ -4029,16 +4029,21 @@
 dren.put
+Value
 (child.m
@@ -4323,23 +4323,20 @@
 %7B%0A      
-return 
+if (
 myChildr
@@ -4342,48 +4342,241 @@
 ren 
-!
+=
 = null
- ? myChildren.remove(name) : null
+) %7B%0A        return null;%0A      %7D%0A      Collection%3CNodeInfo%3E infos = myChildren.get(name);%0A      NodeInfo item = ContainerUtil.getFirstItem(infos);%0A      if (item != null) %7B%0A        infos.remove(item);%0A      %7D%0A      return item
 ;%0A  

allow restoring several tree nodes with the same name (e.g. a varaible and a watch with the same name)