/*
 * Copyright 2000-2010 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 org.jetbrains.idea.maven.tasks;

import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.externalSystem.service.project.manage.ExternalSystemKeymapExtension;
import com.intellij.openapi.keymap.KeymapGroup;
import com.intellij.openapi.keymap.KeymapGroupFactory;
import com.intellij.openapi.keymap.impl.ui.Group;
import com.intellij.openapi.keymap.impl.ui.Hyperlink;
import com.intellij.openapi.keymap.impl.ui.KeymapListener;
import com.intellij.openapi.keymap.impl.ui.KeymapPanel;
import com.intellij.openapi.options.ex.Settings;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfoRt;
import icons.MavenIcons;
import org.jetbrains.annotations.TestOnly;
import org.jetbrains.idea.maven.execution.MavenRunConfigurationType;
import org.jetbrains.idea.maven.execution.MavenRunnerParameters;
import org.jetbrains.idea.maven.model.MavenConstants;
import org.jetbrains.idea.maven.model.MavenExplicitProfiles;
import org.jetbrains.idea.maven.model.MavenPlugin;
import org.jetbrains.idea.maven.navigator.MavenProjectsStructure;
import org.jetbrains.idea.maven.navigator.SelectMavenGoalDialog;
import org.jetbrains.idea.maven.project.MavenProject;
import org.jetbrains.idea.maven.project.MavenProjectsManager;
import org.jetbrains.idea.maven.utils.MavenArtifactUtil;
import org.jetbrains.idea.maven.utils.MavenPluginInfo;
import org.jetbrains.idea.maven.utils.actions.MavenAction;
import org.jetbrains.idea.maven.utils.actions.MavenActionUtil;

import javax.swing.*;
import java.awt.event.MouseEvent;
import java.io.File;
import java.util.*;

public class MavenKeymapExtension implements ExternalSystemKeymapExtension.ActionsProvider {

  @Override
  public KeymapGroup createGroup(Condition<AnAction> condition, final Project project) {
    KeymapGroup result = KeymapGroupFactory.getInstance().createGroup(TasksBundle.message("maven.tasks.action.group.name"),
                                                                      MavenIcons.MavenLogo
    );
    if (project == null) return result;

    Comparator<MavenProject> projectComparator = (o1, o2) -> o1.getDisplayName().compareToIgnoreCase(o2.getDisplayName());
    Map<MavenProject, Set<Pair<String, String>>> projectToActionsMapping
      = new TreeMap<>(projectComparator);

    ActionManager actionManager = ActionManager.getInstance();
    //noinspection TestOnlyProblems
    for (String eachId : actionManager.getActionIds(getActionPrefix(project, null))) {
      AnAction eachAction = actionManager.getAction(eachId);

      if (!(eachAction instanceof MavenGoalAction)) continue;
      if (condition != null && !condition.value(actionManager.getActionOrStub(eachId))) continue;

      MavenGoalAction mavenAction = (MavenGoalAction)eachAction;
      MavenProject mavenProject = mavenAction.getMavenProject();
      Set<Pair<String, String>> actions = projectToActionsMapping.get(mavenProject);
      if (actions == null) {
        final List<String> projectGoals = collectGoals(mavenProject);
        actions = new TreeSet<>((o1, o2) -> {
          String goal1 = o1.getFirst();
          String goal2 = o2.getFirst();
          int index1 = projectGoals.indexOf(goal1);
          int index2 = projectGoals.indexOf(goal2);
          if (index1 == index2) return goal1.compareToIgnoreCase(goal2);
          return index1 < index2 ? -1 : 1;
        });
        projectToActionsMapping.put(mavenProject, actions);
      }
      actions.add(Pair.create(mavenAction.getGoal(), eachId));
    }

    for (Map.Entry<MavenProject, Set<Pair<String, String>>> each : projectToActionsMapping.entrySet()) {
      Set<Pair<String, String>> goalsToActionIds = each.getValue();
      for (Pair<String, String> eachGoalToActionId : goalsToActionIds) {
        result.addActionId(eachGoalToActionId.getSecond());
      }
    }

    Icon icon = AllIcons.General.Add;
    ((Group)result).addHyperlink(new Hyperlink(icon, "Choose a phase/goal to assign a shortcut") {
      @Override
      public void onClick(MouseEvent e) {
        SelectMavenGoalDialog dialog = new SelectMavenGoalDialog(project);
        if (dialog.showAndGet() && dialog.getResult() != null) {
          MavenProjectsStructure.GoalNode goalNode = dialog.getResult();
          String goal = goalNode.getGoal();
          String actionId = MavenShortcutsManager.getInstance(project).getActionId(goalNode.getProjectPath(), goal);
          getOrRegisterAction(goalNode.getMavenProject(), actionId, goal);

          ApplicationManager.getApplication().getMessageBus().syncPublisher(KeymapListener.CHANGE_TOPIC).processCurrentKeymapChanged();
          Settings allSettings = Settings.KEY.getData(DataManager.getInstance().getDataContext(e.getComponent()));
          KeymapPanel keymapPanel = allSettings != null ? allSettings.find(KeymapPanel.class) : null;
          if (keymapPanel != null) {
            // clear actions filter
            keymapPanel.showOption("");
            keymapPanel.selectAction(actionId);
          }
        }
      }
    });

    return result;
  }

  public static void updateActions(Project project, List<MavenProject> mavenProjects) {
    clearActions(project, mavenProjects);
    createActions(project, mavenProjects);
  }

  public static MavenAction getOrRegisterAction(MavenProject mavenProject, String actionId, String goal) {
    MavenGoalAction mavenGoalAction = new MavenGoalAction(mavenProject, goal);
    ActionManager manager = ActionManager.getInstance();
    AnAction anAction = manager.getAction(actionId);
    if (anAction instanceof MavenGoalAction) {
      return (MavenGoalAction)anAction;
    }
    manager.unregisterAction(actionId);
    manager.registerAction(actionId, mavenGoalAction);
    return mavenGoalAction;
  }

  private static void createActions(Project project, List<MavenProject> mavenProjects) {
    ActionManager actionManager = ActionManager.getInstance();
    MavenShortcutsManager shortcutsManager = MavenShortcutsManager.getInstance(project);
    for (MavenProject eachProject : mavenProjects) {
      //noinspection TestOnlyProblems
      String actionIdPrefix = getActionPrefix(project, eachProject);
      for (MavenGoalAction eachAction : collectActions(eachProject)) {
        String id = actionIdPrefix + eachAction.getGoal();
        actionManager.unregisterAction(id);
        if(shortcutsManager.hasShortcuts(eachProject, eachAction.getGoal())) {
          actionManager.registerAction(id, eachAction);
        }
      }
    }
  }

  private static List<MavenGoalAction> collectActions(MavenProject mavenProject) {
    List<MavenGoalAction> result = new ArrayList<>();
    for (String eachGoal : collectGoals(mavenProject)) {
      result.add(new MavenGoalAction(mavenProject, eachGoal));
    }
    return result;
  }

  public static void clearActions(Project project) {
    ActionManager manager = ActionManager.getInstance();
    //noinspection TestOnlyProblems
    for (String each : manager.getActionIds(getActionPrefix(project, null))) {
      manager.unregisterAction(each);
    }
  }

  public static void clearActions(Project project, List<MavenProject> mavenProjects) {
    ActionManager manager = ActionManager.getInstance();
    for (MavenProject eachProject : mavenProjects) {
      //noinspection TestOnlyProblems
      for (String eachAction : manager.getActionIds(getActionPrefix(project, eachProject))) {
        manager.unregisterAction(eachAction);
      }
    }
  }

  private static List<String> collectGoals(MavenProject project) {
    LinkedHashSet<String> result = new LinkedHashSet<>(MavenConstants.PHASES); // may contains similar plugins or something

    for (MavenPlugin each : project.getDeclaredPlugins()) {
      collectGoals(project.getLocalRepository(), each, result);
    }

    return new ArrayList<>(result);
  }

  private static void collectGoals(File repository, MavenPlugin plugin, LinkedHashSet<String> list) {
    MavenPluginInfo info = MavenArtifactUtil.readPluginInfo(repository, plugin.getMavenId());
    if (info == null) return;

    for (MavenPluginInfo.Mojo m : info.getMojos()) {
      list.add(m.getQualifiedGoal());
    }
  }

  @TestOnly
  public static String getActionPrefix(Project project, MavenProject mavenProject) {
    String pomPath = mavenProject == null ? null : mavenProject.getPath();
    return MavenShortcutsManager.getInstance(project).getActionId(pomPath, null);
  }

  private static class MavenGoalAction extends MavenAction {
    private final MavenProject myMavenProject;
    private final String myGoal;

    public MavenGoalAction(MavenProject mavenProject, String goal) {
      myMavenProject = mavenProject;
      myGoal = goal;
      Presentation template = getTemplatePresentation();
      template.setText(goal + " (" + mavenProject.getMavenId() + ")", false);
      template.setIcon(MavenIcons.Phase);
    }

    public void actionPerformed(AnActionEvent e) {
      final DataContext context = e.getDataContext();
      final Project project = MavenActionUtil.getProject(context);
      if (project == null) return;

      final MavenProjectsManager projectsManager = MavenActionUtil.getProjectsManager(context);
      if(projectsManager == null) return;
      MavenExplicitProfiles explicitProfiles = projectsManager.getExplicitProfiles();
      MavenRunnerParameters params = new MavenRunnerParameters(true,
                                                               myMavenProject.getDirectory(),
                                                               myMavenProject.getFile().getName(),
                                                               Collections.singletonList(myGoal),
                                                               explicitProfiles.getEnabledProfiles(),
                                                               explicitProfiles.getDisabledProfiles());
      MavenRunConfigurationType.runConfiguration(project, params, null);
    }

    public MavenProject getMavenProject() {
      return myMavenProject;
    }

    public String getGoal() {
      return myGoal;
    }

    public String toString() {
      return myMavenProject + ":" + myGoal;
    }
  }
}
