/*
 * 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.ide.actions;

import com.intellij.execution.ExecutionException;
import com.intellij.execution.configurations.GeneralCommandLine;
import com.intellij.execution.process.ProcessOutput;
import com.intellij.execution.util.ExecUtil;
import com.intellij.notification.Notification;
import com.intellij.notification.NotificationType;
import com.intellij.notification.Notifications;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.application.*;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ExceptionUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.io.IOException;
import java.util.Locale;
import java.util.Map;

import static com.intellij.openapi.util.Pair.pair;
import static com.intellij.util.containers.ContainerUtil.newHashMap;

/**
 * @author yole
 */
public class CreateLauncherScriptAction extends DumbAwareAction {
  private static final Logger LOG = Logger.getInstance(CreateLauncherScriptAction.class);
  private static final String CONTENTS = "/Contents";

  public static boolean isAvailable() {
    return SystemInfo.isUnix;
  }

  @Override
  public void update(@NotNull AnActionEvent event) {
    boolean enabled = isAvailable();
    event.getPresentation().setEnabledAndVisible(enabled);
  }

  @Override
  public void actionPerformed(@NotNull AnActionEvent event) {
    if (!isAvailable()) return;

    Project project = event.getProject();

    String title = ApplicationBundle.message("launcher.script.title");
    String prompt =
      "<html>You can create a launcher script to enable opening files and projects in " +
      ApplicationNamesInfo.getInstance().getFullProductName() + " from the command line.<br>" +
      "Please specify the name of the script and the path where it should be created:</html>";
    String path = Messages.showInputDialog(project, prompt, title, null, defaultScriptPath(), null);
    if (path == null) {
      return;
    }

    if (!path.startsWith("/")) {
      String home = System.getenv("HOME");
      if (home != null && new File(home).isDirectory()) {
        if (path.startsWith("~")) {
          path = home + path.substring(1);
        }
        else {
          path = home + "/" + path;
        }
      }
    }

    File target = new File(path);
    if (target.exists()) {
      String message = ApplicationBundle.message("launcher.script.overwrite", target);
      if (Messages.showOkCancelDialog(project, message, title, Messages.getQuestionIcon()) != Messages.OK) {
        return;
      }
    }

    try {
      createLauncherScript(target.getAbsolutePath());
    }
    catch (Exception e) {
      reportFailure(e, project);
    }
  }

  public static void createLauncherScript(@NotNull String pathName) throws Exception {
    if (!isAvailable()) return;

    File scriptFile = createLauncherScriptFile();
    try {
      File scriptTarget = new File(pathName);

      File scriptTargetDir = scriptTarget.getParentFile();
      assert scriptTargetDir != null : "path: " + pathName;

      if (!(scriptTargetDir.exists() || scriptTargetDir.mkdirs()) || !scriptFile.renameTo(scriptTarget)) {
        String scriptTargetDirPath = scriptTargetDir.getCanonicalPath();
        // copy file and change ownership to root (UID 0 = root, GID 0 = root (wheel on Macs))
        String installationScriptSrc =
          "#!/bin/sh\n" +
          "mkdir -p \"" + scriptTargetDirPath + "\"\n" +
          "install -g 0 -o 0 \"" + scriptFile.getCanonicalPath() + "\" \"" + pathName + "\"";
        File installationScript = ExecUtil.createTempExecutableScript("launcher_installer", ".sh", installationScriptSrc);
        String prompt = ApplicationBundle.message("launcher.script.sudo.prompt", scriptTargetDirPath);
        ProcessOutput result = ExecUtil.sudoAndGetOutput(new GeneralCommandLine(installationScript.getPath()), prompt);
        int exitCode = result.getExitCode();
        if (exitCode != 0) {
          String message = "Launcher script creation failed with " + exitCode;
          String output = result.getStdout();
          if (!StringUtil.isEmptyOrSpaces(output)) message += "\nOutput: " + output.trim();
          throw new RuntimeException(message);
        }
      }
    }
    finally {
      if (scriptFile.exists()) {
        FileUtil.delete(scriptFile);
      }
    }
  }

  public static void reportFailure(@NotNull Exception e, @Nullable final Project project) {
    LOG.warn(e);
    final String message = ExceptionUtil.getNonEmptyMessage(e, "Internal error");
    Notifications.Bus.notify(
      new Notification(Notifications.SYSTEM_MESSAGES_GROUP_ID, "Launcher Script Creation Failed", message, NotificationType.ERROR),
      project);
  }

  private static File createLauncherScriptFile() throws IOException, ExecutionException {
    String runPath = SystemInfo.isMac ? StringUtil.trimEnd(PathManager.getHomePath(), CONTENTS) : CreateDesktopEntryAction.getLauncherScript();
    if (runPath == null) throw new IOException(ApplicationBundle.message("desktop.entry.script.missing", PathManager.getBinPath()));

    ClassLoader loader = CreateLauncherScriptAction.class.getClassLoader();
    assert loader != null;
    Map<String, String> variables = newHashMap(
      pair("$CONFIG_PATH$", PathManager.getConfigPath()),
      pair("$SYSTEM_PATH$", PathManager.getSystemPath()),
      pair("$RUN_PATH$", runPath));
    String launcherContents = StringUtil.convertLineSeparators(ExecUtil.loadTemplate(loader, "launcher.py", variables));

    return ExecUtil.createTempExecutableScript("launcher", "", launcherContents);
  }

  public static String defaultScriptPath() {
    String scriptName = ApplicationNamesInfo.getInstance().getDefaultLauncherName();
    if (StringUtil.isEmptyOrSpaces(scriptName)) scriptName = ApplicationNamesInfo.getInstance().getProductName().toLowerCase(Locale.US);
    return "/usr/local/bin/" + scriptName;
  }
}