/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jetbrains.idea.maven.project;

import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.ControlFlowException;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.util.ExceptionUtil;
import com.intellij.util.concurrency.Semaphore;
import org.jetbrains.idea.maven.execution.SoutMavenConsole;
import org.jetbrains.idea.maven.utils.*;

import java.util.LinkedList;
import java.util.Queue;

public class MavenProjectsProcessor {
  private final Project myProject;
  private final String myTitle;
  private final boolean myCancellable;
  private final MavenEmbeddersManager myEmbeddersManager;

  private final Queue<MavenProjectsProcessorTask> myQueue = new LinkedList<>();
  private boolean isProcessing;

  private volatile boolean isStopped;

  public MavenProjectsProcessor(Project project, String title, boolean cancellable, MavenEmbeddersManager embeddersManager) {
    myProject = project;
    myTitle = title;
    myCancellable = cancellable;
    myEmbeddersManager = embeddersManager;
  }

  public void scheduleTask(MavenProjectsProcessorTask task) {
    synchronized (myQueue) {
      if (!isProcessing && !ApplicationManager.getApplication().isUnitTestMode()) {
        isProcessing = true;
        startProcessing(task);
        return;
      }
      if (myQueue.contains(task)) return;
      myQueue.add(task);
    }
  }

  public void removeTask(MavenProjectsProcessorTask task) {
    synchronized (myQueue) {
      myQueue.remove(task);
    }
  }

  public void waitForCompletion() {
    if (isStopped) return;

    if (ApplicationManager.getApplication().isUnitTestMode()) {
      synchronized (myQueue) {
        while (!myQueue.isEmpty()) {
          startProcessing(myQueue.poll());
        }
      }
      return;
    }

    final Semaphore semaphore = new Semaphore();
    semaphore.down();
    scheduleTask(new MavenProjectsProcessorTask() {
      @Override
      public void perform(Project project, MavenEmbeddersManager embeddersManager, MavenConsole console, MavenProgressIndicator indicator)
        throws MavenProcessCanceledException {
        semaphore.up();
      }
    });

    while (true) {
      if (isStopped || semaphore.waitFor(1000)) return;
    }
  }

  public void stop() {
    isStopped = true;
    synchronized (myQueue) {
      myQueue.clear();
    }
  }

  private void startProcessing(final MavenProjectsProcessorTask task) {
    MavenUtil.runInBackground(myProject, myTitle, myCancellable, new MavenTask() {
      @Override
      public void run(MavenProgressIndicator indicator) throws MavenProcessCanceledException {
        Condition<MavenProgressIndicator> condition = mavenProgressIndicator -> isStopped;
        indicator.addCancelCondition(condition);
        try {
          doProcessPendingTasks(indicator, task);
        }
        finally {
          indicator.removeCancelCondition(condition);
        }
      }
    });
  }

  private void doProcessPendingTasks(MavenProgressIndicator indicator,
                                     MavenProjectsProcessorTask task)
    throws MavenProcessCanceledException {
    int counter = 0;
    try {
      while (true) {
        indicator.checkCanceled();
        counter++;

        int remained;
        synchronized (myQueue) {
          remained = myQueue.size();
        }
        indicator.setFraction(counter / (double)(counter + remained));

        try {
          final MavenGeneralSettings mavenGeneralSettings = MavenProjectsManager.getInstance(myProject).getGeneralSettings();
          task.perform(myProject, myEmbeddersManager,
                       new SoutMavenConsole(mavenGeneralSettings.getOutputLevel(), mavenGeneralSettings.isPrintErrorStackTraces()),
                       indicator);
        }
        catch (MavenProcessCanceledException e) {
          throw e;
        }
        catch (Throwable e) {
          logImportErrorIfNotControlFlow(e);
        }

        synchronized (myQueue) {
          task = myQueue.poll();
          if (task == null) {
            isProcessing = false;
            return;
          }
        }
      }
    }
    catch (MavenProcessCanceledException e) {
      synchronized (myQueue) {
        myQueue.clear();
        isProcessing = false;
      }
      throw e;
    }
  }

  private void logImportErrorIfNotControlFlow(Throwable e) {
    if (e instanceof ControlFlowException) {
      ExceptionUtil.rethrowAllAsUnchecked(e);
    }
    MavenLog.LOG.error(e);
    new Notification(MavenUtil.MAVEN_NOTIFICATION_GROUP,
                     "Unable to import Maven project",
                     "See logs for details",
                     NotificationType.ERROR
    ).addAction(ActionManager.getInstance().getAction("ShowLog")).notify(myProject);
  }
}

@@ -2456,113 +2456,180 @@
     
-synchronized (myQueue) %7B%0A        while (!myQueue.isEmpty()) %7B%0A          startProcessing(myQueue.poll());%0A
+while (true) %7B%0A        MavenProjectsProcessorTask task;%0A        synchronized (myQueue) %7B%0A          task = myQueue.poll();%0A          if(task == null)%7B%0A            return;%0A  
     
@@ -2626,32 +2626,34 @@
 rn;%0A          %7D%0A
+  
       %7D%0A      re
@@ -2646,32 +2646,61 @@
     %7D%0A      
-return;%0A
+  startProcessing(task);%0A        %7D%0A  
     %7D%0A%0A    f

Fix tests - maven deadlocked in UnitTests