// 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.openapi.application.ex;

import com.intellij.openapi.application.Application;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.EdtReplacementThread;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.util.Computable;
import com.intellij.openapi.util.Ref;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;

public class ApplicationUtil {
  // Used in com.intellij.ide.instrument.LockWrappingClassVisitor
  @SuppressWarnings("unused")
  @ApiStatus.Internal
  public static boolean acquireWriteIntentLockIfNeeded() {
    return ApplicationManager.getApplication().acquireWriteIntentLockIfNeeded();
  }

  // Used in com.intellij.ide.instrument.LockWrappingClassVisitor
  @SuppressWarnings("unused")
  @ApiStatus.Internal
  public static void releaseWriteIntentLockIfNeeded(boolean needed) {
    ApplicationManager.getApplication().releaseWriteIntentLockIfNeeded(needed);
  }

  // throws exception if can't grab read action right now
  public static <T> T tryRunReadAction(@NotNull final Computable<T> computable) throws CannotRunReadActionException {
    final Ref<T> result = new Ref<>();
    tryRunReadAction(() -> result.set(computable.compute()));
    return result.get();
  }

  public static void tryRunReadAction(@NotNull final Runnable computable) throws CannotRunReadActionException {
    if (!((ApplicationEx)ApplicationManager.getApplication()).tryRunReadAction(computable)) {
      throw CannotRunReadActionException.create();
    }
  }

  /**
   * Allows to interrupt a process which does not performs checkCancelled() calls by itself.
   * Note that the process may continue to run in background indefinitely - so <b>avoid using this method unless absolutely needed</b>.
   */
  public static <T> T runWithCheckCanceled(@NotNull final Callable<T> callable, @NotNull final ProgressIndicator indicator) throws Exception {
    final Ref<T> result = Ref.create();
    final Ref<Throwable> error = Ref.create();

    Future<?> future = ApplicationManager.getApplication().executeOnPooledThread(() -> ProgressManager.getInstance().executeProcessUnderProgress(() -> {
      try {
        result.set(callable.call());
      }
      catch (Throwable t) {
        error.set(t);
      }
    }, indicator));

    try {
      runWithCheckCanceled(future, indicator);
      ExceptionUtil.rethrowAll(error.get());
    }
    catch (ProcessCanceledException e) {
      future.cancel(false);
      throw e;
    }
    return result.get();
  }

  /**
   * Waits for {@code future} to be complete, or the current thread's indicator to be canceled.
   * Note that {@code future} will not be cancelled by this method.<br/>
   * See also {@link com.intellij.openapi.progress.util.ProgressIndicatorUtils#awaitWithCheckCanceled(Future)} which throws no checked exceptions.
   */
  public static <T> T runWithCheckCanceled(@NotNull Future<T> future,
                                           @NotNull final ProgressIndicator indicator) throws ExecutionException {
    while (true) {
      indicator.checkCanceled();

      try {
        return future.get(10, TimeUnit.MILLISECONDS);
      }
      catch (InterruptedException e) {
        throw new ProcessCanceledException(e);
      }
      catch (TimeoutException ignored) { }
    }
  }

  public static void showDialogAfterWriteAction(@NotNull Runnable runnable) {
    Application application = ApplicationManager.getApplication();
    if (application.isWriteAccessAllowed()) {
      application.invokeLater(runnable);
    }
    else {
      runnable.run();
    }
  }

  public static void invokeLaterSomewhere(@NotNull Runnable r, @NotNull EdtReplacementThread thread) {
    invokeLaterSomewhere(r, thread, ApplicationManager.getApplication().getDefaultModalityState());
  }

  public static void invokeLaterSomewhere(@NotNull Runnable r, @NotNull EdtReplacementThread thread, @NotNull ModalityState modalityState) {
    switch (thread) {
      case EDT:
        SwingUtilities.invokeLater(r);
        break;
      case WT:
        ApplicationManager.getApplication().invokeLaterOnWriteThread(r, modalityState);
        break;
      case EDT_WITH_IW:
        ApplicationManager.getApplication().invokeLater(r, modalityState);
        break;
    }
  }

  public static void invokeAndWaitSomewhere(Runnable runnable, EdtReplacementThread thread) {
    invokeAndWaitSomewhere(runnable, thread, ApplicationManager.getApplication().getDefaultModalityState());
  }

  public static void invokeAndWaitSomewhere(@NotNull Runnable r,
                                            @NotNull EdtReplacementThread thread,
                                            @NotNull ModalityState modalityState) {
    switch (thread) {
      case EDT:
        if (!SwingUtilities.isEventDispatchThread() && ApplicationManager.getApplication().isWriteThread()) {
          Logger.getInstance(ApplicationUtil.class).error("Can't invokeAndWait from WT to EDT: probably leads to deadlock");
        }
        UIUtil.invokeAndWaitIfNeeded(r);
        break;
      case WT:
        if (ApplicationManager.getApplication().isWriteThread()) {
          r.run();
        }
        else if (SwingUtilities.isEventDispatchThread()) {
          Logger.getInstance(ApplicationUtil.class).error("Can't invokeAndWait from EDT to WT");
        }
        else {
          Semaphore s = new Semaphore(1);
          AtomicReference<Throwable> throwable = new AtomicReference<>();
          ApplicationManager.getApplication().invokeLaterOnWriteThread(() -> {
            try {
              r.run();
            }
            catch (Throwable t) {
              throwable.set(t);
            }
            finally {
              s.up();
            }
          }, modalityState);
          s.waitFor();

          if (throwable.get() != null) {
            ExceptionUtil.rethrow(throwable.get());
          }
        }
        break;
      case EDT_WITH_IW:
        if (!SwingUtilities.isEventDispatchThread() && ApplicationManager.getApplication().isWriteThread()) {
          Logger.getInstance(ApplicationUtil.class).error("Can't invokeAndWait from WT to EDT: probably leads to deadlock");
        }
        ApplicationManager.getApplication().invokeAndWait(r, modalityState);
        break;
    }
  }

  public static class CannotRunReadActionException extends ProcessCanceledException {
    // When ForkJoinTask joins task which was exceptionally completed from the other thread
    // it tries to re-create that exception (by reflection) and sets its cause to the original exception.
    // That horrible hack causes all sorts of confusion when we try to analyze the exception cause, e.g. in GlobalInspectionContextImpl.inspectFile().
    // To prevent creation of unneeded wrapped exception we restrict constructor visibility to private so that stupid ForkJoinTask has no choice
    // but to use the original exception. (see ForkJoinTask.getThrowableException())
    public static CannotRunReadActionException create() {
      return new CannotRunReadActionException();
    }
    private CannotRunReadActionException() {
    }

  }
}
@@ -801,16 +801,49 @@
 aphore;%0A
+import com.intellij.util.ui.EDT;%0A
 import c
@@ -1267,24 +1267,113 @@
 fNeeded() %7B%0A
+    if (!EDT.isCurrentThreadEdt()) return false; // do not do anything for non-EDT calls%0A
     return A

allow invoking @DirtyUI methods from non-EDT threads w/o grabbing IW lock