// Copyright 2000-2019 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.diagnostic;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Comparing;
import gnu.trove.THashSet;
import gnu.trove.TObjectHashingStrategy;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

import java.util.Arrays;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;

@ApiStatus.Internal
public enum LoadingPhase {
  BOOTSTRAP,
  SPLASH,
  CONFIGURATION_STORE_INITIALIZED,
  FRAME_SHOWN,
  PROJECT_OPENED,
  INDEXING_FINISHED;

  @NotNull
  private static Logger getLogger() {
    return Logger.getInstance(LoadingPhase.class);
  }

  private final static boolean KEEP_IN_MIND_LOADING_PHASE = Boolean.parseBoolean("idea.keep.in.mind.loading.phase");

  public static void setCurrentPhase(@NotNull LoadingPhase phase) {
    currentPhase.set(phase);
    logPhaseSet(phase);
  }

  private final static Set<Throwable> stackTraces = new THashSet<>(new TObjectHashingStrategy<Throwable>() {
    @Override
    public int computeHashCode(Throwable throwable) {
      return getCollect(throwable).hashCode();
    }

    private String getCollect(Throwable throwable) {
      return Arrays
        .stream(throwable.getStackTrace())
        .map(element -> element.getClassName() + element.getMethodName())
        .collect(Collectors.joining());
    }

    @Override
    public boolean equals(Throwable o1, Throwable o2) {
      if (o1 == o2) return true;
      if (o1 == null || o2 == null) return false;
      return Comparing.equal(getCollect(o1), getCollect(o2));
    }
  });

  private final static AtomicReference<LoadingPhase> currentPhase = new AtomicReference<>(BOOTSTRAP);

  public static void compareAndSet(@NotNull LoadingPhase expect, @NotNull LoadingPhase phase) {
    if (currentPhase.compareAndSet(expect, phase)) {
      logPhaseSet(phase);
    }
  }

  private static void logPhaseSet(@NotNull LoadingPhase phase) {
    getLogger().info("Reached " + phase + " loading phase");
  }

  public static void assertAtLeast(@NotNull LoadingPhase phase) {
    if (!KEEP_IN_MIND_LOADING_PHASE) {
      return;
    }

    LoadingPhase currentPhase = LoadingPhase.currentPhase.get();
    if (currentPhase.ordinal() >= phase.ordinal() || isKnownViolator()) {
      return;
    }

    Throwable t = new Throwable();
    synchronized (stackTraces) {
      if (!stackTraces.add(t)) {
        return;
      }

      getLogger().warn("Should be called at least at phase " + phase + ", the current phase is: " + currentPhase + "\n" +
                       "Current violators count: " + stackTraces.size() + "\n\n",
                       t);
    }
  }

  private static boolean isKnownViolator() {
    return Arrays.stream(Thread.currentThread().getStackTrace())
      .anyMatch(element -> {
        String className = element.getClassName();
        return className.equals("com.intellij.openapi.application.Preloader") ||
               className.contains("com.intellij.util.indexing.IndexInfrastructure");
      });
  }

  public static boolean isStartupComplete() {
    return isComplete(INDEXING_FINISHED);
  }

  public static boolean isComplete(@NotNull LoadingPhase phase) {
    return currentPhase.get().ordinal() >= phase.ordinal();
  }
}

@@ -846,28 +846,20 @@
 boolean 
-KEEP_IN_MIND
+SKIP
 _LOADING
@@ -898,20 +898,12 @@
 dea.
-keep.in.mind
+skip
 .loa
@@ -2237,21 +2237,12 @@
 if (
-!KEEP_IN_MIND
+SKIP
 _LOA
@@ -2252,32 +2252,24 @@
 G_PHASE)
- %7B%0A     
  return;
 %0A    %7D%0A%0A
@@ -2256,30 +2256,24 @@
 ASE) return;
-%0A    %7D
 %0A%0A    Loadin
@@ -2380,46 +2380,17 @@
 al()
- %7C%7C isKnownViolator()) %7B%0A     
+)
  return;
 %0A   
@@ -2385,22 +2385,16 @@
  return;
-%0A    %7D
 %0A%0A    Th
@@ -2490,33 +2490,15 @@
 t)) 
-%7B%0A        return;%0A      %7D
+return;
 %0A%0A  
@@ -2726,16 +2726,16 @@
     t);%0A
+
     %7D%0A  
@@ -2741,379 +2741,8 @@
  %7D%0A%0A
-  private static boolean isKnownViolator() %7B%0A    return Arrays.stream(Thread.currentThread().getStackTrace())%0A      .anyMatch(element -%3E %7B%0A        String className = element.getClassName();%0A        return className.equals(%22com.intellij.openapi.application.Preloader%22) %7C%7C%0A               className.contains(%22com.intellij.util.indexing.IndexInfrastructure%22);%0A      %7D);%0A  %7D%0A%0A
   pu

enable checks based on loading phase