package jetbrains.mps.baseLanguage.unitTest.execution.server;

/*Generated by MPS */

import jetbrains.mps.baselanguage.unitTest.execution.launcher.TestsContributor;
import jetbrains.mps.tool.environment.Environment;
import jetbrains.mps.baselanguage.unitTest.execution.launcher.ExecutorScript;
import org.junit.runners.model.RunnerBuilder;
import org.jetbrains.annotations.NotNull;
import jetbrains.mps.testbench.junit.runners.PushEnvironmentRunnerBuilder;
import org.junit.runner.Request;
import org.jetbrains.mps.openapi.module.SRepository;
import jetbrains.mps.smodel.MPSModuleRepository;
import jetbrains.mps.smodel.ModelAccessHelper;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import jetbrains.mps.persistence.PersistenceRegistry;
import java.util.ArrayList;
import jetbrains.mps.internal.collections.runtime.CollectionSequence;
import org.jetbrains.mps.openapi.module.SModule;
import jetbrains.mps.module.ReloadableModule;
import org.jetbrains.annotations.Nullable;
import org.junit.runner.Description;

/**
 * Contributes JUnit tests based on their serialized description in {@link jetbrains.mps.baselanguage.unitTest.execution.launcher.ExecutorScript } using MPS module classloaders to access classes.
 * Note, this class doesn't care about {@link jetbrains.mps.baselanguage.unitTest.execution.launcher.ExecutorScript#getStartupArguments() }, the value may be empty.
 * 
 * FIXME pretty much resembles MpsTestsSuite which is a runner for tests invoked from Ant. 
 *       Unlike this one, MpsTestsSuite runs all ITestCase found in the modules. Shall refactor both to reuse similar code.
 * 
 * XXX NodeWrappersTestsContributor is quite similar, too, with the only distinction that it uses ITestNodeWrapper instead of TestRecord
 * 
 * 
 * XXX This class may get relocated to j.m.tool.builder module along with ExecutorScript once I refactor MpsTestSuite to share classloading code.
 * 
 */
public class ScriptTestContributor implements TestsContributor {
  private final Environment myEnv;
  private final ExecutorScript myExecScript;
  private final RunnerBuilder myRunnerBuilder;

  public ScriptTestContributor(@NotNull Environment env, @NotNull ExecutorScript execScript) {
    myEnv = env;
    myExecScript = execScript;
    myRunnerBuilder = new PushEnvironmentRunnerBuilder(env);
  }

  @Override
  public Iterable<Request> gatherTests() throws Exception {
    // XXX likely, need better control over context repository to load test modules from
    final SRepository repo = myEnv.getPlatform().findComponent(MPSModuleRepository.class);
    return new ModelAccessHelper(repo).runReadAction(() -> {
      final PersistenceFacade pf = myEnv.getPlatform().findComponent(PersistenceRegistry.class);
      final ArrayList<Request> rv = new ArrayList<Request>();
      for (ExecutorScript.TestRecord tr : CollectionSequence.fromCollection(myExecScript.getTests())) {
        SModule testModule0 = pf.createModuleReference(tr.myTestModule).resolve(repo);
        final Exception failure;
        ReloadableModule testModule = null;
        if (testModule0 instanceof ReloadableModule) {
          testModule = (ReloadableModule) testModule0;
          failure = null;
        } else {
          if (testModule0 == null) {
            failure = new Exception(String.format("Failed to find test module %s", tr.myTestModule));
          } else {
            failure = new Exception(String.format("Test module %s is not capable to load classes", tr.myTestModule));
          }
        }

        for (int i = 0; i < tr.myTestQualifiedName.size(); i++) {
          String qualifiedName = tr.myTestQualifiedName.get(i);
          String isTestCaseProp = tr.isTestCase.get(i);
          boolean isTestCase = Boolean.valueOf(isTestCaseProp) == Boolean.TRUE;
          Request request = (isTestCase ? processTestCase(testModule, failure, qualifiedName) : processTestMethod(testModule, failure, qualifiedName));
          rv.add(request);
        }
      }
      return rv;
    });

  }

  @NotNull
  private Request processTestCase(@Nullable ReloadableModule testModule, Exception failure, String fqName) {
    if (testModule == null) {
      assert failure != null;
      return createFailedRequestForClass(fqName, failure);
    }
    try {
      Class<?> testClass = testModule.getOwnClass(fqName);
      return Request.runner(myRunnerBuilder.safeRunnerForClass(testClass));
    } catch (Exception ex) {
      return createFailedRequestForClass(fqName, ex);
    }
  }

  @NotNull
  private Request processTestMethod(@Nullable ReloadableModule testModule, Exception failure, String qualifiedName) {
    int indexOfLastDot = qualifiedName.lastIndexOf('.');
    if (indexOfLastDot < 0) {
      return createFailedRequestForMethod(qualifiedName, "", new IllegalStateException("The qualified name of the test " + qualifiedName + " does not contain '.'"));
    }
    String testFqName = qualifiedName.substring(0, indexOfLastDot);
    String methodName = qualifiedName.substring(indexOfLastDot + 1);
    if (testModule == null) {
      assert failure != null;
      return createFailedRequestForMethod(testFqName, methodName, failure);
    }
    try {
      Class<?> testClass = testModule.getOwnClass(testFqName);
      Request classRequest = Request.runner(myRunnerBuilder.safeRunnerForClass(testClass));
      Request filteredRequest = classRequest.filterWith(Description.createTestDescription(testFqName, methodName));
      return filteredRequest;
    } catch (Exception ex) {
      return createFailedRequestForMethod(testFqName, methodName, ex);
    }
  }

  private Request createFailedRequestForClass(String fqName, Exception e) {
    return Request.runner(new AssumptionFailedRunner(e, Description.createSuiteDescription(fqName)));
  }

  private Request createFailedRequestForMethod(String testFqName, String methodName, Exception e) {
    return Request.runner(new AssumptionFailedRunner(e, Description.createTestDescription(testFqName, methodName)));
  }
}
