package jetbrains.mps.tool.builder;

/*Generated by MPS */

import jetbrains.mps.annotations.GeneratedClass;
import java.util.List;
import java.util.ArrayList;
import jetbrains.mps.tool.common.Script;
import jetbrains.mps.compiler.JavaCompilerOptions;
import jetbrains.mps.tool.environment.Environment;
import jetbrains.mps.smodel.BaseMPSModuleOwner;
import jetbrains.mps.tool.common.JavaCompilerProperties;
import jetbrains.mps.compiler.JavaCompilerOptionsComponent;
import jetbrains.mps.tool.environment.EnvironmentConfig;
import jetbrains.mps.tool.common.RepositoryDescriptor;
import java.io.File;
import jetbrains.mps.internal.collections.runtime.IMapping;
import jetbrains.mps.internal.collections.runtime.MapSequence;
import jetbrains.mps.tool.common.PluginData;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import java.util.logging.Logger;
import jetbrains.mps.project.Project;
import jetbrains.mps.components.ComponentHost;
import java.util.Set;
import org.jetbrains.mps.openapi.module.SModule;
import org.jetbrains.mps.openapi.module.SRepository;
import java.util.Collection;
import java.util.LinkedHashSet;
import jetbrains.mps.vfs.IFileSystem;
import jetbrains.mps.vfs.VFSManager;
import jetbrains.mps.project.io.DescriptorIOFacade;
import jetbrains.mps.library.ModulesMiner;
import java.util.Collections;
import jetbrains.mps.vfs.IFile;
import jetbrains.mps.internal.collections.runtime.CollectionSequence;
import jetbrains.mps.smodel.ModuleRepositoryFacade;
import jetbrains.mps.extapi.module.SRepositoryExt;
import java.util.logging.Level;
import java.io.StringWriter;
import java.io.PrintWriter;

/**
 * MPS-aware job triggered by an Ant task. Unlike the task, assumes MPS classes in the classpath. 
 * Extent of the classes is not well defined, though and may vary from worker to worker (e.g. some gonna use IdeaEnironment and hence 
 * need IDEA platform, others may stick to MpsEnvironment with less classes in the classpath). The mechanism to describe this is not 
 * well-defined at the moment. Now MPS workers start with a help of a code in MpsLoadTask class which generally loads everything 
 * under mps-home/lib based on Ant Project properties (various "artifacts.*" values). Task has control over classpath through 
 * MpsLoadTask#calculateClassPath() method and may supply extra elements in use by particular worker (e.g. MigrationTask adds jars of migration plugin)
 */
@GeneratedClass(node = "r:73cef602-d8a6-459c-91ff-d4e129d1a7c5(jetbrains.mps.tool.builder)/878521226300773719", model = "r:73cef602-d8a6-459c-91ff-d4e129d1a7c5(jetbrains.mps.tool.builder)")
public abstract class WorkerBase {
  protected final List<String> myErrors = new ArrayList<String>();
  protected final Script myWhatToDo;
  protected final JavaCompilerOptions myJavaCompilerOptions;
  protected final boolean mySkipCompilation;
  protected Environment myEnvironment;
  private final BaseMPSModuleOwner myOwner = new BaseMPSModuleOwner();

  public WorkerBase(Script whatToDo) {
    myWhatToDo = whatToDo;
    JavaCompilerProperties javaProperties = new JavaCompilerProperties(myWhatToDo);
    myJavaCompilerOptions = getJavaCompilerOptions(javaProperties);
    mySkipCompilation = javaProperties.isSkipCompilation();
  }

  private static JavaCompilerOptions getJavaCompilerOptions(JavaCompilerProperties javaProperties) {
    JavaCompilerOptionsComponent.JavaVersion parsedJavaVersion = JavaCompilerOptionsComponent.JavaVersion.parse(javaProperties.getTargetJavaVersion());
    if (parsedJavaVersion == null) {
      return JavaCompilerOptionsComponent.DEFAULT_JAVA_COMPILER_OPTIONS;
    }

    return new JavaCompilerOptions(parsedJavaVersion);
  }

  protected final boolean isRunningOnTeamCity() {
    return myWhatToDo.getProperty("teamcity.version") != null;
  }

  protected abstract Environment createEnvironment();

  protected EnvironmentConfig initEnvironmentConfig() {
    EnvironmentConfig res = EnvironmentConfig.emptyConfig().withBootstrapLibraries().withWorkbenchPath();
    if (myWhatToDo.getAutomaticPluginDiscoveryMode()) {
      res = res.withAutomaticPluginDiscovery();
    } else {
      res = res.withDefaultPlugins();
    }
    return res;
  }

  protected EnvironmentConfig createEnvironmentConfig(Script whatToDo) {
    EnvironmentConfig config = initEnvironmentConfig();
    RepositoryDescriptor repo = whatToDo.getRepoDescriptor();
    if (repo != null) {
      // todo make this code more typed
      for (String folder : repo.folders) {
        if (!(new File(folder).exists())) {
          warning("Modules folder does not exist: " + folder);
        }
        config = config.addLib(folder);
      }
      for (String file : repo.files) {
        if (!(new File(file).exists())) {
          warning("Module file does not exist: " + file);
        }
        config = config.addLib(file);
      }
    }
    // else assume the one coming from initEnvironmentConfig is fine
    for (IMapping<String, String> macro : MapSequence.fromMap(whatToDo.getMacro())) {
      config.addMacro(macro.key(), new File(macro.value()));
    }
    for (IMapping<String, File> lib : MapSequence.fromMap(whatToDo.getLibraries())) {
      config.addLib(lib.value().getAbsolutePath());
    }
    for (String jar : whatToDo.getLibraryJars()) {
      File jarFile = new File(jar);
      if (!(jarFile.exists())) {
        warning("Library " + jar + " does not exist.");
      }
      config.addLib(jar);
    }
    // let Environment know which idea plugins are expected to be loaded.
    // Note, this doesn't address plugin classpath, as it's up to respective Task to decide whether respective plugins and their classes/libraries
    // are in a global classpath or plugin classes are loaded in any other way.
    for (PluginData pd : ListSequence.fromList(whatToDo.getPlugins())) {
      config.addPlugin(pd.path, pd.id);
    }
    return config;
  }

