/*
 * Copyright 2000-2014 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 com.intellij.openapi.vcs.changes;

import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.NlsContexts;
import com.intellij.openapi.vcs.VcsBundle;
import com.intellij.util.concurrency.Semaphore;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import static com.intellij.util.ObjectUtils.notNull;
import static com.intellij.util.WaitForProgressToShow.runOrInvokeLaterAboveProgress;

class InvokeAfterUpdateCallback {
  private final static Logger LOG = Logger.getInstance(InvokeAfterUpdateCallback.class);

  interface CallbackData {
    void startProgress();

    void endProgress();

    void handleStoppedQueue();
  }

  @NotNull
  public static CallbackData create(@NotNull Project project,
                                    @NotNull InvokeAfterUpdateMode mode,
                                    @NotNull Runnable afterUpdate,
                                    @Nullable String title,
                                    @Nullable ModalityState state) {
    if (mode.isSilent()) {
      return new SilentCallbackData(project, afterUpdate, mode.isCallbackOnAwt(), state);
    }
    else {
      return new TaskCallbackData(project, afterUpdate, mode.isSynchronous(), mode.isCancellable(), title, state);
    }
  }

  private abstract static class CallbackDataBase implements CallbackData {
    protected final Project myProject;
    private final Runnable myAfterUpdate;
    protected final ModalityState myModalityState;

    CallbackDataBase(@NotNull Project project, @NotNull Runnable afterUpdate, @Nullable ModalityState modalityState) {
      myProject = project;
      myAfterUpdate = afterUpdate;
      myModalityState = modalityState;
    }

    @Override
    public void handleStoppedQueue() {
      ModalityState modalityState = notNull(myModalityState, ModalityState.defaultModalityState());
      ApplicationManager.getApplication().invokeLater(this::invokeCallback, modalityState);
    }

    protected final void invokeCallback() {
      LOG.debug("changes update finished for project " + myProject.getName());
      if (!myProject.isDisposed()) myAfterUpdate.run();
    }
  }

  private static class SilentCallbackData extends CallbackDataBase {
    private final boolean myCallbackOnAwt;

    SilentCallbackData(@NotNull Project project,
                       @NotNull Runnable afterUpdate,
                       boolean callbackOnAwt,
                       @Nullable ModalityState state) {
      super(project, afterUpdate, state);
      myCallbackOnAwt = callbackOnAwt;
    }

    @Override
    public void startProgress() {
    }

    @Override
    public void endProgress() {
      if (myCallbackOnAwt) {
        ApplicationManager.getApplication().invokeLater(this::invokeCallback);
      }
      else {
        ApplicationManager.getApplication().executeOnPooledThread(this::invokeCallback);
      }
    }
  }

  private static class TaskCallbackData extends CallbackDataBase {
    private final boolean mySynchronous;
    private final boolean myCanBeCancelled;
    private final @NlsContexts.ProgressTitle String myTaskTitle;

    @NotNull private final Semaphore mySemaphore = new Semaphore(1);

    TaskCallbackData(@NotNull Project project,
                     @NotNull Runnable afterUpdate,
                     boolean synchronous,
                     boolean canBeCancelled,
                     String title,
                     @Nullable ModalityState state) {
      super(project, afterUpdate, state);
      mySynchronous = synchronous;
      myCanBeCancelled = canBeCancelled;
      myTaskTitle = VcsBundle.message("change.list.manager.wait.lists.synchronization", title);
    }

    @Override
    public void startProgress() {
      if (mySynchronous) {
        new ModalWaiterTask().queue();
      }
      else {
        new BackgroundableWaiterTask().queue();
      }
    }

    @Override
    public void endProgress() {
      mySemaphore.up();
    }

    private void awaitSemaphore(@NotNull ProgressIndicator indicator) {
      indicator.setIndeterminate(true);
      indicator.setText2(VcsBundle.message("commit.wait.util.synched.text"));
      ProgressIndicatorUtils.awaitWithCheckCanceled(mySemaphore, indicator);
    }

    private class ModalWaiterTask extends Task.Modal {
      ModalWaiterTask() {
        super(TaskCallbackData.this.myProject, myTaskTitle, myCanBeCancelled);
        setCancelText(VcsBundle.message("button.skip"));
      }

      @Override
      public void run(@NotNull ProgressIndicator indicator) {
        awaitSemaphore(indicator);
      }

      @Override
      public void onFinished() {
        invokeCallback();
      }
    }

    private class BackgroundableWaiterTask extends Task.Backgroundable {
      BackgroundableWaiterTask() {
        super(TaskCallbackData.this.myProject, myTaskTitle, myCanBeCancelled);
      }

      @Override
      public void run(@NotNull ProgressIndicator indicator) {
        awaitSemaphore(indicator);

        ModalityState modalityState = notNull(myModalityState, ModalityState.NON_MODAL);
        runOrInvokeLaterAboveProgress(() -> invokeCallback(), modalityState, myProject);
      }

      @Override
      public boolean isHeadless() {
        return false;
      }
    }
  }
}

@@ -2059,23 +2059,16 @@
 kOnAwt()
-, state
 );%0A    %7D
@@ -2364,59 +2364,8 @@
 ate;
-%0A    protected final ModalityState myModalityState;
 %0A%0A  
@@ -2442,47 +2442,8 @@
 date
-, @Nullable ModalityState modalityState
 ) %7B%0A
@@ -2508,299 +2508,8 @@
 te;%0A
-      myModalityState = modalityState;%0A    %7D%0A%0A    @Override%0A    public void handleStoppedQueue() %7B%0A      ModalityState modalityState = notNull(myModalityState, ModalityState.defaultModalityState());%0A      ApplicationManager.getApplication().invokeLater(this::invokeCallback, modalityState);%0A
     
@@ -2965,62 +2965,8 @@
 nAwt
-,%0A                       @Nullable ModalityState state
 ) %7B%0A
@@ -2989,39 +2989,32 @@
 ect, afterUpdate
-, state
 );%0A      myCallb
@@ -3139,32 +3139,189 @@
 endProgress() %7B%0A
+      scheduleCallback();%0A    %7D%0A%0A    @Override%0A    public void handleStoppedQueue() %7B%0A      scheduleCallback();%0A    %7D%0A%0A    private void scheduleCallback() %7B%0A
       if (myCall
@@ -3757,16 +3757,65 @@
 skTitle;
+%0A    private final ModalityState myModalityState;
 %0A%0A    @N
@@ -4185,23 +4185,16 @@
 erUpdate
-, state
 );%0A     
@@ -4356,24 +4356,55 @@
 n%22, title);%0A
+      myModalityState = state;%0A
     %7D%0A%0A    @
@@ -4665,32 +4665,284 @@
 re.up();%0A    %7D%0A%0A
+    @Override%0A    public void handleStoppedQueue() %7B%0A      ModalityState modalityState = notNull(myModalityState, ModalityState.defaultModalityState());%0A      ApplicationManager.getApplication().invokeLater(this::invokeCallback, modalityState);%0A    %7D%0A%0A
     private void

use same threading for silent callbacks if update queue is stopped