package jetbrains.mps.testbench.util;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import java.util.regex.Pattern;
import gnu.trove.TLongObjectHashMap;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.management.ManagementFactory;

/**
 * fyodor, Sep 8, 2010
 */
@GeneratedClass(node = "r:36bec8f9-6cb9-42f4-a517-ea58c11993b3(jetbrains.mps.testbench.util)/5294483648489409095", model = "r:36bec8f9-6cb9-42f4-a517-ea58c11993b3(jetbrains.mps.testbench.util)")
public class ThreadWatcher implements Output {
  private ThreadState myState;
  private String myErrors;
  private String myDescription = "threads difference";

  private static class ThreadState {
    private static final Pattern IGNORED_THREAD;
    private TLongObjectHashMap<ThreadInfo> myAllThreads = new TLongObjectHashMap<ThreadInfo>();
    private TLongObjectHashMap<ThreadInfo> myRunningThreads = new TLongObjectHashMap<ThreadInfo>();

    static {
      final StringBuilder builder = new StringBuilder();

      // --------- Java threads -----------
      builder.append("(AWT\\-.*)");
      builder.append("|(Image Fetch.*)");
      builder.append("|(Finalizer.*)");
      builder.append("|(Reference.*)");
      builder.append("|(Java2D.*)");
      builder.append("|(TimerQueue)");
      builder.append("|(Change List.*)");

      // java.util.Timer
      builder.append("|(Timer\\-.*)");

      // Java 9+ Cleaner thread
      builder.append("|(Common-Cleaner)");
      // --------------------------------


      // ---- IntelliJ Platform threads ---

      // com.intellij.util.Alarm
      builder.append("|(Alarm Pool)");

      // no idea why this thread hangs
      builder.append("|(AWT.*)");

      // com.intellij.openapi.vfs.newvfs.persistent.FlushingDaemon
      builder.append("|(Flushing Daemon)");

      // com.intellij.util.concurrency.AppScheduledExecutorService
      builder.append("|(ApplicationImpl.*)");

      // com.intellij.util.concurrency.AppDelayQueue
      builder.append("|(Periodic task.*)");

      // com.intellij.util.io.BaseDataReader
      builder.append("|(BaseDataReader: error stream of .*git(\\.exe)?.*)");
      builder.append("|(BaseDataReader: output stream of .*git(\\.exe)?.*)");
      builder.append("|(BaseDataReader: setsid .*git(\\.exe)?.*)");

      // com.intellij.execution.process.ProcessIOExecutorService
      builder.append("|(I/O pool [0-9]+)");

      builder.append("|(JobScheduler FJ pool.*)");

      // com.intellij.psi.impl.DocumentCommitThread
      builder.append("|(Document Committing Pool)");

      // com.intellij.util.indexing.ChangedFilesCollector
      builder.append("|(FileBasedIndex Vfs Event Processor)");

      // com.intellij.openapi.project.CacheUpdateRunner#createRunnable
      builder.append("|(Indexing)");

      // java.util.concurrent.ForkJoinPool#workerNamePrefix
      builder.append("|(ForkJoinPool.*)");

      // com.intellij.openapi.actionSystem.impl.ActionUpdater
      builder.append("|(Action Updater .*)");
      // --------------------------------


      // ----------- MPS threads ----------

      // jetbrains.mps.smodel.TryRunPlatformWriteHelper
      builder.append("|(MPS interrupting thread)");

      // jetbrains.mps.smodel.EDTExecutorInternal
      builder.append("|(MPS EDT Executor.*)");

      // jetbrains.mps.vcs.changesmanager.CurrentDifferenceRegistry
      builder.append("|(ChangesManager command queue)");

      // com.intellij.util.io.storage.RefCountingStorage
      builder.append("|(RefCountingStorage.*)");

      // jetbrains.mps.plugins.PluginLoaderRegistry.UpdatingTask
      builder.append("|(Reloading MPS Plugins)");

      // jetbrains.mps.nodeEditor.Highlighter#startUpdater
      builder.append("|(Highlighter)");

      // jetbrains.mps.editor.runtime#HighlightUsagesSupport 
      builder.append("|(HighlightUsages)");
      // --------------------------------


      // ------ Other & unsorted threads -----

      builder.append("|(Keep\\-Alive.*)");
      builder.append("|(AnimatorThread)");

      // Kotlin coroutines threads
      builder.append("|(DefaultDispatcher-worker-.*)");
      builder.append("|(kotlinx.coroutines.DefaultExecutor)");

      // RMI specific threads (used by KotlinCompilerRunner)
      builder.append("|(RMI .*)");

      // macOS specific thread
      builder.append("|(Poller SunPKCS11-Darwin)");

      // Linux specific thread
      builder.append("|(process reaper.*)");

      // org.apache.batik.util.CleanerThread from batik-util library
      builder.append("|(Batik CleanerThread)");

      // org.jetbrains.io.BuiltInServer
      builder.append("|(Netty Builtin Server [0-9]+)");
      // --------------------------------

      IGNORED_THREAD = Pattern.compile(builder.toString());
    }

