/*
 * Copyright 2018 The Bazel Authors. All rights reserved.
 *
 * 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.google.idea.blaze.android.run.runner;

import com.android.tools.idea.run.AndroidSessionInfo;
import com.android.tools.idea.run.DeviceFutures;
import com.android.tools.idea.run.editor.DeployTarget;
import com.android.tools.idea.run.editor.DeployTargetState;
import com.intellij.execution.ExecutionException;
import com.intellij.execution.Executor;
import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.icons.AllIcons;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.Messages;
import javax.annotation.Nullable;
import org.jetbrains.android.facet.AndroidFacet;

/** Selects a device. */
public interface BlazeAndroidDeviceSelector {

  /** A device session */
  class DeviceSession {
    @Nullable public final DeployTarget deployTarget;
    @Nullable public final DeviceFutures deviceFutures;
    @Nullable public final AndroidSessionInfo sessionInfo;

    public DeviceSession(
        @Nullable DeployTarget deployTarget,
        @Nullable DeviceFutures deviceFutures,
        @Nullable AndroidSessionInfo sessionInfo) {
      this.deployTarget = deployTarget;
      this.deviceFutures = deviceFutures;
      this.sessionInfo = sessionInfo;
    }
  }

  DeviceSession getDevice(
      Project project,
      AndroidFacet facet,
      BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager,
      Executor executor,
      ExecutionEnvironment env,
      AndroidSessionInfo info,
      boolean debug,
      int runConfigId)
      throws ExecutionException;

  /** Standard device selector */
  class NormalDeviceSelector implements BlazeAndroidDeviceSelector {

    private static final DialogWrapper.DoNotAskOption ourKillLaunchOption =
        new KillLaunchDialogOption();
    private static final Logger LOG = Logger.getInstance(NormalDeviceSelector.class);

    static class KillLaunchDialogOption implements DialogWrapper.DoNotAskOption {
      private boolean show;

      @Override
      public boolean isToBeShown() {
        return !show;
      }

      @Override
      public void setToBeShown(boolean toBeShown, int exitCode) {
        show = !toBeShown;
      }

      @Override
      public boolean canBeHidden() {
        return true;
      }

      @Override
      public boolean shouldSaveOptionsOnCancel() {
        return true;
      }

      @Override
      public String getDoNotShowMessage() {
        return "Do not ask again";
      }
    }

    @Override
    public DeviceSession getDevice(
        Project project,
        AndroidFacet facet,
        BlazeAndroidRunConfigurationDeployTargetManager deployTargetManager,
        Executor executor,
        ExecutionEnvironment env,
        AndroidSessionInfo info,
        boolean debug,
        int runConfigId)
        throws ExecutionException {
      // If there is an existing session, then terminate those sessions
      if (info != null) {
        boolean continueLaunch = promptAndKillSession(executor, project, info);
        if (!continueLaunch) {
          return null;
        }
      }

      DeployTarget deployTarget =
          deployTargetManager.getDeployTarget(executor, env, facet, runConfigId);
      if (deployTarget == null) {
        return null;
      }

      DeviceFutures deviceFutures = null;
      DeployTargetState deployTargetState = deployTargetManager.getCurrentDeployTargetState();
      if (!deployTarget.hasCustomRunProfileState(executor)) {
        deviceFutures =
            deployTarget.getDevices(
                deployTargetState, facet, deployTargetManager.getDeviceCount(), debug, runConfigId);
      }
      return new DeviceSession(deployTarget, deviceFutures, info);
    }

    private boolean promptAndKillSession(
        Executor executor, Project project, AndroidSessionInfo info) {
      String previousExecutor = info.getExecutorId();
      String currentExecutor = executor.getId();

      if (ourKillLaunchOption.isToBeShown()) {
        String msg;
        String noText;
        if (previousExecutor.equals(currentExecutor)) {
          msg =
              String.format(
                  "Restart App?\nThe app is already running. "
                      + "Would you like to kill it and restart the session?");
          noText = "Cancel";
        } else {
          msg =
              String.format(
                  "To switch from %1$s to %2$s, the app has to restart. Continue?",
                  previousExecutor, currentExecutor);
          noText = "Cancel " + currentExecutor;
        }

        String targetName = info.getExecutionTarget().getDisplayName();
        String title = "Launching " + targetName;
        String yesText = "Restart " + targetName;
        if (Messages.NO
            == Messages.showYesNoDialog(
                project,
                msg,
                title,
                yesText,
                noText,
                AllIcons.General.QuestionDialog,
                ourKillLaunchOption)) {
          return false;
        }
      }

      LOG.info("Disconnecting existing session of the same launch configuration");
      info.getProcessHandler().detachProcess();
      return true;
    }
  }
}