package jetbrains.mps.java.workbench.actions;

/*Generated by MPS */

import jetbrains.mps.workbench.action.BaseAction;
import javax.swing.Icon;
import com.intellij.openapi.actionSystem.AnActionEvent;
import java.util.Map;
import jetbrains.mps.project.MPSProject;
import jetbrains.mps.ide.actions.MPSCommonDataKeys;
import org.jetbrains.annotations.NotNull;
import jetbrains.mps.baseLanguage.closures.runtime.Wrappers;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.progress.ProgressIndicator;
import jetbrains.mps.progress.ProgressMonitorAdapter;
import java.util.List;
import org.jetbrains.mps.openapi.module.SModule;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import org.jetbrains.mps.openapi.util.ProgressMonitor;
import org.jetbrains.mps.openapi.model.SModel;
import jetbrains.mps.util.IterableUtil;
import jetbrains.mps.internal.collections.runtime.Sequence;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.baseLanguage.logging.rt.LogContext;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SModelOperations;
import java.util.ArrayList;
import java.util.Set;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import java.util.HashSet;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SLinkOperations;
import jetbrains.mps.internal.collections.runtime.NotNullWhereFilter;
import jetbrains.mps.typechecking.TypecheckingFacade;
import org.jetbrains.mps.openapi.language.SAbstractConcept;
import jetbrains.mps.baseLanguage.behavior.IClassifierType__BehaviorDescriptor;
import jetbrains.mps.baseLanguage.behavior.IClassifier__BehaviorDescriptor;
import jetbrains.mps.baseLanguage.closures.runtime._FunctionTypes;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;
import org.jetbrains.mps.openapi.language.SConcept;

public class CalcClassifiersInRootsStatistic_Action extends BaseAction {
  private static final Icon ICON = null;

