package jetbrains.mps.lang.test.generator.rt;

/*Generated by MPS */

import org.junit.jupiter.api.extension.ExtendWith;
import jetbrains.mps.baseLanguage.unitTest.runtime.EnvironmentAwareExtension;
import jetbrains.mps.tool.environment.EnvironmentAware;
import jetbrains.mps.tool.environment.Environment;
import org.jetbrains.mps.openapi.module.SRepository;
import java.util.List;
import java.util.ArrayList;
import org.jetbrains.annotations.NotNull;
import jetbrains.mps.smodel.MPSModuleRepository;
import org.jetbrains.mps.openapi.project.Project;
import jetbrains.mps.messages.LogHandler;
import jetbrains.mps.logging.Logger;
import org.jetbrains.mps.openapi.model.SModel;
import jetbrains.mps.lang.test.matcher.NodeDifference;
import jetbrains.mps.smodel.ModelAccessHelper;
import jetbrains.mps.lang.test.matcher.NodesMatcher;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SModelOperations;
import org.junit.Assert;
import java.util.Collection;
import java.util.Iterator;
import jetbrains.mps.generator.ModelGenerationPlan;
import jetbrains.mps.smodel.language.LanguageRegistry;
import org.jetbrains.mps.openapi.model.SNodeReference;
import jetbrains.mps.lang.smodel.generator.smodelAdapter.SNodeOperations;
import jetbrains.mps.internal.collections.runtime.ListSequence;
import jetbrains.mps.generator.InterpretedPlanProvider;
import org.jetbrains.mps.openapi.model.SModelReference;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import org.junit.Assume;
import org.junit.After;
import org.jetbrains.mps.openapi.language.SConcept;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;

@ExtendWith(value = EnvironmentAwareExtension.class)
public class BaseGeneratorTest implements EnvironmentAware {
  private Environment myEnv;
  private SRepository myRepository;
  private final List<TransformHelper> myTransformHelpers = new ArrayList<TransformHelper>(2);

  @Override
  public void setEnvironment(@NotNull Environment env) {
    myEnv = env;
    // FIXME AntModuleTestSuite opens a project and I don't see a reason for the test to open another one.
    //       Project shall be external configuration setting.
    //       As long as I don't have access to the project created inside MpsTestsSuite, I decided to resort to a CL repository for now,
    //       though there's a chance to override this with setProject()
    myRepository = myEnv.getPlatform().findComponent(MPSModuleRepository.class);
  }

  protected final Environment getEnvironment() {
    return myEnv;
  }

  protected final void setProject(Project mpsProject) {
    myRepository = mpsProject.getRepository();
  }

  protected final TransformHelper newTransformer() {
    // Perhaps, we shall use a handler that pipes everything to stdout (warn -> stdout, error -> stderr?), but for now it's just
    // a logger with a category matching name of a test class
    TransformHelper rv = new TransformHelper(myRepository, new LogHandler(Logger.getLogger(getClass())));
    myTransformHelpers.add(rv);
    return rv;
  }

  protected final void assertMatch(final SModel m1, final SModel m2) {
    // Next is wishful thinking, imagined contract, not necessarily real at the moment,
    // IOW, what I'd like match(m1,m2) contract to look like. Have to refactor
    // NodesMatcher first, and write some tests for it to ensure the contract:
    // return true if models are the same from structure, metadata, value and reference perspectives
    // equality from reference perspective means that references within the same model point to
    // equal (in aforementioned sense) nodes, for external references that the target is equal is java sense.
    // FIXME use of myProject.getModelAccess() is wrong, empty project we've just created doesn't have modules with test data,
    //       however, at the moment I've got no better idea how to access project of MpsTestsSuite
    List<NodeDifference> diff = new ModelAccessHelper(myRepository.getModelAccess()).runReadAction(() -> new NodesMatcher(SModelOperations.roots(m1, null), SModelOperations.roots(m2, null)).diff());
    if (diff.isEmpty()) {
      return;
    }
    StringBuilder sb = new StringBuilder();
    for (NodeDifference nd : diff) {
      sb.append(nd.print());
    }
    Assert.fail("Transformation output model doesn't match reference one:\n" + sb.toString());
  }

  protected final void assertMatch(Collection<SModel> actual, SModel... expected) {
    // FIXME move all assert methods into dedicated ModelAsserts class
    assert actual.size() == expected.length;
    Iterator<SModel> it = actual.iterator();
    for (SModel ex : expected) {
      assertMatch(it.next(), ex);
    }
  }

  protected final ModelGenerationPlan.Provider planProviderFromModel(final SModel gpm) {
    return new ModelAccessHelper(gpm.getRepository()).runReadAction(() -> {
      LanguageRegistry lreg = myEnv.getPlatform().findComponent(LanguageRegistry.class);
      LogHandler mh = new LogHandler(Logger.getLogger(getClass()));
      SNodeReference planNode = SNodeOperations.getPointer(ListSequence.fromList(SModelOperations.roots(gpm, CONCEPTS.Plan$X1)).first());
      return new InterpretedPlanProvider(lreg, mh, planNode, myRepository);
    });
  }

  protected final SModel findModel(String modelRef) {
    // FIXME lacking proper model access, works as mr.resolve just complains with WARN if there's no model read
    //       Need to decide how to grab model locks effectively, don't want to grab it here, either shall wrap whole prepareArguments() or test method
    SModelReference mr = PersistenceFacade.getInstance().createModelReference(modelRef);
    return mr.resolve(myRepository);
  }

  protected final SModel findModel(String parameterName, String modelRef) {
    SModel m = findModel(modelRef);
    Assume.assumeTrue(String.format("Could not find model for parameter '%s' (%s)", parameterName, modelRef), m != null);
    return m;
  }

  @After
  public void cleanup() {
    for (TransformHelper th : myTransformHelpers) {
      th.dispose();
    }
    myTransformHelpers.clear();
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept Plan$X1 = MetaAdapterFactory.getConcept(0x7ab1a6fa0a114b95L, 0x9e4875f363d6cb00L, 0x19443180a20717fbL, "jetbrains.mps.lang.generator.plan.structure.Plan");
  }
}
