package be.iminds.ilabt.jfed.rspec.parser;

import be.iminds.ilabt.jfed.rspec.model.ModelRspec;
import be.iminds.ilabt.jfed.rspec.model.RspecFactory;
import be.iminds.ilabt.jfed.rspec.model.impl.BasicRspecFactory;
import be.iminds.ilabt.jfed.util.XmlUtil;
import org.custommonkey.xmlunit.DetailedDiff;
import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier;
import org.custommonkey.xmlunit.XMLUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.Factory;
import org.testng.annotations.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.Source;
import javax.xml.transform.Templates;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;

/**
 * TestRspecParsing: Test Parsing into RspecModel
 * <p>
 * Most tests are simple: parse into ModelRspec, then convert back into string RSpec, and compare. If it is the same, all is ok.
 */
public class TestRspecReconstructable {
    private final static Logger LOG = LoggerFactory.getLogger(TestRspecReconstructable.class);

    private static RspecFactory basicRspecFactory = BasicRspecFactory.getInstance();
    protected final RspecFactory rspecFactory;

    protected TestRspecReconstructable(RspecFactory rspecFactory) {
        this.rspecFactory = rspecFactory;
    }

    public TestRspecReconstructable() {
        this.rspecFactory = basicRspecFactory;
    }

    /**
     * Parse the RSpec XML to a Document.
     * <p>
     * Note: this uses an XSLT transformation to remove some attributes that may be ignored when comparing.
     */
    private static Document rspecStringToDocument(String rspec) throws Exception {
        String xslt = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
                "<!-- Remove unwanted attributes or/and nodes -->\n" +
                "<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" xmlns:fo=\"http://www.w3.org/1999/XSL/Format\"" +
                "   xmlns:jfed-commands-bad=\"http://jfed.iminds.be/rspec/ext/jfed/1\" " +
                "   xmlns:jfed-commands-good=\"http://jfed.iminds.be/rspec/ext/jfed-command/1\" " +
                ">\n" +
                "    <xsl:output method=\"xml\" encoding=\"UTF-8\" indent=\"yes\"/>\n" +
                " \n" +
                "    <!-- Copy everything -->\n" +
                "    <xsl:template match=\"@*|node()|text()|comment()|processing-instruction()\">\n" +
                "        <xsl:copy>\n" +
                "            <xsl:apply-templates select=\"@*|node()|text()|comment()|processing-instruction()\"/>\n" +
                "        </xsl:copy>\n" +
                "    </xsl:template>\n" +
                " \n" +
                "    <!-- To remove attributes or nodes, simply write a matching template that doesn't do anything. Therefore, it is removed -->\n" +
                "    <xsl:template match=\"/*[local-name()='rspec']/@generated\"/>\n" +
                "    <xsl:template match=\"/*[local-name()='rspec']/@generated_by\"/>\n" +
                "    <xsl:template match=\"/*[local-name()='rspec']/@xmlns:jFed\"/>\n" +
                "" +
                "     <!-- automatically change an old mistake in namespace to the correct, as rspec parsing will also do this. -->" +
                "    <xsl:template match=\"jfed-commands-bad:experimentCommand\">\n" +
                "    <xsl:element name=\"jfed-commands-good:experimentCommand\">\n" +
                "         <xsl:apply-templates select=\"node() | @*\" />\n" +
                "    </xsl:element>" +
                "    </xsl:template>\n" +
                "\n" +
                " \n" +
                "</xsl:stylesheet>";

        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);

        TransformerFactory factory = TransformerFactory.newInstance();
        Templates template = factory.newTemplates(new StreamSource(new StringReader(xslt)));
        Transformer xformer = template.newTransformer();

        DocumentBuilder builder = dbf.newDocumentBuilder();
        assert builder.isNamespaceAware();
        Document doc = builder.newDocument();
        DOMResult result = new DOMResult(doc);

        Source source = new StreamSource(new StringReader(rspec));
        xformer.transform(source, result);

        assert result.getNode().getNodeType() == Node.DOCUMENT_NODE;

