// Copyright 2000-2018 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.execution.testframework;

import com.intellij.concurrency.SensitiveProgressWrapper;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.process.OSProcessHandler;
import com.intellij.execution.process.ProcessAdapter;
import com.intellij.execution.process.ProcessEvent;
import com.intellij.openapi.application.ApplicationManager;
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.progress.Task;
import com.intellij.openapi.progress.impl.BackgroundableProcessIndicator;
import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicBoolean;

public abstract class SearchForTestsTask extends Task.Backgroundable {

  private static final Logger LOG = Logger.getInstance(SearchForTestsTask.class);
  protected Socket mySocket;
  private final ServerSocket myServerSocket;
  private ProgressIndicator myProcessIndicator;

  public SearchForTestsTask(@Nullable final Project project,
                            final ServerSocket socket) {
    super(project, ExecutionBundle.message("searching.test.progress.title"), true);
    myServerSocket = socket;
  }


  protected abstract void search() throws ExecutionException;
  protected abstract void onFound() throws ExecutionException;

  public void ensureFinished() {
    if (myProcessIndicator != null && !myProcessIndicator.isCanceled()) {
      finish();
    }
  }

  public void startSearch() {
    if (ApplicationManager.getApplication().isUnitTestMode()) {
      try {
        search();
      }
      catch (Throwable e) {
        LOG.error(e);
      }
      try {
        onFound();
      }
      catch (ExecutionException e) {
        LOG.error(e);
      }
    }
    else {
      myProcessIndicator = new BackgroundableProcessIndicator(this);
      ProgressManager.getInstance().runProcessWithProgressAsynchronously(this, myProcessIndicator);
    }
  }

  public void attachTaskToProcess(final OSProcessHandler handler) {
    handler.addProcessListener(new ProcessAdapter() {
      @Override
      public void processTerminated(@NotNull final ProcessEvent event) {
        handler.removeProcessListener(this);
        ensureFinished();
      }

      @Override
      public void startNotified(@NotNull final ProcessEvent event) {
        startSearch();
      }
    });
  }

  @Override
  public void run(@NotNull ProgressIndicator indicator) {
    try {
      mySocket = myServerSocket.accept();
      final ExecutionException[] ex = new ExecutionException[1];
      Runnable runnable = () ->
        DumbService.getInstance(myProject).withAlternativeResolveEnabled(() -> {
          try {
            search();
          }
          catch (ExecutionException e) {
            ex[0] = e;
          }
        });
      //noinspection StatementWithEmptyBody
      while (!runSmartModeReadActionWithWritePriority(runnable, new SensitiveProgressWrapper(indicator)));
      if (ex[0] != null) {
        logCantRunException(ex[0]);
      }
    }
    catch (ProcessCanceledException e) {
      throw e;
    }
    catch (IOException e) {
      LOG.info(e);
    }
    catch (Throwable e) {
      LOG.error(e);
    }
  }

  /**
   * @return true if runnable has been executed with no write action interference and in "smart" mode
   */
  private boolean runSmartModeReadActionWithWritePriority(@NotNull Runnable runnable, ProgressIndicator indicator) {
    DumbService dumbService = DumbService.getInstance(myProject);

    indicator.checkCanceled();
    dumbService.waitForSmartMode();

    AtomicBoolean dumb = new AtomicBoolean();
    boolean success = ProgressIndicatorUtils.runInReadActionWithWriteActionPriority(() -> {
      if (myProject.isDisposed()) return;

      if (dumbService.isDumb()) {
        dumb.set(true);
        return;
      }

      runnable.run();
    }, indicator);
    if (dumb.get()) {
      return false;
    }
    if (!success) {
      ProgressIndicatorUtils.yieldToPendingWriteActions();
    }
    return success;
  }

  protected void logCantRunException(ExecutionException e) throws ExecutionException {
    throw e;
  }

  @Override
  public void onCancel() {
    finish();
  }

  @Override
  public void onSuccess() {
    Runnable runnable = () -> {
      try {
        onFound();
      }
      catch (ExecutionException e) {
        LOG.error(e);
      }
      finish();
    };
    DumbService.getInstance(getProject()).runWhenSmart(runnable);
  }

  public void finish() {
    DataOutputStream os = null;
    try {
      if (mySocket == null || mySocket.isClosed()) return;
      os = new DataOutputStream(mySocket.getOutputStream());
      os.writeBoolean(true);
    }
    catch (Throwable e) {
      LOG.info(e);
    }
    finally {
      try {
        if (os != null) os.close();
      }
      catch (Throwable e) {
        LOG.info(e);
      }

      try {
        if (!myServerSocket.isClosed()) {
          myServerSocket.close();
        }
      }
      catch (Throwable e) {
        LOG.info(e);
      }
    }
  }
}

@@ -185,66 +185,8 @@
 k;%0A%0A
-import com.intellij.concurrency.SensitiveProgressWrapper;%0A
 impo
@@ -492,32 +492,84 @@
 icationManager;%0A
+import com.intellij.openapi.application.ReadAction;%0A
 import com.intel
@@ -893,74 +893,8 @@
 or;%0A
-import com.intellij.openapi.progress.util.ProgressIndicatorUtils;%0A
 impo
@@ -1187,58 +1187,8 @@
 ket;
-%0Aimport java.util.concurrent.atomic.AtomicBoolean;
 %0A%0Apu
@@ -3070,105 +3070,29 @@
    R
-unnable runnable = () -%3E%0A        DumbService.getInstance(myProject).withAlternativeResolveEnabled
+eadAction.nonBlocking
 (() 
@@ -3100,34 +3100,30 @@
 %3E %7B%0A        
-  
 try %7B%0A
-  
           se
@@ -3134,30 +3134,26 @@
 ();%0A        
-  %7D%0A  
+%7D%0A
         catc
@@ -3189,18 +3189,16 @@
         
-  
 ex%5B0%5D = 
@@ -3212,22 +3212,18 @@
     
-  %7D%0A  
+%7D%0A
       %7D)
 ;%0A  
@@ -3222,158 +3222,53 @@
   %7D)
-;%0A      //noinspection StatementWithEmptyBody%0A      while (!runSmartModeReadActionWithWritePriority(runnable, new SensitiveProgressWrapper(indicator))
+.inSmartMode(myProject).executeSynchronously(
 );%0A 
@@ -3519,837 +3519,8 @@
  %7D%0A%0A
-  /**%0A   * @return true if runnable has been executed with no write action interference and in %22smart%22 mode%0A   */%0A  private boolean runSmartModeReadActionWithWritePriority(@NotNull Runnable runnable, ProgressIndicator indicator) %7B%0A    DumbService dumbService = DumbService.getInstance(myProject);%0A%0A    indicator.checkCanceled();%0A    dumbService.waitForSmartMode();%0A%0A    AtomicBoolean dumb = new AtomicBoolean();%0A    boolean success = ProgressIndicatorUtils.runInReadActionWithWriteActionPriority(() -%3E %7B%0A      if (myProject.isDisposed()) return;%0A%0A      if (dumbService.isDumb()) %7B%0A        dumb.set(true);%0A        return;%0A      %7D%0A%0A      runnable.run();%0A    %7D, indicator);%0A    if (dumb.get()) %7B%0A      return false;%0A    %7D%0A    if (!success) %7B%0A      ProgressIndicatorUtils.yieldToPendingWriteActions();%0A    %7D%0A    return success;%0A  %7D%0A%0A
   pr

use NonBlockingReadAction.executeSynchronously