package jetbrains.mps.build.migration;

/*Generated by MPS */

import jetbrains.mps.tool.builder.WorkerBase;
import jetbrains.mps.tool.common.Script;
import jetbrains.mps.tool.environment.Environment;
import jetbrains.mps.tool.environment.IdeaEnvironment;
import jetbrains.mps.tool.environment.EnvironmentConfig;
import jetbrains.mps.tool.common.MigrateTaskProperties;
import jetbrains.mps.tool.builder.WorkerHelper;
import jetbrains.mps.smodel.MPSModuleRepository;
import jetbrains.mps.util.IterableUtil;
import jetbrains.mps.baseLanguage.closures.runtime.Wrappers;
import org.jetbrains.mps.openapi.module.SModuleReference;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import java.io.File;
import java.nio.file.Files;
import jetbrains.mps.project.Project;
import java.util.Collection;
import org.jetbrains.mps.openapi.module.SModule;
import com.intellij.openapi.application.ApplicationManager;
import jetbrains.mps.tool.run.ModuleClassCode;
import java.lang.reflect.Method;
import com.intellij.openapi.application.ModalityState;
import java.util.Properties;
import java.io.FileOutputStream;
import java.io.IOException;

public class MigrationWorker extends WorkerBase {
  private static final String TASK_EXEC_CLASS = "jetbrains.mps.ide.migration.AntTaskExecutionUtil";

  private static final String MARKER_FILE_NAME = ".allow-pending-migrations";

  public MigrationWorker(Script whatToDo) {
    super(whatToDo);
  }

  @Override
  protected Environment createEnvironment() {
    IdeaEnvironment environment = new IdeaEnvironment(createEnvironmentConfig(myWhatToDo));
    environment.init();
    return environment;
  }

  @Override
  protected EnvironmentConfig initEnvironmentConfig() {
    EnvironmentConfig withBootstrapLibraries = EnvironmentConfig.emptyConfig().withBootstrapLibraries();
    if (myWhatToDo.getAutomaticPluginDiscoveryMode()) {
      return withBootstrapLibraries.withAutomaticPluginDiscovery();
    } else {
      // for mysterious reasons, lang.migration.util is part of mps-core plugin
      return withBootstrapLibraries.withCorePlugin().withMigrationPlugin();
    }
  }

  @Override
  public void work() {
    final MigrateTaskProperties taskProps = new MigrateTaskProperties(myWhatToDo);
    WorkerHelper compileReloadHelper = new WorkerHelper(myEnvironment.getPlatform());

    if (taskProps.getMakeDistribModules()) {
      // todo the following line is needed until we introduce layered migration
      // FIXME why do I care to make these modules?
      final MPSModuleRepository repo = myEnvironment.getPlatform().findComponent(MPSModuleRepository.class);
      compileReloadHelper.makeAndReload(repo, () -> IterableUtil.asCollection(repo.getModules()), myJavaCompilerOptions);
    }

    final boolean preCheckFailureHalt = taskProps.getPreCheckFailureHalt();
    final boolean dependencyErrorHalt = taskProps.getHaltOnDependencyError();
    final Wrappers._boolean result = new Wrappers._boolean(true);

    final SModuleReference execClassModule = PersistenceFacade.getInstance().createModuleReference("a5b1c28d-abeb-49a6-a58c-559039616d64(jetbrains.mps.migration.component)");

    for (File file : myWhatToDo.getMPSProjectFiles()) {
      // fixme duplicate in NoPendingMigrationTest    
      if (!(taskProps.getForceFlag()) && Files.exists(file.toPath().resolve(MARKER_FILE_NAME))) {
        info("Project " + file.getName() + ": automatic migrations are not allowed (" + MARKER_FILE_NAME + " file is present in project dir)");

        continue;
      }

      final Project[] container = new Project[1];
      container[0] = myEnvironment.openProject(file);
      final Project project = container[0];
      info("Loaded project " + project);
      myEnvironment.flushAllEvents();

      compileReloadHelper.makeAndReload(project.getRepository(), () -> (Collection<SModule>) project.getProjectModulesWithGenerators(), myJavaCompilerOptions);

      myEnvironment.flushAllEvents();

      ApplicationManager.getApplication().invokeAndWait(() -> {
        try {
          // FIXME why another reflection? MigrationTask builds classpath to load MigrationWorker by reflection
          // and them MigrationWorker once again uses reflection to load another class.
          // The answer to ^^ is twofold. One, I'd like to get rid of this by generic IdeaEnv worker and the rest
          // being run from within MPS-managed code. Two, this worker performs some activities prior to switching to
          // MPS code, and first I'd need to move this carefully into the TASK_EXEC_CLASS (or elsewhere, it's not specific 
          // to the code to start IDEA).
          ModuleClassCode cc = new ModuleClassCode(execClassModule.toString());
          cc.load(myEnvironment.getPlatform(), TASK_EXEC_CLASS);
          Method method = cc.staticMethod("migrate", Project.class, Boolean.TYPE, Boolean.TYPE).orElseThrow();
          Object rv = method.invoke(null, project, preCheckFailureHalt, dependencyErrorHalt);
          if (rv instanceof Boolean) {
            result.value &= (Boolean) rv;
          } else {
            error(String.format("Migration of project %s didn't yield expected boolean result (%s)", project.getName(), rv));
            result.value = false;
          }
        } catch (ClassNotFoundException e) {
          error(String.format("Module %s not loaded, likely broken module/plugin dependencies, check log for reasons", execClassModule.getModuleName()), e);
          result.value = false;
        } catch (Exception e) {
          error("Exception during migration", e);
          result.value = false;
        }
      }, ModalityState.defaultModalityState());

      myEnvironment.closeProject(project);
      myEnvironment.flushAllEvents();
    }

    // here we treat no projects case as 'success', although that differs from previous logic. Do I care?
    File propFile = taskProps.getOutputPropertyFile();
    if (propFile != null && taskProps.getOutputPropertyErrorKey() != null) {
      final Properties properties = new Properties();
      properties.setProperty(taskProps.getOutputPropertyErrorKey(), (result.value ? "0" : "1"));
      try (FileOutputStream fos = new FileOutputStream(propFile)) {
        properties.store(fos, "");
      } catch (IOException ex) {
        error("Exception while saving property file with result code", ex);
      }
    }
  }

  public static void main(String[] args) {
    MigrationWorker mpsWorker = new MigrationWorker(Script.fromDumpInFile(new File(args[0])));
    mpsWorker.workFromMain();
  }
}