        return (Document) result.getNode();
    }

    public static boolean testSameXml(String origRspec, String reconstructedRspec) throws Exception {
        XMLUnit.setIgnoreWhitespace(true);
        XMLUnit.setIgnoreAttributeOrder(true);
        XMLUnit.setIgnoreComments(true);

        try {
            Document origRspecDoc = rspecStringToDocument(origRspec);
            Document reconstructedRspecDoc = rspecStringToDocument(reconstructedRspec);
            String origRspecDocXml = XmlUtil.elementToXmlString(origRspecDoc.getDocumentElement());
            String reconstructedRspecDocXml = XmlUtil.elementToXmlString(reconstructedRspecDoc.getDocumentElement());

            DetailedDiff diff = null;
            diff = new DetailedDiff(XMLUnit.compareXML(
                    origRspecDoc,
                    reconstructedRspecDoc
//                    origRspec,
//                    reconstructedRspec
            ));
//            IgnoreUnimportantRspecFields listener = new IgnoreUnimportantRspecFields();
//            diff.overrideDifferenceListener(listener);
//            diff.overrideElementQualifier(new RecursiveElementNameAndTextQualifier()); //ignore all sorts of position switches for Elements
            diff.overrideElementQualifier(new ElementNameAndAttributeQualifier());

            List<?> allDifferences = diff.getAllDifferences();

            //normally, this is good enough for a test
            //Assert.assertTrue(diff.similar(), "Differences found: " + diff.toString() + "\n\nExpected XML:\n" + origRspecDocXml + "\n\nActual XML\n" + reconstructedRspecDocXml);

            //but we want ot let IDE show Xml difference, so we do this instead:
            if (!diff.similar()) {
                System.err.println("Differences found: " + diff.toString() + "\n");
                System.err.flush();
                Assert.assertEquals(reconstructedRspecDocXml, origRspecDocXml); //this should always fail
                Assert.assertTrue(diff.similar()); //just in case above doesn't fail
            }

        } catch (SAXException e) {
            throw new RuntimeException("SAXException: " + e.getMessage() + "\n\nExpected XML:\n" + origRspec + "\n\nActual XML\n" + reconstructedRspec + "\n---", e);
        } catch (IOException e) {
            throw new RuntimeException("IOException: " + e.getMessage() + "\n\nExpected XML:\n" + origRspec + "\n\nActual XML\n" + reconstructedRspec + "\n---", e);
        }

        return true;
    }

    public static class ReconstructableTest {
        private final String origRspec;
        private final RspecFactory testRspecFactory;

        public ReconstructableTest(String origRspec, RspecFactory rspecFactory) {
            this.origRspec = origRspec;
            this.testRspecFactory = rspecFactory;
        }

        @Test
        public void isReconstructable() throws Throwable {
            testXmlStaxReconstructable(origRspec, testRspecFactory);
        }

        @Test
        public void isReconstructableWithDefaultNamespace() throws Exception {
            testRSpecIsReconstructedWithDefaultNamespace(origRspec, testRspecFactory);
        }

        @Test
        public void isNotLosingData() throws RspecParseException, XMLStreamException {
            ModelRspec tstModelRspec = new StaxRspecParser(origRspec, testRspecFactory).call();
            RspecCompare compare = new RspecCompare(tstModelRspec.toGeni3Rspec(), origRspec);

            Assert.assertTrue(compare.isEqual(), "differences: " + compare.getDifferences());
        }

        @Test
        public void testCopyConstuctor() throws Throwable {
            try {
                ModelRspec tstModelRspec = new StaxRspecParser(origRspec, testRspecFactory).call();
                ModelRspec copyModelRspec = testRspecFactory.copyModelRspec(tstModelRspec, tstModelRspec.getType());
                String rpsecOrig = tstModelRspec.toGeni3Rspec();
                String rpsecCopy = copyModelRspec.toGeni3Rspec();

                RspecCompare compare = new RspecCompare(rpsecOrig, rpsecCopy, "Original RSpec", "Copied RSpec");
                if (!compare.isEqual())
                    Assert.assertEquals(rpsecCopy, rpsecOrig, compare.getDifferences().toString()); //never the same, certainly not if compare differs, and gives good readable error message.
                //to vague error
//            Assert.assertTrue(compare.isEqual(), compare.getDifferences().toString());
            } catch (Exception t) {
                LOG.error("Error testing copy constructor of RSpec (fact="+testRspecFactory.getClass().getName()+"):\n" +
                        "\""+origRspec+"\"", t);
                throw  t;
            }
        }
    }

    @Factory
    public static Object[] createReconstructableTests() {
        return createReconstructableTestsInternal(basicRspecFactory);
    }

    public static Object[] createReconstructableTestsInternal(RspecFactory rspecFactory) {
        List<String> rspecs = new ArrayList<String>();
        rspecs.add(TestRspecsLibrary.rspecA);
        rspecs.add(TestRspecsLibrary.rspecAstartComment);
        rspecs.add(TestRspecsLibrary.rspecAtailComment);
        rspecs.add(TestRspecsLibrary.rspecA_nitoschannel);
        rspecs.add(TestRspecsLibrary.rspecA_comment);
        rspecs.add(TestRspecsLibrary.rspecA_routablecontrolip);
        rspecs.add(TestRspecsLibrary.rspecA_xen);
        rspecs.add(TestRspecsLibrary.rspecWithLinkA);
        rspecs.add(TestRspecsLibrary.rspecWithLinkAutoShareLan);
        rspecs.add(TestRspecsLibrary.rspecB);
        rspecs.add(TestRspecsLibrary.rspecC);
        rspecs.add(TestRspecsLibrary.rspecCsimple);
        rspecs.add(TestRspecsLibrary.rspecCsimple2);
        rspecs.add(TestRspecsLibrary.rspecManifestA);
        rspecs.add(TestRspecsLibrary.rspecManifestAvlan);
        rspecs.add(TestRspecsLibrary.rspecManifestAcustomProxy);
        rspecs.add(TestRspecsLibrary.rspecManifestAcustomProxyAndProxyLogin);
        rspecs.add(TestRspecsLibrary.rspecD);
        rspecs.add(TestRspecsLibrary.rspecE);
        rspecs.add(TestRspecsLibrary.rspecE2);
        rspecs.add(TestRspecsLibrary.rspecE3);
        rspecs.add(TestRspecsLibrary.rspecE4);
        rspecs.add(TestRspecsLibrary.rspecOpenflow13test);
        rspecs.add(TestRspecsLibrary.rspecE5);
        rspecs.add(TestRspecsLibrary.rspecE6);
        rspecs.add(TestRspecsLibrary.rspecF);
        rspecs.add(TestRspecsLibrary.rspecG);
        rspecs.add(TestRspecsLibrary.rspecH);
        rspecs.add(TestRspecsLibrary.rspecI);
        rspecs.add(TestRspecsLibrary.rspecJ);
        rspecs.add(TestRspecsLibrary.rspecK1);
        rspecs.add(TestRspecsLibrary.rspecK3);
        rspecs.add(TestRspecsLibrary.stitchedManifestA);
        rspecs.add(TestRspecsLibrary.stitchedManifestB);
        rspecs.add(TestRspecsLibrary.emuManifestProb);
        rspecs.add(TestRspecsLibrary.requestWithCommandsAndBarriersStaxErrorFormat);
        rspecs.add(TestRspecsLibrary.advertisementWithOpenflowDatapaths);

        rspecs.add(TestRspecsLibrary.requestNodeWithInterfaceButNoLink);

        rspecs.add(TestRspecsLibrary.advertisementWithNitosLeases);
        rspecs.add(TestRspecsLibrary.reservationWithNitosLeases);
        rspecs.add(TestRspecsLibrary.manifestWithNitosLeases);

        rspecs.add(TestRspecsLibrary.advertisementWithTypeSlots);
        rspecs.add(TestRspecsLibrary.advertisementWithUnlimitedTypeSlots);

        rspecs.add(TestRspecsLibrary.fedibbtdev814prob); //see https://ibcn-jira.intec.ugent.be/browse/FEDIBBTDEV-814

        rspecs.add(TestRspecsLibrary.omnRequestRspecExample);

        rspecs.add(TestRspecsLibrary.requestRspecNodeBlockStoreExtExample);
        rspecs.add(TestRspecsLibrary.requestRspecLinkBlockStoreExtExample);
        rspecs.add(TestRspecsLibrary.requestRspecLinkBestEffortExtExample);
        rspecs.add(TestRspecsLibrary.requestRspecLinkMultiplexingExtExample);
        rspecs.add(TestRspecsLibrary.requestRspecLinkInterswitchDisallowExtExample);
        rspecs.add(TestRspecsLibrary.requestRspecRoutablePoolExtExample);

        // rspecs.add(TestRspecsLibrary.nitosAdvertisement);

        rspecs.add(TestRspecsLibrary.rspecA_routableAddresses);
        rspecs.add(TestRspecsLibrary.felixRequest);

        rspecs.add(TestRspecsLibrary.performltrManifest);

        rspecs.add(TestRspecsLibrary.rspecAwithExecuteAnsiblePlaybookA);
        rspecs.add(TestRspecsLibrary.rspecAwithExecuteAnsiblePlaybookB);

        try {
            URL url = TestRspecReconstructable.class.getResource("delaylink.rspec");
            Path resPath = java.nio.file.Paths.get(url.toURI());
            String delayRspec = new String(java.nio.file.Files.readAllBytes(resPath), "UTF8");
            rspecs.add(delayRspec);
        } catch (Exception e) {
            LOG.error("Failed to load delaylink.rspec", e); //ignore if not available.
        }

        List<Object> res = new ArrayList<Object>();
        for (String rspec : rspecs)
            res.add(new ReconstructableTest(rspec, rspecFactory));
        return res.toArray();
    }

    //    public static void testXmlReconstructable(String origRspec) throws Exception {
