// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.psi.util;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public final class CachedValueProfiler {
  private static final CachedValueProfiler ourInstance = new CachedValueProfiler();

  private volatile MultiMap<StackTraceElement, ProfilingInfo> myStorage = null;

  private final Object myLock = new Object();
  private volatile ConcurrentMap<CachedValueProvider.Result<?>, ProfilingInfo> myTemporaryResults;

  public static boolean canProfile() {
    return ApplicationManager.getApplication().isInternal();
  }

  public boolean isEnabled() {
    return myStorage != null;
  }

  public void setEnabled(boolean value) {
    synchronized (myLock) {
      if (value) {
        MultiMap<StackTraceElement, ProfilingInfo> storage = myStorage;
        if (storage == null) {
          myStorage = MultiMap.createConcurrent();
        }
        myTemporaryResults = new ConcurrentHashMap<>();
      }
      else {
        myStorage = null;
        myTemporaryResults = null;
      }
    }
  }

  public static @NotNull CachedValueProfiler getInstance() {
    return ourInstance;
  }

  public void createInfo(@NotNull CachedValueProvider.Result<?> result) {
    MultiMap<StackTraceElement, ProfilingInfo> storage = myStorage;
    if (storage == null) return;

    ConcurrentMap<CachedValueProvider.Result<?>, ProfilingInfo> temporaryResults = myTemporaryResults;
    if (temporaryResults == null) return;

    StackTraceElement origin = findOrigin();
    if (origin == null) return;

    ProfilingInfo info = new ProfilingInfo(origin);
    storage.putValue(origin, info);
    temporaryResults.put(result, info);
  }

  public @Nullable <T> ProfilingInfo getTemporaryInfo(@NotNull CachedValueProvider.Result<T> result) {
    ConcurrentMap<CachedValueProvider.Result<?>, ProfilingInfo> map = myTemporaryResults;
    return map != null ? map.remove(result) : null;
  }

  public MultiMap<StackTraceElement, ProfilingInfo> getStorageSnapshot() {
    return myStorage.copy();
  }

  private static @Nullable StackTraceElement findOrigin() {
    StackTraceElement[] stackTrace = new Throwable().getStackTrace();
    return findFirstStackTraceElementExcluding(stackTrace, CachedValueProfiler.class.getName(), CachedValueProvider.class.getName());
  }

  private static @Nullable StackTraceElement findFirstStackTraceElementExcluding(StackTraceElement @NotNull [] stackTraceElements,
                                                                                 String @NotNull ... excludedClasses) {
    for (StackTraceElement element : stackTraceElements) {
      if (!matches(element, excludedClasses)) {
        return element;
      }
    }

    return null;
  }

  private static boolean matches(@NotNull StackTraceElement element, String @NotNull [] excludedClasses) {
    for (String aClass : excludedClasses) {
      if (element.getClassName().startsWith(aClass)) return true;
    }
    return false;
  }

}

@@ -573,16 +573,160 @@
 filer();
+%0A  private static final boolean ourCanProfile = ApplicationManager.getApplication().isInternal();%0A%0A  private final Object myLock = new Object();
 %0A%0A  priv
@@ -794,62 +794,8 @@
 rage
- = null;%0A%0A  private final Object myLock = new Object()
 ;%0A  
@@ -946,56 +946,21 @@
 urn 
-ApplicationManager.getApplication().isInternal()
+ourCanProfile
 ;%0A  
@@ -2521,21 +2521,28 @@
 e = 
-new Throwable
+Thread.currentThread
 ().g
@@ -2565,25 +2565,31 @@
     
-return findFirstS
+for (int i = 3, len = s
 tack
@@ -2597,514 +2597,439 @@
 race
-ElementExcluding(stackTrace, CachedValueProfiler.class.getName(), CachedValueProvider.class.get
+.length; i %3C len; i++) %7B%0A      String className = stackTrace%5Bi%5D.getClass
 Name()
-)
 ;%0A  
-%7D%0A%0A  private static @Nullable StackTraceElement findFirstStackTraceElementExcluding(StackTraceElement @NotNull %5B%5D stackTraceElements,%0A                                                                                 String @NotNull ... excludedClasses) %7B%0A    for (StackTraceEleme
+    if (className.startsWith(%22com.intellij.util.CachedValue%22)) continue;%0A      if (className.startsWith(%22com.intellij.psi.util.CachedValue%22)) continue;%0A      if (className.startsWith(%22com.i
 nt
- 
 el
-ement : stackTraceElements) %7B%0A      if (!matches(element, excludedClasses)) %7B%0A        return element;%0A      %7D
+lij.psi.impl.PsiCachedValue%22)) continue;%0A      if (className.startsWith(%22com.intellij.openapi.util.Recursion%22)) continue;%0A      return stackTrace%5Bi%5D;
 %0A    %7D%0A
-%0A
     
@@ -3045,257 +3045,10 @@
 ll;%0A
+
   %7D%0A
-%0A  private static boolean matches(@NotNull StackTraceElement element, String @NotNull %5B%5D excludedClasses) %7B%0A    for (String aClass : excludedClasses) %7B%0A      if (element.getClassName().startsWith(aClass)) return true;%0A    %7D%0A    return false;%0A  %7D%0A%0A
 %7D%0A

improve origin lookup