package jetbrains.mps.ide.migration;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.project.Project;
import jetbrains.mps.lang.migration.runtime.base.DataCollector;
import java.util.Map;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.model.SNode;
import jetbrains.mps.lang.migration.runtime.base.MigrationScriptReference;
import jetbrains.mps.internal.collections.runtime.MapSequence;
import java.util.HashMap;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import jetbrains.mps.lang.migration.runtime.base.MigrationModuleUtil;
import jetbrains.mps.ide.migration.util.MigrationDataUtil;
import jetbrains.mps.lang.migration.runtime.base.RefactoringScriptReference;
import jetbrains.mps.migration.global.ProjectMigration;
import jetbrains.mps.lang.migration.runtime.base.MigrationScript;
import jetbrains.mps.project.AbstractModule;
import org.jetbrains.mps.openapi.language.SLanguage;
import org.jetbrains.mps.openapi.model.SModel;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.smodel.SModelInternal;
import jetbrains.mps.refactoring.participant.RefactoringSessionImpl;
import jetbrains.mps.lang.migration.runtime.base.RefactoringScript;
import jetbrains.mps.baseLanguage.closures.runtime._FunctionTypes;
import jetbrains.mps.refactoring.participant.RefactoringUI;
import jetbrains.mps.refactoring.participant.RefactoringParticipant;
import jetbrains.mps.refactoring.participant.RefactoringSession;
import jetbrains.mps.refactoring.participant.RefactoringProcessor;
import jetbrains.mps.ide.findusages.model.scopes.ModulesScope;
import jetbrains.mps.internal.collections.runtime.Sequence;
import java.util.List;
import java.util.ArrayList;

@GeneratedClass(node = "a5b1c28d-abeb-49a6-a58c-559039616d64/r:a9597bdf-0806-4a79-8ace-88240c6b9878(jetbrains.mps.migration.component/jetbrains.mps.ide.migration)/4815078419490673773", model = "a5b1c28d-abeb-49a6-a58c-559039616d64/r:a9597bdf-0806-4a79-8ace-88240c6b9878(jetbrains.mps.migration.component/jetbrains.mps.ide.migration)")
public class MigrationExecutorImpl implements MigrationExecutor {
  private Project myProject;

  private DataCollector myDataCollector = new DataCollector() {
    public Map<SModule, SNode> collectData(SModule module, final MigrationScriptReference scriptReference) {
      final Map<SModule, SNode> requiredData = MapSequence.fromMap(new HashMap<SModule, SNode>());
      SetSequence.fromSet(MigrationModuleUtil.getModuleDependencies(module)).visitAll((it) -> {
        SNode dataString = MigrationDataUtil.readData(it, scriptReference);
        if (dataString != null) {
          MapSequence.fromMap(requiredData).put(it, dataString);
        }
      });
      return requiredData;
    }
  };

  public MigrationExecutorImpl(Project project) {
    myProject = project;
  }

  @Override
  public void execute(ScriptApplied s) {
    // todo remove explicit class mention (map<ref->script>?)
    if (s.getScriptReference() instanceof MigrationScriptReference) {
      executeMigrationScript(s);
    } else if (s.getScriptReference() instanceof RefactoringScriptReference) {
      executeRefactoringScript(s);
    } else {
      throw new IllegalArgumentException();
    }
  }
  @Override
  public void execute(ProjectMigration pm) {
    pm.execute(myProject);
  }

