package tests.testPersistence.test;

/*Generated by MPS */

import jetbrains.mps.MPSLaunch;
import jetbrains.mps.lang.test.runtime.BaseTransformationTest;
import org.junit.ClassRule;
import jetbrains.mps.lang.test.runtime.TestParametersCache;
import org.junit.Rule;
import jetbrains.mps.lang.test.runtime.RunWithCommand;
import org.junit.Test;
import jetbrains.mps.lang.test.runtime.BaseTestBody;
import jetbrains.mps.lang.test.runtime.TransformationTest;
import jetbrains.mps.persistence.PersistenceUtil;
import jetbrains.mps.extapi.persistence.ModelFactoryService;
import jetbrains.mps.persistence.PreinstalledModelFactoryTypes;
import jetbrains.mps.smodel.persistence.def.ModelPersistence;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import junit.framework.Assert;
import jetbrains.mps.smodel.adapter.structure.concept.SConceptAdapterById;
import jetbrains.mps.java.stub.JavaPackageNameStub;
import org.jetbrains.mps.openapi.persistence.PersistenceFacade;
import jetbrains.mps.smodel.SNodeId;
import jetbrains.mps.smodel.SNodePointer;
import jetbrains.mps.smodel.loading.ModelLoadResult;
import jetbrains.mps.smodel.SModelHeader;
import jetbrains.mps.persistence.ByteArrayInputSource;
import jetbrains.mps.smodel.loading.ModelLoadingState;
import jetbrains.mps.extapi.model.SModelBase;
import jetbrains.mps.smodel.SModel;
import org.jetbrains.mps.openapi.model.SNode;
import java.util.HashMap;
import java.util.List;
import jetbrains.mps.smodel.ImplicitImportsLegacyHolder;
import java.util.Comparator;
import java.util.Set;
import org.jetbrains.mps.openapi.language.SContainmentLink;
import java.util.HashSet;
import jetbrains.mps.util.IterableUtil;
import java.util.Iterator;
import org.jetbrains.mps.openapi.language.SProperty;
import org.jetbrains.mps.openapi.language.SReferenceLink;
import org.jetbrains.mps.openapi.model.SReference;
import java.util.Map;
import org.jetbrains.mps.openapi.model.SModelReference;
import java.util.ArrayList;
import java.util.Collections;
import org.jetbrains.mps.openapi.language.SConcept;
import jetbrains.mps.smodel.adapter.structure.MetaAdapterFactory;

@MPSLaunch
public class TestPersistence_Test extends BaseTransformationTest {
  @ClassRule
  public static final TestParametersCache ourParamCache = new TestParametersCache(TestPersistence_Test.class, "${mps_home}", "r:8ef4c1fc-fb61-4d5c-806c-7a971cfb9392(tests.testPersistence.test@tests)", false);
  @Rule
  public final RunWithCommand myWithCommandRule = new RunWithCommand(this);

  public TestPersistence_Test() {
    super(ourParamCache);
  }

  @Test
  public void test_testLastVersionIndexing() throws Throwable {
    new TestBody(this).test_testLastVersionIndexing();
  }
  @Test
  public void test_testPersistenceReadWrite() throws Throwable {
    new TestBody(this).test_testPersistenceReadWrite();
  }
  @Test
  public void test_testPersistenceUpgrade() throws Throwable {
    new TestBody(this).test_testPersistenceUpgrade();
  }

