content: package com.antfortune.freeline.idea.utils;
  
  import com.antfortune.freeline.idea.actions.UpdateAction;
  
  import com.android.tools.idea.gradle.dsl.model.GradleBuildModel;
  import com.android.tools.idea.gradle.dsl.model.dependencies.ArtifactDependencyModel;
  import com.android.tools.idea.gradle.dsl.model.dependencies.ArtifactDependencySpec;
  import com.android.tools.idea.gradle.parser.GradleBuildFile;
  import com.intellij.execution.ExecutionException;
  import com.intellij.execution.configurations.GeneralCommandLine;
  import com.intellij.execution.process.OSProcessHandler;
  import com.intellij.execution.process.ProcessHandler;
  import com.intellij.execution.process.ProcessTerminatedListener;
  import com.intellij.execution.ui.ConsoleView;
  import com.intellij.openapi.application.ApplicationManager;
  import com.intellij.openapi.command.CommandProcessor;
  import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId;
  import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter;
  import com.intellij.openapi.module.Module;
  import com.intellij.openapi.module.ModuleManager;
  import com.intellij.openapi.project.Project;
  import com.intellij.openapi.ui.DialogBuilder;
  import com.intellij.openapi.ui.DialogWrapper;
  import com.intellij.openapi.ui.Messages;
  import com.intellij.openapi.util.Pair;
  import com.intellij.openapi.util.SystemInfo;
  import com.intellij.openapi.vfs.VirtualFile;
  import com.intellij.openapi.wm.ToolWindow;
  import com.intellij.openapi.wm.ToolWindowAnchor;
  import com.intellij.openapi.wm.ToolWindowManager;
  import com.intellij.psi.PsiFile;
  import com.intellij.ui.content.impl.ContentImpl;
  import com.antfortune.freeline.idea.icons.PluginIcons;
  import com.antfortune.freeline.idea.models.*;
  import org.apache.commons.io.FileUtils;
  import org.jetbrains.annotations.NotNull;
  import org.jetbrains.plugins.groovy.lang.psi.GroovyFile;
  
  import java.util.*;
  
  import javax.swing.*;
  import java.awt.event.ActionEvent;
  import java.io.File;
  import java.io.IOException;
  import java.util.regex.Matcher;
  import java.util.regex.Pattern;
  
  /**
   * Freeline Utility
   *
   * @author act262@gmail.com
   */
  public class FreelineUtil {
  
      // TODO: 2016/9/13 0013 need refactor tool window
      private final static String TOOL_ID = "Freeline Console";
  
      public static void build(Project project) {
          GeneralCommandLine commandLine = new GeneralCommandLine();
          commandLine.setWorkDirectory(project.getBasePath());
          commandLine.setExePath("python");
          commandLine.addParameter("freeline.py");
          // debug
          commandLine.addParameter("-d");
  
          // commands process
          try {
              processCommandline(project, commandLine);
          } catch (ExecutionException e) {
              e.printStackTrace();
          }
      }
  
      /* process command line */
      private static void processCommandline(final Project project, GeneralCommandLine commandLine) throws ExecutionException {
          final OSProcessHandler processHandler = new OSProcessHandler(commandLine);
          ProcessTerminatedListener.attach(processHandler);
          processHandler.startNotify();
  
          ApplicationManager.getApplication().invokeLater(new Runnable() {
              @Override
              public void run() {
                  processConsole(project, processHandler);
              }
          });
      }
  
      /* process attach to console,show the log */
      // TODO: 2016/9/14 0014 need refactor console method
      private static void processConsole(Project project, ProcessHandler processHandler) {
          ConsoleView consoleView = FreeUIManager.getInstance(project).getConsoleView(project);
          consoleView.clear();
          consoleView.attachToProcess(processHandler);
  
          ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(project);
          ToolWindow toolWindow;
          toolWindow = toolWindowManager.getToolWindow(TOOL_ID);
  
          // if already exist tool window then show it
          if (toolWindow != null) {
              toolWindow.show(null);
              return;
          }
  
          toolWindow = toolWindowManager.registerToolWindow(TOOL_ID, true, ToolWindowAnchor.BOTTOM);
          toolWindow.setTitle("free....");
          toolWindow.setStripeTitle("Free Console");
          toolWindow.setShowStripeButton(true);
          toolWindow.setIcon(PluginIcons.ICON_TOOL_WINDOW);
          toolWindow.getContentManager().addContent(new ContentImpl(consoleView.getComponent(), "Build", true));
          toolWindow.show(null);
      }
  
      /**
       * if had init freeline return true
       */
      public static boolean hadInitFreeline(Project project) {
          if (project != null) {
              String projectPath = project.getBasePath();
              // freeline directory
              File freelineDir = new File(projectPath, "freeline");
              // freeline.py file
              File freeline_py = new File(projectPath, "freeline.py");
              if (freelineDir.exists() && freeline_py.exists()) {
                  return true;
              }
          }
          return false;
      }
  
      /**
       * 是否载入freeline
       *
       * @param project
       * @return
       */
      public static boolean hasInitFreeline(@NotNull Project project) {
          return getFreelineStatus(project).hasInitFreeline();
      }
  
      /**
       * 获取freeline安装状态
       *
       * @param project
       * @return
       */
      public static FreelineStatus getFreelineStatus(@NotNull Project project) {
          FreelineStatus status = new FreelineStatus();
          Collection<VirtualFile> gradleFiles = GradleUtil.getAllGradleFile(project);
          status.setGradleBuildFiles(gradleFiles);
          for (VirtualFile file : gradleFiles) {
              if (!status.isExistClasspath()) {
                  GradleBuildModel model = GradleBuildModel.parseBuildFile(file, project);
                  if (model != null) {
                      List<ArtifactDependencyModel> classPaths = model.buildscript().dependencies().artifacts();
                      for (ArtifactDependencyModel classpath : classPaths) {
                          ArtifactDependencyModelWrapper wrapper = new ArtifactDependencyModelWrapper(classpath);
                          if (wrapper.group().equals(Constant.FREELINE_CLASSPATH_GROUP)
                                  && wrapper.name().equals(Constant.FREELINE_CLASSPATH_ARTIFACT)) {
                              status.setClasspathFile(file);
                              break;
                          }
                      }
                  }
              }
              // 正则二次判断是否存在Freeline classpath
              if (!status.isExistClasspath() && regularExistFreelineClassPath(file)) {
                  status.setClasspathFile(file);
              }
              if (!status.isExistPlugin()) {
                  GradleBuildFile gradleBuildFile = new GradleBuildFile(file, project);
                  if (gradleBuildFile != null) {
                      List<String> plugins = gradleBuildFile.getPlugins();
                      if (plugins.contains(Constant.FREELINE_PLUGIN_ID)) {
                          status.setPluginFile(file);
                      }
                  }
              }
              if (status.isExistClasspath() && status.isExistPlugin()) {
                  break;
              }
          }
          File baseFile = new File(project.getBasePath());
          if (new File(baseFile, Constant.FREELINE_ROOT_FOLDER).exists()
                  && new File(baseFile, Constant.FREELINE_PYTHON).exists()) {
              if (SystemInfo.isWindows) {
                  if (new File(baseFile, Constant.FREELINE_ROOT_FOLDER_CORE).exists()) {
                      status.setExistFreelineCore(true);
                  }
              } else {
                  status.setExistFreelineCore(true);
              }
          }
          return status;
      }
  
      /**
       * 检查是否需要载入Freeline
       *
       * @param project
       * @return
       */
      public static boolean checkInstall(@NotNull final Project project) {
          final FreelineStatus status = getFreelineStatus(project);
          if (GradleUtil.isSyncInProgress(project)) {
              NotificationUtils.errorMsgDialog("Waiting for sync project to complete");
              return false;
          }
          if (status.hasInitFreeline()) {
              return true;
          }
          if (status.getGradleBuildFiles().size() < 1) {
              NotificationUtils.errorMsgDialog("It's not an Android Gradle project Currently?");
              return false;
          }
          if (status.isExistClasspath() && status.isExistPlugin() && !status.isExistFreelineCore()) {
              NotificationUtils.errorNotification("Execute task initFreeline and download freeline dependencies...");
              initFreeline(project);
              return false;
          }
          if (DialogUtil.createDialog("Detected that you did not installFreeline Freeline, Whether installFreeline Automatically？",
                  "Install Freeline Automatically", "Cancel")) {
              Module[] modules = ModuleManager.getInstance(project).getModules();
              List<Pair<Module, PsiFile>> selectModulesList = new ArrayList<Pair<Module, PsiFile>>();
              for (Module module : modules) {
                  GradleBuildFile file = GradleBuildFile.get(module);
                  if (file != null && !GradleUtil.isLibrary(file)) {
                      selectModulesList.add(Pair.create(module, file.getPsiFile()));
                  }
              }
              // 多个app模块的情况
              if (selectModulesList.size() > 1) {
                  final DialogBuilder builder = new DialogBuilder();
                  builder.setTitle("Install Freeline");
                  builder.resizable(false);
                  builder.setCenterPanel(new JLabel("There are multiple application modules, Please select the module to be installed Freeline.",
                          Messages.getInformationIcon(), SwingConstants.CENTER));
                  builder.addOkAction().setText("Cancel");
                  for (final Pair<Module, PsiFile> pair : selectModulesList) {
                      builder.addAction(new AbstractAction(":" + pair.first.getName()) {
                          @Override
                          public void actionPerformed(ActionEvent e) {
                              builder.getDialogWrapper().close(DialogWrapper.CANCEL_EXIT_CODE);
                              installFreeline(project, status, pair.getSecond());
                          }
                      });
                  }
                  if (builder.show() > -1) {
                      return false;
                  }
              } else if (selectModulesList.size() == 1) {
                  installFreeline(project, status, selectModulesList.get(0).getSecond());
              } else {
                  NotificationUtils.errorMsgDialog("Can not found Application Module! Please Sync Project.");
                  return false;
              }
          }
          return false;
      }
  
      /**
       * 载入Freeline
       *
       * @param project
       * @param status
       * @param psiFile
       */
      private static void installFreeline(final Project project, final FreelineStatus status, final PsiFile psiFile) {
          ApplicationManager.getApplication().executeOnPooledThread(new UpdateAction.GetServerVersion(new GetServerCallback() {
              @Override
              public void onSuccess(final GradleDependencyEntity entity) {
                  LogUtil.d("获取版本号成功:" + entity);
                  ApplicationManager.getApplication().invokeLater(new Runnable() {
                      @Override
                      public void run() {
                          installFreeline(project, status, psiFile, entity);
                      }
                  });
              }
  
              @Override
              public void onFailure(String errMsg) {
                  LogUtil.d("获取版本号失败:" + errMsg);
                  NotificationUtils.errorNotification("Get Freeline Version Failure: " + errMsg);
              }
          }));
      }
  
      private static boolean needReformatCode = false;
  
      private static void installFreeline(final Project project, final FreelineStatus status, final PsiFile psiFile,
                                          final GradleDependencyEntity dependencyEntity) {
          needReformatCode = false;
          CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
              @Override
              public void run() {
                  ApplicationManager.getApplication().runWriteAction(new Runnable() {
                      @Override
                      public void run() {
                          if (!status.isExistClasspath()) {
                              Collection<VirtualFile> collection = status.getGradleBuildFiles();
                              if (dependencyEntity != null) {
                                  for (VirtualFile file : collection) {
                                      GradleBuildModel model = GradleBuildModel.parseBuildFile(file, project);
                                      List<ArtifactDependencyModel> artifactDependencyModels = model.buildscript().dependencies().artifacts();
                                      for (ArtifactDependencyModel model1 : artifactDependencyModels) {
                                          ArtifactDependencyModelWrapper wrapper = new ArtifactDependencyModelWrapper(model1);
                                          if (wrapper.group().equals(Constant.ANDROID_GRADLE_TOOL_GROUP_NAME)) {
                                              ArtifactDependencySpec spec = new ArtifactDependencySpec(dependencyEntity.getArtifactId(),
                                                      dependencyEntity.getGroupId(), dependencyEntity.getNewestReleaseVersion());
                                              model.buildscript().dependencies().addArtifact("classpath", spec);
                                              model.applyChanges();
                                              needReformatCode = true;
                                              status.setClasspathFile(file);
                                              break;
                                          }
                                      }
                                      if (status.isExistClasspath()) {
                                          break;
                                      }
                                  }
                              }
                          }
                          if (!status.isExistPlugin()) {
                              if (psiFile != null && psiFile instanceof GroovyFile) {
                                  GradleUtil.applyPlugin(project, (GroovyFile) psiFile, Constant.FREELINE_PLUGIN_ID);
                              }
                          }
                      }
                  });
              }
          });
          if (needReformatCode && status.getClasspathFile() != null) {
              DocumentUtil.reformatCode(project, status.getClasspathFile());
          }
          LogUtil.d("Sync Project Finish, start download freeline.zip.");
          initFreeline(project);
      }
  
      public static final Pattern PATTERN_CLASSPATH = Pattern.compile("classpath\\s+'"
              + Constant.FREELINE_CLASSPATH_GROUP + ":" + Constant.FREELINE_CLASSPATH_ARTIFACT + ":[\\d|\\.]*'");
  
      /**
       * 正则二次判断是否存在Freeline classpath
       *
       * @param file
       * @return
       */
      public static boolean regularExistFreelineClassPath(VirtualFile file) {
          try {
              if (file.exists()) {
                  String content = FileUtils.readFileToString(new File(file.getPath()));
                  Matcher matcher = PATTERN_CLASSPATH.matcher(content);
                  return matcher.find();
              }
          } catch (IOException e) {
              e.printStackTrace();
          }
          return false;
      }
  
      /**
       * 执行./gradlew initFreeline
       * @param project
       */
      public static void initFreeline(Project project) {
          GradleUtil.executeTask(project, "initFreeline", "-Pmirror", new ExternalSystemTaskNotificationListenerAdapter() {
              @Override
              public void onTaskOutput(@NotNull ExternalSystemTaskId id, @NotNull String text, boolean stdOut) {
                  super.onTaskOutput(id, text, stdOut);
              }
          });
      }
  }
  