package jetbrains.mps.tool.environment;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import jetbrains.mps.logging.Logger;
import org.jetbrains.annotations.NotNull;
import jetbrains.mps.project.Project;
import jetbrains.mps.components.ComponentHost;
import java.util.Collection;
import jetbrains.mps.library.ModulesMiner;
import jetbrains.mps.make.MPSCompilationResult;
import jetbrains.mps.messages.MessageCollector;
import java.util.ArrayList;
import jetbrains.mps.messages.IMessage;
import jetbrains.mps.make.ModuleMaker;
import jetbrains.mps.messages.MessageKind;
import jetbrains.mps.util.IterableUtil;
import jetbrains.mps.progress.EmptyProgressMonitor;
import java.util.Set;
import org.jetbrains.mps.openapi.module.SModuleReference;
import jetbrains.mps.classloading.ClassLoaderManager;
import jetbrains.mps.internal.collections.runtime.SetSequence;
import org.jetbrains.mps.openapi.module.SModule;
import jetbrains.mps.project.AbstractModule;

@GeneratedClass(node = "r:2876f1ee-0b45-4db5-8c09-0682cdee5c67(jetbrains.mps.tool.environment)/6450649963068953208", model = "r:2876f1ee-0b45-4db5-8c09-0682cdee5c67(jetbrains.mps.tool.environment)")
public abstract class ProjectStrategyBase implements ProjectStrategy {
  private static final Logger LOG = Logger.getLogger(ProjectStrategyBase.class);
  @Override
  public boolean isApplicable() {
    return true;
  }

  @NotNull
  public Project create(@NotNull Environment env) {
    if (isApplicable()) {
      Project emptyProject = env.createEmptyProject();
      return construct(env.getPlatform(), emptyProject);
    }
    throw new IllegalStateException("Strategy " + this + " is not applicable -- cannot create project");
  }

  @NotNull
  protected abstract Project construct(@NotNull ComponentHost mpsPlatform, @NotNull Project emptyProject);

  @NotNull
  protected Project loadProjectFromModuleHandles(@NotNull Project emptyProject, Collection<ModulesMiner.ModuleHandle> moduleHandles) {
    Project projectFilledWithModules = new ProjectModulesFiller(emptyProject, moduleHandles).load();
    return projectFilledWithModules;
  }

  private static MPSCompilationResult makeAllInCreatedEnvironment(final Project project) {
    if (LOG.isInfoLevel()) {
      LOG.info("Building modules within project");
    }
    MessageCollector mc = new MessageCollector(new ArrayList<IMessage>());
    final ModuleMaker mm = new ModuleMaker(mc.restrict(MessageKind.ERROR));
    project.getModelAccess().runReadAction(() -> {
      // FIXME figure out why project.getProjectModulesWithGenerators() instead of all repo modules
      //      fails few MPS tests. Likely, wrong project specification.
      mm.prepare(IterableUtil.asCollection(project.getRepository().getModules()), false, new EmptyProgressMonitor());
    });
    MPSCompilationResult rv = mm.make(new EmptyProgressMonitor());
    if (!(rv.isOk())) {
      // regardless of what's in the log (and whether it's turned on at all(, I care to see errors
      // Perhaps, not sysout but part of ISE from makeOnFirstTimeOpened(), shall decide once need arises.
      for (IMessage m : mc.result()) {
        System.out.println(m.getText());
      }
    }
    return rv;
  }

  @NotNull
  protected final Project makeOnFirstTimeOpened(@NotNull Project project) {
    MPSCompilationResult result = makeAllInCreatedEnvironment(project);
    if (!(result.isOk())) {
      throw new IllegalStateException(String.format("Cannot proceed with compilation errors. %s", result.toString()));
    }
    try {
      if (result.isReloadingNeeded()) {
        reloadAfterMake(project, result.getAffectedModules());
      }
      updateModelsInModules(project, result.getAffectedModules());
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
    return project;
  }

  protected final void reloadAfterMake(@NotNull Project project, Set<SModuleReference> changed) {
    if (LOG.isInfoLevel()) {
      LOG.info("Reloading built modules");
    }

    // todo create make process listeners, class loading is a client
    project.getComponent(ClassLoaderManager.class).reload(changed, new EmptyProgressMonitor());
  }

  /**
   * Why do not we need it in IDE?
   * Danya:
   * added reload of all changed (or new) models after make.
   * Usecase: stub model with source at classes_gen dir which is populated only during make.
   * But by that time model repository is already filled and it has no such models since there was no class files
   * when it got filled.
   */
  protected static void updateModelsInModules(@NotNull final Project project, final Set<SModuleReference> changed) {
    project.getModelAccess().runWriteAction(() -> {
      for (SModuleReference mref : SetSequence.fromSet(changed)) {
        SModule module = mref.resolve(project.getRepository());
        if (module instanceof AbstractModule) {
          ((AbstractModule) module).updateModelsSet();
        }
      }
    });
  }
}