  public void workFromMain() {
    try {
      // FIXME I don't like the way log initialization happens (from static{} blocks in MpsEnv/IdeaEnv)
      //      I'd rather have it initialized here explicitly. However, seems too much to change now
      //      to make sure log is ready before any code in EnvBase or Launcher uses it.
      Logger.getLogger("").setLevel(myWhatToDo.getLogLevel());
      myEnvironment = createEnvironment();
      work();
      myEnvironment.flushAllEvents();
      dispose();
      System.exit(0);
    } catch (Throwable e) {
      error("workFromMain", e);
      System.exit(1);
    }
  }

  public abstract void work();

  protected Project createDummyProject() {
    return myEnvironment.createEmptyProject();
  }

  protected void dispose() {
    if (myEnvironment != null) {
      myEnvironment.dispose();
      myEnvironment = null;
    }
  }

  protected final ComponentHost getPlatform() {
    return myEnvironment.getPlatform();
  }

  protected StringBuffer formatErrorsReport(String taskName) {
    StringBuffer sb = new StringBuffer();
    for (int i = 0; i < 100; i++) {
      sb.append('*');
    }
    sb.append("\n");
    sb.append(myErrors.size());
    sb.append(" errors during " + taskName + ":\n");
    for (String error : myErrors) {
      sb.append(error);
      sb.append("\n");
    }
    for (int i = 0; i < 100; i++) {
      sb.append('*');
    }
    return sb;
  }
  protected void failBuild(String name) {
    if (!(myErrors.isEmpty()) && myWhatToDo.getFailOnError()) {
      throw new RuntimeException(this.formatErrorsReport(name).toString());
    }
  }

  /**
   * Discovers module(s) from specified location of a module descriptor, loads and registers them in
   * global (JUST FOR NOW) repository with custom owner.
   * 
   * The method used to filter out read-only module and DevKit which is odd provided we have no idea what's the reason to load the module in the first place.
   * Now it's caller responsibility to deal with loaded modules and ignore those undesired as appropriate.
   * 
   * @param moduleSourceDescriptorFiles not null
   */
  protected Set<SModule> processModuleFiles(SRepository repo, final Collection<File> moduleSourceDescriptorFiles) {
    Set<SModule> modules = new LinkedHashSet<SModule>();

    // XXX need a way to figure which FS to use here. Technically, it should come from a project as we are going to
    // use these modules as part of the project. OTOH, we know these are local FS files.
    final IFileSystem fs = myEnvironment.getPlatform().findComponent(VFSManager.class).getFileSystem(VFSManager.FILE_FS);
    DescriptorIOFacade descriptorIOFacade = myEnvironment.getPlatform().findComponent(DescriptorIOFacade.class);
    final ModulesMiner mminer = new ModulesMiner(Collections.<IFile>emptySet(), descriptorIOFacade);
    for (File df : CollectionSequence.fromCollection(moduleSourceDescriptorFiles)) {
      IFile descriptorFile = fs.getFile(df);
      if (descriptorIOFacade.fromFileType(descriptorFile) == null) {
        info(String.format("File %s doesn't point to module descriptor, ignored", moduleSourceDescriptorFiles));
        continue;
      }
      mminer.collectModules(descriptorFile);
    }
    ModuleRepositoryFacade mrf = new ModuleRepositoryFacade(repo);
    final SRepositoryExt repoExt = ((SRepositoryExt) mrf.getRepository());
    for (ModulesMiner.ModuleHandle mh : mminer.getCollectedModules()) {
      // seems reasonable just to instantiate a module here and leave its registration to caller
      // however, at the moment, Generator modules need access to their source Language module, which they look up in the repository
      SModule module = repoExt.registerModule(mrf.instantiate(mh.getDescriptor(), mh.getFile()), myOwner);
      info("Loaded module " + module);
      modules.add(module);
    }
    return modules;
  }

  private void log(String text, Level level) {
    // see jul.Logger.isLoggable(Level)
    final int globalLevel = myWhatToDo.getLogLevel().intValue();
    if (level.intValue() < globalLevel || globalLevel == Level.OFF.intValue()) {
      return;
    }

    if (level == Level.SEVERE) {
      System.err.println(text);
    } else {
      System.out.println(text);
    }
  }
  public void info(String text) {
    log(text, Level.INFO);
  }
  public void warning(String text) {
    log(text, Level.WARNING);
  }
  public void debug(String text) {
    log(text, Level.FINE);
  }
  public void error(String text) {
    log(text, Level.SEVERE);
    myErrors.add(text);
  }
  public void error(String text, Throwable e) {
    if (e != null) {
      StringBuffer sb = WorkerBase.extractStackTrace(e);
      text = text + "\n" + sb.toString();
    }
    error(text);
  }
  private static StringBuffer extractStackTrace(Throwable e) {
    StringWriter writer = new StringWriter();
    e.printStackTrace(new PrintWriter(writer));
    return writer.getBuffer();
  }
}