  private void executeMigrationScript(ScriptApplied<MigrationScriptReference> sa) {
    MigrationScript script = (MigrationScript) sa.getScriptInstance();
    AbstractModule module = ((AbstractModule) sa.getModule(myProject.getRepository()));
    SLanguage fromLanguage = script.getReference().getLanguage();
    final int usedVersion = MigrationModuleUtil.getUsedLanguageVersion(module, fromLanguage);
    assert usedVersion == script.getReference().getFromVersion();

    script.setDataCollector(myDataCollector);
    SNode data = script.execute(module);
    if (data != null) {
      MigrationDataUtil.addData(module, script.getReference(), data);
    }

    int toVersion = usedVersion + 1;
    MigrationModuleUtil.putUsedLanguageVersion(module, fromLanguage, toVersion);

    for (SModel model : ListSequence.fromList(module.getModels())) {
      if (model.isReadOnly()) {
        continue;
      }
      if (!(model instanceof SModelInternal)) {
        continue;
      }
      if (!(((SModelInternal) model).importedLanguageIds().contains(fromLanguage))) {
        continue;
      }

      ((SModelInternal) model).setLanguageImportVersion(fromLanguage, toVersion);
    }
    module.setChanged();
  }

  private void executeRefactoringScript(ScriptApplied<RefactoringScriptReference> sa) {
    RefactoringScriptReference rLog = sa.getScriptReference();
    final AbstractModule module = ((AbstractModule) sa.getModule(myProject.getRepository()));
    SModule fromModule = rLog.getModule(myProject.getRepository());
    int importedVersion = MigrationModuleUtil.getDependencyVersion(module, fromModule);
    importedVersion = Math.max(importedVersion, 0);
    assert importedVersion == rLog.getFromVersion();

    final RefactoringSessionImpl refactoringSession = new RefactoringSessionImpl("Apply Logged Refactoring");
    RefactoringScript script = (RefactoringScript) sa.getScriptInstance();
    script.setSession(refactoringSession);
    script.setTaskExecutor((Runnable task) -> RefactoringSessionTaskQueue.getInstance(refactoringSession).putTask(task));
    script.setRefactoringProcessor(new _FunctionTypes._void_P4_E0<RefactoringUI, RefactoringParticipant.PersistentRefactoringParticipant, Iterable<SNode>, Map<SNode, SNode>>() {
      public void invoke(RefactoringUI ui, RefactoringParticipant.PersistentRefactoringParticipant p, Iterable<SNode> initialState, Map<SNode, SNode> initialToFinal) {
        doRun(module, p, ui, initialState, initialToFinal, refactoringSession);
      }
    });
    script.execute(module);
    RefactoringSessionTaskQueue.getInstance(refactoringSession).runAll();
    refactoringSession.performAllRegistered();

    int toVersion = rLog.getFromVersion() + 1;
    MigrationModuleUtil.setDepVersion(module, fromModule.getModuleReference(), toVersion);

    // todo: versions in models
  }

  private <IP, FP> void doRun(AbstractModule module, RefactoringParticipant.PersistentRefactoringParticipant<?, ?, IP, FP> participant, RefactoringUI ui, Iterable<SNode> initialState, final Map<SNode, SNode> initialToFinal, RefactoringSession refactoringSession) {
    RefactoringProcessor.<IP,FP,SNode,SNode>performRefactoring(new RefactoringParticipant.DeserializingParticipantStateFactory<IP, FP>(), ui, refactoringSession, MigrationExecutorImpl.this.myProject.getRepository(), new ModulesScope(module), Sequence.singleton(participant), Sequence.fromIterable(initialState).toList(), null, (Iterable<RefactoringParticipant.ParticipantApplied<?, ?, IP, FP, SNode, SNode>> changes) -> initialToFinal, null);
  }

  private static class RefactoringSessionTaskQueue {
    private static final String myId = "refactoringSession.migrationAssistant.taskQueue";
    private List<Runnable> myTasks = ListSequence.fromList(new ArrayList<Runnable>());
    public static RefactoringSessionTaskQueue getInstance(RefactoringSession session) {
      RefactoringSessionTaskQueue result = ((RefactoringSessionTaskQueue) session.getObject(myId));
      if (result == null) {
        result = new RefactoringSessionTaskQueue();
        session.putObject(myId, result);
      }
      return result;
    }
    public void putTask(Runnable task) {
      ListSequence.fromList(myTasks).addElement(task);
    }
    public void runAll() {
      for (Runnable task : ListSequence.fromList(myTasks)) {
        task.run();
      }
    }
  }
}