  public CalcClassifiersInRootsStatistic_Action() {
    super("Calc Classifiers in Roots Statistic", "", ICON);
    this.setIsAlwaysVisible(false);
    this.setExecuteOutsideCommand(true);
    updateInBackground(true);
  }
  @Override
  public boolean isDumbAware() {
    return true;
  }
  @Override
  protected boolean collectActionData(AnActionEvent event, final Map<String, Object> _params) {
    if (!(super.collectActionData(event, _params))) {
      return false;
    }
    {
      MPSProject p = event.getData(MPSCommonDataKeys.MPS_PROJECT);
      if (p == null) {
        return false;
      }
    }
    return true;
  }
  @Override
  public void doExecute(@NotNull final AnActionEvent event, final Map<String, Object> _params) {
    final Wrappers._int rootsCount = new Wrappers._int(0);
    final Wrappers._long membersOverallTime = new Wrappers._long(0);

    ProgressManager.getInstance().run(new Task.Modal(event.getData(MPSCommonDataKeys.MPS_PROJECT).getProject(), "Classifiers in roots stats", true) {
      @Override
      public void run(@NotNull ProgressIndicator indicator) {
        final ProgressMonitorAdapter progress = new ProgressMonitorAdapter(indicator);
        event.getData(MPSCommonDataKeys.MPS_PROJECT).getModelAccess().runReadAction(() -> {
          List<SModule> modules = event.getData(MPSCommonDataKeys.MPS_PROJECT).getProjectModulesWithGenerators();
          progress.start("Modules...", ListSequence.fromList(modules).count());
          for (SModule module : ListSequence.fromList(modules)) {
            ProgressMonitor subTask = progress.subTask(1);
            Iterable<SModel> models = module.getModels();
            subTask.start(module.getModuleName(), IterableUtil.asCollection(models).size());
            for (SModel m : Sequence.fromIterable(models)) {
              subTask.step(m.getName().getSimpleName());
              for (SNode node : Sequence.fromIterable(m.getRootNodes())) {
                rootsCount.value++;
                membersOverallTime.value += CalcClassifiersInRootsStatistic_Action.this.analyzeClassifiersInRoot(node, event);
              }
              subTask.advance(1);
            }
            subTask.done();
          }
          progress.done();
        });
      }
    });
    LogContext.with(CalcClassifiersInRootsStatistic_Action.class, null, event.getData(MPSCommonDataKeys.MPS_PROJECT), null).info(String.format("Members average time: %.3f", membersOverallTime.value * 0.001 / rootsCount.value));
  }
  private long analyzeClassifiersInRoot(final SNode node, final AnActionEvent event) {
    String nodeName = SNodeOperations.present(node) + "@" + SModelOperations.getModelName(SNodeOperations.getModel(node));

    StringBuilder sb = new StringBuilder();

    final Wrappers._T<List<SNode>> types = new Wrappers._T<List<SNode>>(ListSequence.fromList(new ArrayList<SNode>()));
    long typesCalcTime = CalcClassifiersInRootsStatistic_Action.this.calculateElapsedTime(() -> types.value = CalcClassifiersInRootsStatistic_Action.this.calcAllClassifierTypesInRoot(node, event), event);

    if (typesCalcTime > 1000) {
      sb.append(String.format("%s: type calc time = %.3f%n", nodeName, typesCalcTime * 0.001));
    }

    final Set<SNode> classifiers = SetSequence.fromSet(new HashSet<SNode>());
    SetSequence.fromSet(classifiers).addSequence(Sequence.fromIterable(SLinkOperations.collect(types.value, LINKS.classifier$cxMr)).where(new NotNullWhereFilter()));
    if (SetSequence.fromSet(classifiers).count() > 50) {
      sb.append(String.format("%s: classifier types = %d; classifiers = %d%n", nodeName, ListSequence.fromList(types.value).count(), SetSequence.fromSet(classifiers).count()));
    }

    final Wrappers._T<List<SNode>> members = new Wrappers._T<List<SNode>>(ListSequence.fromList(new ArrayList<SNode>()));
    long membersCalcTime = CalcClassifiersInRootsStatistic_Action.this.calculateElapsedTime(() -> members.value = CalcClassifiersInRootsStatistic_Action.this.calcAllMembersOfClassifiers(classifiers, event), event);

    if (ListSequence.fromList(members.value).count() > 4000) {
      sb.append(String.format("%s: members count = %d%n", nodeName, ListSequence.fromList(members.value).count()));
    }

    if (membersCalcTime > 500) {
      sb.append(String.format("%s: members calc time = %.3f%n", nodeName, membersCalcTime * 0.001));
    }
    if (sb.length() > 0) {
      LogContext.with(CalcClassifiersInRootsStatistic_Action.class, null, event.getData(MPSCommonDataKeys.MPS_PROJECT), null).info(sb.toString());
    }
    return membersCalcTime;
  }
  private List<SNode> calcAllClassifierTypesInRoot(final SNode rootNode, final AnActionEvent event) {

    List<SNode> result = TypecheckingFacade.getFromContext().computeIsolated(() -> {
      List<SNode> list = ListSequence.fromList(new ArrayList<SNode>());

      for (SNode node : SNodeOperations.getNodeDescendants(rootNode, null, true, new SAbstractConcept[]{})) {
        SNode type = TypecheckingFacade.getFromContext().getTypeOf(node);
        if (SNodeOperations.isInstanceOf(type, CONCEPTS.ClassifierType$bL)) {
          ListSequence.fromList(list).addElement(SNodeOperations.cast(type, CONCEPTS.ClassifierType$bL));
        }
      }

      return list;
    });

    return result;
  }
  private List<SNode> calcAllMembersOfClassifiers(Iterable<SNode> classifiers, final AnActionEvent event) {
    List<SNode> result = ListSequence.fromList(new ArrayList<SNode>());

    for (SNode classifier : classifiers) {
      try {
        ListSequence.fromList(result).addSequence(Sequence.fromIterable(IClassifierType__BehaviorDescriptor.getMembers_id6r77ob2V1Fr.invoke(IClassifier__BehaviorDescriptor.getThisType_id6r77ob2UWbY.invoke(classifier))));
      } catch (Exception e) {
        LogContext.with(CalcClassifiersInRootsStatistic_Action.class, e, event.getData(MPSCommonDataKeys.MPS_PROJECT), null).error("Error calculating type");
      }
    }

    return result;
  }
  private long calculateElapsedTime(_FunctionTypes._void_P0_E0 toRun, final AnActionEvent event) {
    long startTime = System.currentTimeMillis();
    try {
      toRun.invoke();
    } catch (Exception e) {
      LogContext.with(CalcClassifiersInRootsStatistic_Action.class, e, event.getData(MPSCommonDataKeys.MPS_PROJECT), null).error("Error calculating time");
    } finally {
      return System.currentTimeMillis() - startTime;
    }
  }

  private static final class LINKS {
    /*package*/ static final SReferenceLink classifier$cxMr = MetaAdapterFactory.getReferenceLink(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101de48bf9eL, 0x101de490babL, "classifier");
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept ClassifierType$bL = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0x101de48bf9eL, "jetbrains.mps.baseLanguage.structure.ClassifierType");
  }
}