    private ThreadState() {
    }

    private void captureState() {
      myAllThreads.clear();
      myRunningThreads.clear();
      long cid = Thread.currentThread().getId();
      ThreadMXBean bean = ManagementFactory.getThreadMXBean();
      for (ThreadInfo info : bean.getThreadInfo(bean.getAllThreadIds())) {
        if (info != null && cid != info.getThreadId() && info.getThreadState() != Thread.State.NEW && info.getThreadState() != Thread.State.TERMINATED && !(IGNORED_THREAD.matcher(info.getThreadName()).matches())) {
          myAllThreads.put(info.getThreadId(), info);
          if (Thread.State.RUNNABLE == info.getThreadState()) {
            myRunningThreads.put(info.getThreadId(), info);
          }
        }
      }
    }

    public void dump(StringBuilder sb, String pref) {
      String sep = "";
      for (long id : myRunningThreads.keys()) {
        sb.append(sep).append(pref).append("Running thread ").append(id).append(" \"").append(myRunningThreads.get(id).getThreadName()).append("\"");
        sep = "\n";
      }
      for (long id : myAllThreads.keys()) {
        if (!(myRunningThreads.contains(id))) {
          sb.append(sep).append(pref).append("Inactive thread ").append(id).append(" \"").append(myAllThreads.get(id).getThreadName()).append("\"");
          sep = "\n";
        }
      }
    }

    public ThreadState[] diff(ThreadState baseLine) {
      ThreadState newDiff = new ThreadState();
      ThreadState oldDiff = new ThreadState();
      for (long id : this.myAllThreads.keys()) {
        if (!(baseLine.myAllThreads.containsKey(id))) {
          newDiff.myAllThreads.put(id, this.myAllThreads.get(id));
          if (this.myRunningThreads.containsKey(id)) {
            newDiff.myRunningThreads.put(id, myRunningThreads.get(id));
          }
        }
      }
      for (long id : baseLine.myAllThreads.keys()) {
        if (!(this.myAllThreads.containsKey(id))) {
          oldDiff.myAllThreads.put(id, baseLine.myAllThreads.get(id));
          if (baseLine.myRunningThreads.containsKey(id)) {
            oldDiff.myRunningThreads.put(id, baseLine.myRunningThreads.get(id));
          }
        }
      }
      for (long id : this.myRunningThreads.keys()) {
        if (!(baseLine.myRunningThreads.containsKey(id))) {
          newDiff.myRunningThreads.put(id, this.myRunningThreads.get(id));
          newDiff.myAllThreads.put(id, this.myAllThreads.get(id));
        }
      }
      for (long id : baseLine.myRunningThreads.keys()) {
        if (!(this.myRunningThreads.containsKey(id))) {
          oldDiff.myRunningThreads.put(id, baseLine.myRunningThreads.get(id));
          oldDiff.myAllThreads.put(id, baseLine.myAllThreads.get(id));
        }
      }
      return new ThreadState[]{newDiff, oldDiff};
    }
  }

  public ThreadWatcher(boolean capture) {
    if (capture) {
      this.myState = new ThreadState();
      myState.captureState();
    }
  }

  @Override
  public boolean isNotEmpty() {
    return myErrors != null;
  }

  @Override
  public String getDescription() {
    return myDescription;
  }

  @Override
  public String getText() {
    return myErrors;
  }

  public boolean waitUntilSettled(long millis) {
    if (myState == null) {
      return true;
    }
    if (myErrors != null) {
      throw new IllegalStateException("Settled already");
    }
    ThreadState current = new ThreadState();
    ThreadState[] diff;
    long step = 100;
    long leftMillis = millis;
    do {
      current.captureState();
      diff = current.diff(myState);
      if (diff[0].myAllThreads.isEmpty() && diff[1].myAllThreads.isEmpty()) {
        return true;
      }
      if (diff[0].myAllThreads.isEmpty()) {
        break;
      }
      try {
        Thread.sleep(step);
      } catch (InterruptedException ignore) {
      }
      leftMillis -= step;
    } while (leftMillis >= 0);
    StringBuilder sb = new StringBuilder();
    sb.append("After ").append(millis).append(" ms. --\n");
    StringBuilder sb2 = new StringBuilder();
    String sep2 = "";
    String pr2 = "no";
    if (!(diff[0].myAllThreads.isEmpty())) {
      sb2.append(String.valueOf(diff[0].myAllThreads.size())).append(" new");
      sep2 = ", ";
      pr2 = "";
      sb.append("  New:\n");
      diff[0].dump(sb, "    ");
      sb.append("\n");
    }
    if (!(diff[1].myAllThreads.isEmpty())) {
      sb2.append(sep2).append(String.valueOf(diff[1].myAllThreads.size())).append(" killed");
      pr2 = "";
      sb.append("  Killed:\n");
      diff[1].dump(sb, "    ");
      sb.append("\n");
    }
    sb2.append(pr2).append(" threads");
    this.myDescription = sb2.toString();
    this.myErrors = sb.toString();
    return false;
  }
}