  /*package*/ static class TestBody extends BaseTestBody {

    /*package*/ TestBody(TransformationTest owner) {
      super(owner);
    }

    public void test_testLastVersionIndexing() throws Exception {
      TestPersistenceHelper helper = new TestPersistenceHelper(myProject.getRepository());
      CollectCallback c = new CollectCallback();
      byte[] serialized = PersistenceUtil.modelAsBytes(helper.getTestModel(), myProject.getComponent(ModelFactoryService.class).getFactoryByType(PreinstalledModelFactoryTypes.PLAIN_XML));

      try {
        ModelPersistence.index(new ByteArrayInputStream(serialized), c);
      } catch (IOException e) {
        Assert.fail(e.getMessage());
      }

      Assert.assertTrue(c.myConcepts.contains(((SConceptAdapterById) CONCEPTS.ClassConcept$bK).getId()));
      Assert.assertTrue(c.myImports.contains(new JavaPackageNameStub("java.io").asModelReference(PersistenceFacade.getInstance().createModuleReference("6354ebe7-c22a-4a0f-ac54-50b52ab9b065(JDK)"))));
      Assert.assertTrue(c.myExtRefs.contains(new SNodeId.Foreign("~System")));
      Assert.assertTrue(c.myLocalRefs.contains(new SNodePointer("r:b44bed60-e0f0-4d48-bb29-e0fdb2041a66(tests.testPersistence.testModel)", "3895553186365322355").getNodeId()));
      Assert.assertTrue(c.myPropertyValues.contains("instance of ClassConcept"));
    }
    public void test_testPersistenceReadWrite() throws Exception {
      // tests write and read in each supported persistence, check that model is not changed after write/read cycle 
      TestPersistenceHelper helper = new TestPersistenceHelper(myProject.getRepository());
      for (int i = TestPersistenceHelper.START_PERSISTENCE_TEST_VERSION; i <= ModelPersistence.LAST_VERSION; ++i) {
        PersistenceUtil.InMemoryStreamDataSource dataSource = new PersistenceUtil.InMemoryStreamDataSource();
        helper.saveTestModelInPersistence(dataSource, i);
        byte[] content = dataSource.getContentBytes();
        ModelLoadResult result = ModelPersistence.readModel(SModelHeader.create(i), new ByteArrayInputSource(content), ModelLoadingState.FULLY_LOADED);

        Assert.assertTrue(result.getState() == ModelLoadingState.FULLY_LOADED);
        this.assertDeepModelEquals(helper.getTestModel().getSModel(), result.getModel());
        result.getModel().dispose();
      }
    }
    public void test_testPersistenceUpgrade() throws Exception {
      TestPersistenceHelper helper = new TestPersistenceHelper(myProject.getRepository());

      final ModelFactoryService mfsvc = myProject.getComponent(ModelFactoryService.class);
      // tests that it's possible to upgrade to the latest persistence from any supported persistence 
      for (int fromVersion = TestPersistenceHelper.START_PERSISTENCE_TEST_VERSION; fromVersion < ModelPersistence.LAST_VERSION; fromVersion++) {
        // prepare data source in requested version 
        PersistenceUtil.InMemoryStreamDataSource notUpgradedData = new PersistenceUtil.InMemoryStreamDataSource();
        helper.saveTestModelInPersistence(notUpgradedData, fromVersion);

        // load model from source version 
        SModelBase notUpgradedModel = ((SModelBase) PersistenceUtil.loadModel(notUpgradedData.getContentBytes(), mfsvc.getFactoryByType(PreinstalledModelFactoryTypes.PLAIN_XML)));

        // save model in last persistence 
        PersistenceUtil.InMemoryStreamDataSource upgradedData = new PersistenceUtil.InMemoryStreamDataSource();
        ModelPersistence.saveModel(notUpgradedModel.getSModel(), upgradedData, ModelPersistence.LAST_VERSION);

        // load model in last persistence from saved 
        SModelBase upgradedModel = ((SModelBase) PersistenceUtil.loadModel(upgradedData.getContentBytes(), mfsvc.getFactoryByType(PreinstalledModelFactoryTypes.PLAIN_XML)));

        // do test 
        this.assertDeepModelEquals(notUpgradedModel.getSModel(), upgradedModel.getSModel());

        notUpgradedModel.getSModel().dispose();
        upgradedModel.getSModel().dispose();
      }
    }


    public void assertDeepModelEquals(SModel expectedModel, SModel actualModel) {
      this.assertSameImports(expectedModel, actualModel);
      this.assertSameModelImports(expectedModel, actualModel);
      this.assertSameLanguageAspects(expectedModel, actualModel);
      this.assertSameNodesCollections("root", expectedModel.getRootNodes(), actualModel.getRootNodes());
    }
    public void assertSameNodesCollections(String objectName, Iterable<SNode> expected, Iterable<SNode> actual) {
      HashMap<org.jetbrains.mps.openapi.model.SNodeId, SNode> actualIdToNodeMap = new HashMap<org.jetbrains.mps.openapi.model.SNodeId, SNode>();
      for (SNode actualNode : actual) {
        actualIdToNodeMap.put(actualNode.getNodeId(), actualNode);
      }
      for (SNode expectedNode : expected) {
        org.jetbrains.mps.openapi.model.SNodeId rootId = expectedNode.getNodeId();
        SNode actualNode = actualIdToNodeMap.get(rootId);
        Assert.assertNotNull("Not found expected " + objectName + " " + expectedNode, actualNode);
        this.assertDeepNodeEquals(expectedNode, actualNode);
        actualIdToNodeMap.remove(rootId);
      }
      Assert.assertTrue("Found not expected " + objectName + " " + actualIdToNodeMap, actualIdToNodeMap.isEmpty());
    }
    public void assertSameModelImports(SModel expectedModel, SModel actualModel) {
      TestPersistenceHelper.assertListsEqual(this.getImportedModelUIDs(expectedModel), this.getImportedModelUIDs(actualModel), "model import");
    }
    public void assertSameLanguageAspects(SModel expectedModel, SModel actualModel) {
      List<SModel.ImportElement> expectedLanguageAspects = expectedModel.getImplicitImportsSupport().getAdditionalModelVersions();
      List<SModel.ImportElement> actualLanguageAspects = actualModel.getImplicitImportsSupport().getAdditionalModelVersions();
      for (SModel.ImportElement expectedEl : expectedLanguageAspects) {
        boolean found = false;
        for (SModel.ImportElement actualEl : actualLanguageAspects) {
          if (actualEl.getModelReference().equals(expectedEl.getModelReference())) {
            found = true;
            break;
          }
        }
        if (!(found)) {
          Assert.fail("Not found expected language aspect " + expectedEl.getModelReference());
        }
      }
      for (SModel.ImportElement actualEl : actualLanguageAspects) {
        boolean found = false;
        for (SModel.ImportElement expectedEl : expectedLanguageAspects) {
          if (actualEl.getModelReference().equals(expectedEl.getModelReference())) {
            found = true;
            break;
          }
        }
        if (!(found)) {
          Assert.fail("Unexpected language aspect " + actualEl.getModelReference());
        }
      }
    }
    public void assertSameImports(SModel expectedModel, SModel actualModel) {
      final ImplicitImportsLegacyHolder is1 = expectedModel.getImplicitImportsSupport();
      final ImplicitImportsLegacyHolder is2 = actualModel.getImplicitImportsSupport();
      is1.calculateImplicitImports();
      is2.calculateImplicitImports();
      TestPersistenceHelper.assertListsEqual(is1.getAdditionalModelVersions(), is2.getAdditionalModelVersions(), new Comparator<SModel.ImportElement>() {
        @Override
        public int compare(SModel.ImportElement import1, SModel.ImportElement import2) {
          return (import1.getModelReference().equals(import2.getModelReference()) ? 0 : 1);
        }
      }, "import");
    }
    public void assertDeepNodeEquals(SNode expectedNode, SNode actualNode) {
      Assert.assertEquals(this.getErrorString("concept", expectedNode, actualNode), expectedNode.getConcept().getQualifiedName(), actualNode.getConcept().getQualifiedName());
      this.assertPropertyEquals(expectedNode, actualNode);
      this.assertReferenceEquals(expectedNode, actualNode);
      this.assertDeepChildrenEquals(expectedNode, actualNode);
    }
    public void assertDeepChildrenEquals(SNode expectedNode, SNode actualNode) {
      Set<SContainmentLink> roles = new HashSet<SContainmentLink>();
      for (SNode child : expectedNode.getChildren()) {
        roles.add(child.getContainmentLink());
      }
      for (SNode child : actualNode.getChildren()) {
        roles.add(child.getContainmentLink());
      }
      for (SContainmentLink role : roles) {
        Iterable<? extends SNode> expectedChildren = expectedNode.getChildren(role);
        Iterable<? extends SNode> actualChildren = actualNode.getChildren(role);
        int esize = IterableUtil.asCollection(expectedChildren).size();
        int asize = IterableUtil.asCollection(actualChildren).size();
        Assert.assertEquals(this.getErrorString("child count in role " + role, expectedNode, actualNode), esize, asize);
        Iterator<? extends SNode> actualIterator = actualChildren.iterator();
        for (SNode expectedChild : expectedChildren) {
          SNode actualChild = actualIterator.next();
          Assert.assertEquals(this.getErrorString("children in role " + role, expectedNode, actualNode), expectedChild.getNodeId(), actualChild.getNodeId());
          this.assertDeepNodeEquals(expectedChild, actualChild);
        }
      }
    }
    public void assertPropertyEquals(SNode expectedNode, SNode actualNode) {
      HashSet<SProperty> propertes = new HashSet<SProperty>();
      propertes.addAll(IterableUtil.asCollection(expectedNode.getProperties()));
      propertes.addAll(IterableUtil.asCollection(actualNode.getProperties()));
      for (SProperty key : propertes) {
        String expectedProperty = expectedNode.getProperty(key);
        String actualProperty = actualNode.getProperty(key);
        Assert.assertEquals(this.getErrorString("property " + key, expectedNode, actualNode), expectedProperty, actualProperty);
      }
    }
    public String getErrorString(String text, SNode expectedNode, SNode actualNode) {
      return "Different " + text + " for nodes " + expectedNode + " and " + actualNode + ".";
    }
    public void assertReferenceEquals(SNode expectedNode, SNode actualNode) {
      Set<SReferenceLink> roles = new HashSet<SReferenceLink>();
      for (SReference r : expectedNode.getReferences()) {
        roles.add(r.getLink());
      }
      for (SReference r : actualNode.getReferences()) {
        roles.add(r.getLink());
      }
      Map<SReferenceLink, Set<SReference>> expRoleToReferenceMap = this.createRoleToReferenceMap(expectedNode);
      Map<SReferenceLink, Set<SReference>> actRoleToReferenceMap = this.createRoleToReferenceMap(actualNode);
      for (SReferenceLink role : roles) {
        Assert.assertEquals(this.getErrorString("different number of referents in role " + role, expectedNode, actualNode), expRoleToReferenceMap.get(role).size(), actRoleToReferenceMap.get(role).size());
        SReference expectedReference = expectedNode.getReference(role);
        SReference actualReference = actualNode.getReference(role);
        this.assertReferenceEquals(this.getErrorString("reference in role " + role, expectedNode, actualNode), expectedReference, actualReference);
      }
    }
    public Map<SReferenceLink, Set<SReference>> createRoleToReferenceMap(SNode n) {
      // XXX I don't get why there's map with set, MPS doesn't support associations with cardinality > 1 
      Map<SReferenceLink, Set<SReference>> expRoleToReferenceMap = new HashMap<SReferenceLink, Set<SReference>>();
      for (SReference ref : n.getReferences()) {
        Set<SReference> set = expRoleToReferenceMap.get(ref.getLink());
        if (set == null) {
          set = new HashSet<SReference>();
          expRoleToReferenceMap.put(ref.getLink(), set);
        }
        set.add(ref);
      }
      return expRoleToReferenceMap;
    }
    public void assertReferenceEquals(String errorString, SReference expectedReference, SReference actualReference) {
      if (expectedReference == null) {
        Assert.assertNull(errorString, actualReference);
        return;
      }
      Assert.assertNotNull(errorString, actualReference);
      // assertIdEqualsOrBothNull(errorString, expectedReference.getTargetNode(), actualReference.getTargetNode()); 
      Assert.assertEquals(errorString, ((jetbrains.mps.smodel.SReference) expectedReference).getResolveInfo(), ((jetbrains.mps.smodel.SReference) actualReference).getResolveInfo());
      Assert.assertEquals(errorString, expectedReference.getLink(), actualReference.getLink());
      Assert.assertEquals(errorString, expectedReference.getTargetNodeId(), actualReference.getTargetNodeId());
    }
    public void assertIdEqualsOrBothNull(String errorString, SNode expectedNode, SNode actualNode) {
      if (expectedNode == null) {
        Assert.assertNull(errorString, actualNode);
        return;
      }
      Assert.assertNotNull(errorString, actualNode);
      Assert.assertEquals(errorString, expectedNode.getNodeId(), actualNode.getNodeId());
    }
    public List<SModelReference> getImportedModelUIDs(SModel sModel) {
      List<SModelReference> references = new ArrayList<SModelReference>();
      for (SModel.ImportElement importElement : sModel.importedModels()) {
        references.add(importElement.getModelReference());
      }
      return Collections.unmodifiableList(references);
    }
  }

  private static final class CONCEPTS {
    /*package*/ static final SConcept ClassConcept$bK = MetaAdapterFactory.getConcept(0xf3061a5392264cc5L, 0xa443f952ceaf5816L, 0xf8c108ca66L, "jetbrains.mps.baseLanguage.structure.ClassConcept");
  }
}