//        testXmlStaxReconstructable(origRspec, new FXRspecFactory());
//    }
    public static void testXmlStaxReconstructable(String origRspec, RspecFactory rspecFactory) throws Throwable {
        System.err.flush();
        System.out.flush();
        ModelRspec rspec;
        try {
            rspec = new StaxRspecParser(origRspec, rspecFactory).call();
        } catch (Exception e) {
            System.out.println("Failed to parse: origspec =\n\n" + origRspec + "\n\n");
            System.out.flush();
            throw e;
        }
        String reconstructedRspec = new StaxRspecWriter(rspec).call();
        System.err.flush();
        System.out.flush();
        System.out.println("origspec          =" + origRspec);
        System.out.println("reconstructedRspec=" + reconstructedRspec);
        System.out.flush();
        System.err.flush();
        Assert.assertTrue(testSameXml(origRspec, reconstructedRspec));
    }

    /**
     * test if rspec namespace is default namespace in reconstructed RSpec
     * (if this test fails, the RSpec is correctly reconstructable, but some bug messes up the default namespace)
     */
    public static void testRSpecIsReconstructedWithDefaultNamespace(String rspecXmlString, RspecFactory rspecFactory) throws RspecParseException, XMLStreamException {
        ModelRspec rspec = new StaxRspecParser(rspecXmlString, rspecFactory).call();
        String reconstructedRspec = rspec.toGeni3Rspec();
        Assert.assertTrue(reconstructedRspec.contains("xmlns=\"http://www.geni.net/resources/rspec/3\""));
    }


    @Test(expectedExceptions = {be.iminds.ilabt.jfed.rspec.parser.RspecParseException.class})
    public void testRspecFailAmpersand() throws Throwable {
        testXmlStaxReconstructable(TestRspecsLibrary.rspecK2, rspecFactory);
        testRSpecIsReconstructedWithDefaultNamespace(TestRspecsLibrary.rspecK2, rspecFactory);
    }

    @Test
    public void testRspecNamedNS() throws Throwable {
        testXmlStaxReconstructable(TestRspecsLibrary.rspecStringNamedNS, rspecFactory);
    }

    /**
     * Test if xslt transformation is removing generated and generated_by from the result rspecs
     */
    @Test
    public void testRspecTransform() throws Exception {
        {
            String testXml = "<rspec generated=\"bar\" generated_by=\"foo\" test=\"keepme\"/>";
            Document doc = rspecStringToDocument(testXml);
            Element e = doc.getDocumentElement();
            Assert.assertEquals(e.getTagName(), "rspec", "Problem with transform: " + XmlUtil.elementToXmlString(e));
            Assert.assertTrue(e.hasAttribute("test"), "Problem with transform: " + XmlUtil.elementToXmlString(e));
            Assert.assertEquals(e.getAttribute("test"), "keepme", "Problem with transform: " + XmlUtil.elementToXmlString(e));
            Assert.assertFalse(e.hasAttribute("generated"), "Problem with transform: " + XmlUtil.elementToXmlString(e));
            Assert.assertFalse(e.hasAttribute("generated_by"), "Problem with transform: " + XmlUtil.elementToXmlString(e));
        }

        {
            String testXml = "<rspec " +
                    "generated=\"bar\" " +
                    "generated_by=\"foo\" " +
                    "test=\"keepme\" " +
                    "xsi:schemaLocation=\"http://www.geni.net/resources/rspec/3 http://www.geni.net/resources/rspec/3/request.xsd  \" " +
                    "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " +
                    "xmlns=\"http://www.geni.net/resources/rspec/3\"/>";
            Document doc = rspecStringToDocument(testXml);
            Element e = doc.getDocumentElement();
            Assert.assertEquals(e.getTagName(), "rspec", "Problem with transform: " + XmlUtil.elementToXmlString(e));
            Assert.assertTrue(e.hasAttribute("test"), "Problem with transform: " + XmlUtil.elementToXmlString(e));
            Assert.assertEquals(e.getAttribute("test"), "keepme", "Problem with transform: " + XmlUtil.elementToXmlString(e));
            Assert.assertFalse(e.hasAttribute("generated"), "Problem with transform: " + XmlUtil.elementToXmlString(e));
            Assert.assertFalse(e.hasAttribute("generated_by"), "Problem with transform: " + XmlUtil.elementToXmlString(e));
        }
    }

    private RspecCompare createRspecCompare(String originalRspec) throws XMLStreamException, RspecParseException {
        ModelRspec tstModelRspec = new StaxRspecParser(originalRspec, rspecFactory).call();
        return
                new RspecCompare(tstModelRspec.toGeni3Rspec(), originalRspec, "the converted RSpec", "the original RSpec");
    }

    private void testLosingData(String originalRspec) throws XMLStreamException, RspecParseException {
        ModelRspec tstModelRspec =
                new StaxRspecParser(originalRspec, rspecFactory)
//                        .setUseNitosENodeBHack(false)
                        .call();

        RspecCompare compareWithOrig =
                new RspecCompare(tstModelRspec.toGeni3Rspec(
//                        ModelRspec.RequestRspecSpecialCases.NITOS_NO_E_NODE_B_HACK
                ),
                        originalRspec,
                        "the converted RSpec",
                        "the original RSpec");

        Assert.assertTrue(compareWithOrig.isEqual(), "Differences: " + compareWithOrig.getDifferences());

    }


    @Test
    public void testIsLosingData1() throws RspecParseException, XMLStreamException {
        RspecCompare compare = createRspecCompare(TestRspecsLibrary.testSamerspeclinkA);

        Assert.assertTrue(compare.isEqual(), "differences: " + compare.getDifferences());
    }

    @Test
    /**
     *  These link have empty properties. They should be reconstructed.
     *  There is also 1 property missing. That should remain missing on reconstruct.
     */
    public void testIsLosingData2() throws RspecParseException, XMLStreamException {

        RspecCompare compare = createRspecCompare(TestRspecsLibrary.rspecWithEmptyLinkProperties);

        Assert.assertTrue(compare.isEqual(), "differences: " + compare.getDifferences());
    }

    @Test
    public void testIsLosingData3() throws RspecParseException, XMLStreamException {
        RspecCompare compare = createRspecCompare(TestRspecsLibrary.testSamerspecAdiffA);

        Assert.assertFalse(compare.isEqual(), "differences: " + compare.getDifferences());
    }

    @Test
    public void testIsLosingData4() throws RspecParseException, XMLStreamException {
        RspecCompare compare = createRspecCompare(TestRspecsLibrary.testSamerspecAdiffB);

        Assert.assertFalse(compare.isEqual(), "differences: " + compare.getDifferences());
    }

    @Test
    /**
     *  Model must NOT automatically adds missing sliver with default value
     */
    public void testIsLosingData5() throws RspecParseException, XMLStreamException {
        RspecCompare compare = createRspecCompare(TestRspecsLibrary.testSamerspecAdiffD);

        Assert.assertTrue(compare.isEqual(), "differences: " + compare.getDifferences());
    }

    @Test
    public void testIsLosingData6() throws RspecParseException, XMLStreamException {
        //should also not lose data for manifest

        RspecCompare compare = createRspecCompare(TestRspecsLibrary.testSamerspecAdiffE);

        Assert.assertTrue(compare.isEqual(), "differences: " + compare.getDifferences());
        //Model must NOT automatically adds missing sliver with default value
    }

    @Test
    public void testIsLosingData7() throws RspecParseException, XMLStreamException {
        RspecCompare compare = createRspecCompare(TestRspecsLibrary.testSamerspecAdiffF);

        Assert.assertTrue(compare.isEqual(), "differences: " + compare.getDifferences());
    }

    @Test
    public void testIsLosingDataRoutablePool() throws RspecParseException, XMLStreamException {
        RspecCompare compare = createRspecCompare(TestRspecsLibrary.testRoutablePool);

        Assert.assertTrue(compare.isEqual(), "differences: " + compare.getDifferences());
    }



    @Test
    public void testPleSpecialcase() throws Exception {
        ModelRspec rspec = new StaxRspecParser(TestRspecsLibrary.pleRequest, rspecFactory).call();

        String reconstructedRspecGeneral = rspec.toGeni3Rspec();
        String reconstructedRspecPle = rspec.toGeni3Rspec(ModelRspec.RequestRspecSpecialCases.PLE);

        System.out.println("default case RSpec: " + reconstructedRspecGeneral);
        System.out.println("PLE special case RSpec: " + reconstructedRspecPle);
        System.out.flush();

        ///schema is actually ignored in comparison, so comparing with pleRequestNoSchema or pleRequest is the same
        Assert.assertTrue(testSameXml(TestRspecsLibrary.pleRequestNoSchema, reconstructedRspecPle));
        Assert.assertTrue(testSameXml(TestRspecsLibrary.pleRequest, reconstructedRspecGeneral));

        //check that default namespace is ok.
        Assert.assertTrue(reconstructedRspecGeneral.contains("xmlns=\"http://www.geni.net/resources/rspec/3\""));
        Assert.assertTrue(reconstructedRspecPle.contains("xmlns=\"http://www.geni.net/resources/rspec/3\""));
        Assert.assertTrue(reconstructedRspecGeneral.contains("<sliver/>"));
        Assert.assertTrue(reconstructedRspecPle.contains("<sliver/>"));
        Assert.assertTrue(reconstructedRspecGeneral.contains("<sliver_type name=\"plab-vserver\"/>"));
        Assert.assertTrue(reconstructedRspecPle.contains("<sliver_type name=\"plab-vserver\"/>"));

        //assert schema is not included for ple special case
        Assert.assertTrue(reconstructedRspecGeneral.contains("xsi:schemaLocation="));
        Assert.assertFalse(reconstructedRspecPle.contains("xsi:schemaLocation="));
    }


    //seperate for debugging when it goes wrong.
    @Test
    public void testManifestRspecWithDuplicates() throws Throwable {
        String origRspec = TestRspecsLibrary.rspecWithDuplicateIfaceNames_reconstructable;
        testXmlStaxReconstructable(origRspec, rspecFactory);
        testRSpecIsReconstructedWithDefaultNamespace(origRspec, rspecFactory);

        RspecCompare compare = createRspecCompare(origRspec);

        Assert.assertTrue(compare.isEqual(), "differences: " + compare.getDifferences());
    }
}
