/*
 * 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.debugger.ui.breakpoints;

import com.intellij.debugger.engine.DebugProcessImpl;
import com.intellij.debugger.engine.SuspendContextImpl;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
import com.intellij.debugger.jdi.StackFrameProxyImpl;
import com.intellij.debugger.memory.utils.StackFrameItem;
import com.intellij.debugger.settings.DebuggerSettings;
import com.intellij.openapi.util.Key;
import com.intellij.util.containers.ContainerUtil;
import com.sun.jdi.ObjectReference;
import com.sun.jdi.Value;
import com.sun.jdi.event.LocatableEvent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.java.debugger.breakpoints.properties.JavaMethodBreakpointProperties;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author egor
 */
public class StackCapturingLineBreakpoint extends WildcardMethodBreakpoint {
  private final DebugProcessImpl myDebugProcess;
  private final int myParamNo;
  public static final Key<Map<ObjectReference, List<StackFrameItem>>> CAPTURED_STACKS = Key.create("CAPTURED_STACKS");
  private static final int MAX_STORED_STACKS = 1000;

  private final JavaMethodBreakpointProperties myProperties = new JavaMethodBreakpointProperties();

  public StackCapturingLineBreakpoint(DebugProcessImpl debugProcess, String className, String methodName, int paramNo) {
    super(debugProcess.getProject(), null);
    myDebugProcess = debugProcess;
    myParamNo = paramNo;
    myProperties.EMULATED = true;
    myProperties.WATCH_EXIT = false;
    myProperties.myClassPattern = className;
    myProperties.myMethodName = methodName;
  }

  @NotNull
  @Override
  protected JavaMethodBreakpointProperties getProperties() {
    return myProperties;
  }

  @Override
  public String getSuspendPolicy() {
    return DebuggerSettings.SUSPEND_THREAD;
  }

  @Override
  public boolean processLocatableEvent(SuspendContextCommandImpl action, LocatableEvent event) throws EventProcessingException {
    try {
      SuspendContextImpl suspendContext = action.getSuspendContext();
      if (suspendContext != null) {
        StackFrameProxyImpl frameProxy = suspendContext.getFrameProxy();
        if (frameProxy != null) {
          Map<ObjectReference, List<StackFrameItem>> stacks = myDebugProcess.getUserData(CAPTURED_STACKS);
          if (stacks == null) {
            stacks = new LinkedHashMap<ObjectReference, List<StackFrameItem>>() {
              @Override
              protected boolean removeEldestEntry(Map.Entry eldest) {
                return size() > MAX_STORED_STACKS;
              }
            };
            myDebugProcess.putUserData(CAPTURED_STACKS, Collections.synchronizedMap(stacks));
          }
          Value key = ContainerUtil.getOrElse(frameProxy.getArgumentValues(), myParamNo, null);
          if (key instanceof ObjectReference) {
            stacks.put((ObjectReference)key, StackFrameItem.createFrames(suspendContext.getThread()));
          }
        }
      }
    }
    catch (EvaluateException ignored) {
    }
    return false;
  }

  public static void track(DebugProcessImpl debugProcess, String classFqn, String methodName, int paramNo) {
    StackCapturingLineBreakpoint breakpoint = new StackCapturingLineBreakpoint(debugProcess, classFqn, methodName, paramNo);
    breakpoint.createRequest(debugProcess);
  }
}

@@ -1055,16 +1055,60 @@
 ttings;%0A
+import com.intellij.openapi.util.Comparing;%0A
 import c
@@ -1137,16 +1137,16 @@
 il.Key;%0A
-
 import c
@@ -1188,16 +1188,43 @@
 erUtil;%0A
+import com.sun.jdi.Method;%0A
 import c
@@ -1318,16 +1318,51 @@
 eEvent;%0A
+import one.util.streamex.StreamEx;%0A
 import o
@@ -1743,16 +1743,53 @@
 Process;
+%0A%0A  private final String mySignature;
 %0A  priva
@@ -1812,16 +1812,17 @@
 aramNo;%0A
+%0A
   public
@@ -2159,45 +2159,221 @@
 ess,
- String className, String methodName,
+%0A                                      String className,%0A                                      String methodName,%0A                                      String methodSignature,%0A                                     
  int
@@ -2463,16 +2463,51 @@
 rocess;%0A
+    mySignature = methodSignature;%0A
     myPa
@@ -4119,16 +4119,333 @@
 e;%0A  %7D%0A%0A
+  @Override%0A  public StreamEx matchingMethods(StreamEx%3CMethod%3E methods, DebugProcessImpl debugProcess) %7B%0A    String methodName = getMethodName();%0A    return methods%0A      .filter(m -%3E Comparing.equal(methodName, m.name()) && (mySignature == null %7C%7C Comparing.equal(mySignature, m.signature())))%0A      .limit(1);%0A  %7D%0A%0A
   public
@@ -4529,16 +4529,40 @@
 hodName,
+ String methodSignature,
  int par
@@ -4683,16 +4683,33 @@
 hodName,
+ methodSignature,
  paramNo

allow to specify method signature