/*
 * Copyright 2000-2016 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.actionSystem;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.util.concurrency.AppExecutorUtil;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.util.concurrent.ExecutorService;

/**
 * Base class for the actions, which update() method might be potentially slow to be executed synchronously in Swing UI thread.
 *
 * @author max
 */
public abstract class AsyncUpdateAction<T> extends AnAction {
  private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.actionSystem.AsyncUpdateAction");

  private static final ExecutorService ourUpdaterService = AppExecutorUtil.createBoundedApplicationPoolExecutor("AsyncUpdateAction pool", 1);

  // Async update
  @Override
  public final void update(AnActionEvent e) {
    final T data = prepareDataFromContext(e);
    final Presentation originalPresentation = e.getPresentation();
    if (!forceSyncUpdate(e) && isDumbAware()) {
      final Presentation realPresentation = originalPresentation.clone();
      ourUpdaterService.submit(() -> {
        performUpdate(realPresentation, data);
        SwingUtilities.invokeLater(() -> {
          if (originalPresentation.isVisible() != realPresentation.isVisible()) {
            LOG.error("Async update is not supported for actions that change their visibility." +
                      "Either stop extending AsyncUpdateAction or override forceSyncUpdate() to return true." +
                      "Action class is: " + this.getClass().getName());
          }
          originalPresentation.copyFrom(realPresentation);
        });
      });

      originalPresentation.setVisible(true);
      originalPresentation.setEnabled(false);
    }
    else {
      performUpdate(originalPresentation, data);
    }
  }

  // Sync update
  @Override
  public final void beforeActionPerformedUpdate(@NotNull AnActionEvent e) {
    performUpdate(e.getPresentation(), prepareDataFromContext(e));
  }

  /**
   * Get all necessary data from event's DataContext to be used in <code>performUpdate()</code>, which is called asynchronously.
   * @param e action event original update() method have been called with.
   * @return prepared data for {@link #performUpdate} method.
   */
  protected abstract T prepareDataFromContext(final AnActionEvent e);

  /**
   * Perform real presentation tweaking here. Be aware of the fact this method may be called in thread other than Swing UI thread thus
   * probable restrictions like necessity to call ApplicationManager.getApplication().runReadAction() apply.
   * @param presentation Presentation object to be tweaked.
   * @param data necessary data calculated by {@link #prepareDataFromContext(AnActionEvent)}.
   */
  protected abstract void performUpdate(Presentation presentation, T data);

  /**
   * Override this method to return <code>true</code> value if update method cannot be called asynchronously for whatever reason.
   * @param e action event original update() method have been called with.
   * @return <code>false</code> if async update is possible and <code>false</code> otherwise.
   */
  protected boolean forceSyncUpdate(AnActionEvent e) {
    return false;
  }
}
