/*
 * Copyright 2000-2015 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.execution.startup;

import com.intellij.concurrency.JobScheduler;
import com.intellij.execution.*;
import com.intellij.execution.executors.DefaultRunExecutor;
import com.intellij.execution.impl.RunManagerImpl;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.ExecutionEnvironmentBuilder;
import com.intellij.ide.startup.StartupManagerEx;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.startup.StartupActivity;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.ui.MessageType;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.util.Alarm;
import org.jetbrains.annotations.NotNull;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author Irina.Chernushina on 8/19/2015.
 */
public class ProjectStartupRunner implements StartupActivity, DumbAware {
  public static final int DELAY_MILLIS = 200;

  @Override
  public void runActivity(@NotNull final Project project) {
    final ProjectStartupTaskManager projectStartupTaskManager = ProjectStartupTaskManager.getInstance(project);
    if (projectStartupTaskManager.isEmpty()) return;

    RunManagerImpl.getInstanceImpl(project).addRunManagerListener(new RunManagerListener() {
      @Override
      public void runConfigurationRemoved(@NotNull RunnerAndConfigurationSettings settings) {
        projectStartupTaskManager.delete(settings.getUniqueID());
      }

      @Override
      public void runConfigurationChanged(@NotNull RunnerAndConfigurationSettings settings, String existingId) {
        if (existingId != null) {
          projectStartupTaskManager.rename(existingId, settings);
        }
        projectStartupTaskManager.checkOnChange(settings);
      }

      @Override
      public void runConfigurationAdded(@NotNull RunnerAndConfigurationSettings settings) {
        projectStartupTaskManager.checkOnChange(settings);
      }
    });
    scheduleRunActivities(project);
  }

  private static void scheduleRunActivities(@NotNull Project project) {
    JobScheduler.getScheduler().schedule(() -> {
      if (!((StartupManagerEx)StartupManager.getInstance(project)).postStartupActivityPassed() && !Registry.is("dumb.aware.run.configurations")) {
        scheduleRunActivities(project);
      }
      else {
        runActivities(project);
      }
    }, DELAY_MILLIS, TimeUnit.MILLISECONDS);
  }

  private static void runActivities(final Project project) {
    final ProjectStartupTaskManager projectStartupTaskManager = ProjectStartupTaskManager.getInstance(project);
    final List<RunnerAndConfigurationSettings> configurations =
      new ArrayList<>(projectStartupTaskManager.getLocalConfigurations());
    configurations.addAll(projectStartupTaskManager.getSharedConfigurations());

    ApplicationManager.getApplication().invokeLater(() -> {
      long pause = 0;
      final Alarm alarm = new Alarm(Alarm.ThreadToUse.SWING_THREAD, project);
      final Executor executor = DefaultRunExecutor.getRunExecutorInstance();
      for (final RunnerAndConfigurationSettings configuration : configurations) {
        if (! canBeRun(configuration)) {
          showNotification(project, "Run Configuration '" + configuration.getName() + "' can not be started with 'Run' action.", MessageType.ERROR);
          return;
        }

        try {
          alarm.addRequest(new MyExecutor(executor, configuration, alarm), pause);
        }
        catch (ExecutionException e) {
          showNotification(project, e.getMessage(), MessageType.ERROR);
        }
        pause = MyExecutor.PAUSE;
      }
    });
  }

  private static void showNotification(Project project, String text, MessageType type) {
    ProjectStartupTaskManager.NOTIFICATION_GROUP.createNotification(ProjectStartupTaskManager.PREFIX + " " + text, type).notify(project);
  }

  private static class MyExecutor implements Runnable {
    public static final int ATTEMPTS = 10;
    private final ExecutionEnvironment myEnvironment;
    @NotNull private final Alarm myAlarm;
    private final Project myProject;
    private int myCnt = ATTEMPTS;
    private final static long PAUSE = 300;
    private final String myName;

    public MyExecutor(@NotNull final Executor executor, @NotNull final RunnerAndConfigurationSettings configuration,
                      @NotNull Alarm alarm) throws ExecutionException {
      myName = configuration.getName();
      myProject = configuration.getConfiguration().getProject();
      myAlarm = alarm;
      myEnvironment = ExecutionEnvironmentBuilder.create(executor, configuration).contentToReuse(null).dataContext(null)
        .activeTarget().build();
    }

    @Override
    public void run() {
      if (ExecutorRegistry.getInstance().isStarting(myEnvironment)) {
        if (myCnt <= 0) {
          showNotification(myProject, "'" + myName + "' not started after " + ATTEMPTS + " attempts.", MessageType.ERROR);
          return;
        }
        --myCnt;
        myAlarm.addRequest(this, PAUSE);
      }
      // reporting that the task successfully started would require changing the interface of execution subsystem, not it reports errors by itself
      ProjectStartupTaskManager.NOTIFICATION_GROUP
        .createNotification(ProjectStartupTaskManager.PREFIX + " starting '" + myName + "'", MessageType.INFO).notify(myProject);
      ProgramRunnerUtil.executeConfiguration(myEnvironment, true, true);
      // same thread always
      if (myAlarm.isEmpty()) Disposer.dispose(myAlarm);
    }
  }

  public static boolean canBeRun(@NotNull RunnerAndConfigurationSettings configuration) {
    return RunnerRegistry.getInstance().getRunner(DefaultRunExecutor.EXECUTOR_ID, configuration.getConfiguration()) != null;
  }
}
