package jetbrains.mps.ide.depanalyzer;

/*Generated by MPS */

import jetbrains.mps.ide.ui.tree.MPSTree;
import com.intellij.openapi.actionSystem.DataProvider;
import jetbrains.mps.project.Project;
import org.jetbrains.mps.openapi.module.SModule;
import jetbrains.mps.ide.ui.tree.MPSTreeNode;
import jetbrains.mps.ide.ui.tree.TextTreeNode;
import jetbrains.mps.ide.icons.GlobalIconManager;
import jetbrains.mps.smodel.ModelReadRunnable;
import jetbrains.mps.ide.ui.tree.TreeMessage;
import jetbrains.mps.nodeEditor.MPSColors;
import java.awt.Color;
import org.jetbrains.mps.openapi.module.SModuleReference;
import jetbrains.mps.internal.collections.runtime.Sequence;
import org.jetbrains.mps.util.Condition;
import java.util.List;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import com.intellij.openapi.actionSystem.ActionGroup;
import jetbrains.mps.workbench.action.ActionUtils;
import jetbrains.mps.workbench.action.BaseAction;
import com.intellij.openapi.actionSystem.ActionManager;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NonNls;
import jetbrains.mps.ide.actions.MPSCommonDataKeys;
import jetbrains.mps.ide.actions.SModuleActionData;

public class DependencyTree extends MPSTree implements DataProvider {
  private Project myProject;
  private SModule myModule;
  private boolean myShowRuntime;
  private boolean myShowUsedLanguage = true;
  public DependencyTree(Project project) {
    myProject = project;
  }
  /*package*/ Project getProject() {
    return myProject;
  }
  public void setModules(SModule module) {
    myModule = module;
  }
  public SModule getModule() {
    return myModule;
  }
  public boolean isShowRuntime() {
    return myShowRuntime;
  }
  public void setShowRuntime(boolean showRuntime) {
    myShowRuntime = showRuntime;
  }
  public boolean isShowUsedLanguage() {
    return myShowUsedLanguage;
  }
  public void setShowUsedLanguage(boolean showUsedLanguage) {
    myShowUsedLanguage = showUsedLanguage;
  }
  @Override
  protected MPSTreeNode rebuild() {
    if (myModule == null) {
      return new TextTreeNode("No Content");
    }
    DepLink deps = new DependencyUtil(myModule.getRepository()).trackRuntime(isShowRuntime()).build(myModule);
    TextTreeNode root = new TextTreeNode(myModule.getModuleName());
    root.setIcon(GlobalIconManager.getInstance().getIconFor(myModule));
    populate(root, deps.allDependencies());
    return root;
  }


  @Override
  protected void runRebuildAction(Runnable rebuildAction, boolean saveExpansion) {
    super.runRebuildAction(new ModelReadRunnable(myProject.getModelAccess(), rebuildAction), saveExpansion);
  }

  private void populate(MPSTreeNode root, Iterable<DepLink> allDependencies) {
    final TreeMessage DEPENDENCY_CYCLE = new TreeMessage(MPSColors.RED, "dependency cycle", null);
    final TreeMessage HAS_CYCLE = new TreeMessage(new Color(128, 0, 0), "module with dependency cycle", null);
    final TreeMessage BOOTSTRAP_DEPENDENCY = new TreeMessage(MPSColors.RED, "language with bootstrap dependency", null);

    Iterable<SModuleReference> sortedModules = Sequence.fromIterable(allDependencies).select((it) -> it.module).distinct().sort((it) -> it.getModuleName(), true);

    final CycleBuilder cbDeps = new CycleBuilder(new Condition<DepLink>() {
      public boolean met(DepLink dl) {
        return dl.role.isDependency();
      }
    });
    for (final SModuleReference module : Sequence.fromIterable(sortedModules)) {
      Iterable<DepLink> moduleDeps = Sequence.fromIterable(allDependencies).where((it) -> module.equals(it.module) && it.role.isDependency()).distinct();
      if (Sequence.fromIterable(moduleDeps).isEmpty()) {
        continue;
      }
      ModuleDependencyNode n = new ModuleDependencyNode(module, moduleDeps, false);
      n.updateIcon(myModule.getRepository());
      if (module.equals(myModule.getModuleReference())) {
        n.addTreeMessage(DEPENDENCY_CYCLE);
      } else {
        // if there's any dependency with loop to itself, and role of each element of this path isDependency, then it's dependency cycle
        //  NOTE, selectMany ends up as TranslatingSequence, it we don't want cycles to be recalculated again and again on any
        // ModuleDependencyNode.isLeaf call, shall keep it calcualted in a collection once and for all (e.g. with toList)
        List<DepPath> cycles = Sequence.fromIterable(moduleDeps).translate((dep) -> cbDeps.cyclePaths(dep)).toList();
        if (ListSequence.fromList(cycles).isNotEmpty()) {
          n.setCycles(cycles);
          n.addTreeMessage(HAS_CYCLE);
        }
      }
      root.add(n);
    }
    if (isShowUsedLanguage()) {
      MPSTreeNode usedlanguages = new TextTreeNode("Used Languages");
      final CycleBuilder cbUsedLang = new CycleBuilder(new Condition<DepLink>() {
        public boolean met(DepLink dl) {
          return dl.role.isUsedLanguage();
        }
      });
      for (final SModuleReference module : Sequence.fromIterable(sortedModules)) {
        Iterable<DepLink> usedLangDeps = Sequence.fromIterable(allDependencies).where((it) -> it.module == module && it.role.isUsedLanguage());
        if (Sequence.fromIterable(usedLangDeps).isEmpty()) {
          continue;
        }
        ModuleDependencyNode n = new ModuleDependencyNode(module, usedLangDeps, true);
        n.updateIcon(myModule.getRepository());
        if (module.equals(myModule.getModuleReference())) {
          n.addTreeMessage(BOOTSTRAP_DEPENDENCY);
        } else {
          Iterable<DepPath> cycles = Sequence.fromIterable(usedLangDeps).translate((dep) -> cbUsedLang.cyclePaths(dep));
          if (Sequence.fromIterable(cycles).isNotEmpty()) {
            n.setCycles(cycles);
            n.addTreeMessage(BOOTSTRAP_DEPENDENCY);
          }
        }
        usedlanguages.add(n);
      }
      if (usedlanguages.getChildCount() > 0) {
        root.add(usedlanguages);
      }
    }
  }
  @Override
  protected ActionGroup createPopupActionGroup(MPSTreeNode treeNode) {
    return ActionUtils.groupFromActions(((BaseAction) ActionManager.getInstance().getAction("jetbrains.mps.ide.depanalyzer.actions.ShowDependenciesInViewer_Action")), ((BaseAction) ActionManager.getInstance().getAction("jetbrains.mps.ide.depanalyzer.actions.AnalyzeModuleDependencies_Action")), ((BaseAction) ActionManager.getInstance().getAction("jetbrains.mps.ide.actions.ModuleProperties_Action")));
  }
  @Nullable
  @Override
  public Object getData(@NonNls String id) {
    if (!(getCurrentNode() instanceof ModuleDependencyNode)) {
      return null;
    }
    ModuleDependencyNode current = (ModuleDependencyNode) getCurrentNode();
    if (MPSCommonDataKeys.TREE_NODE.is(id)) {
      return current;
    }
    if (SModuleActionData.KEY.is(id)) {
      return SModuleActionData.from(current.getModule());
    }
    return null;
  }
  @Override
  protected void doInit(MPSTreeNode node, Runnable runnable) {
    super.doInit(node, new ModelReadRunnable(myProject.getModelAccess(), runnable));
  }
}
