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

import org.custommonkey.xmlunit.DetailedDiff;
import org.custommonkey.xmlunit.ElementNameAndAttributeQualifier;
import org.custommonkey.xmlunit.XMLUnit;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Node;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.stream.StreamSource;
import java.io.StringReader;
import java.util.List;

/**
 * StringRspec extends BasicStringRspec with comparison methods.
 * <p/>
 * BasicStringRspec is in library to provide some basic Rspec parsing there, that does not require ModelRspec (which uses javafx).
 */
public class StringRspec extends BasicStringRspec {
    private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(StringRspec.class);

    public StringRspec(String xmlString) {
        super(xmlString);
    }

    /**
     * note: removes generated, generated_by and jfed namespace to make things easier. Also removes everything in jFed namespace.
     */
    private static Document isSimilarXmlHelper_StringToDocument(String rspec) throws TransformerException, ParserConfigurationException {
        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=\"http://jfed.iminds.be/rspec/ext/jfed/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" +
//                "    <xsl:template match=\"/*[namespace-uri()='jFed']\"/>\n" + doesnt work
                "    <xsl:template match=\"jFed:*\"/>\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();
    }

    /**
     * @return true if the rspec xml is the same, except for the order of elements and other unimportant changes
     */
    public static boolean isSimilarXml(String a, String b) {
        XMLUnit.setIgnoreWhitespace(true);
        XMLUnit.setIgnoreAttributeOrder(true);
        XMLUnit.setIgnoreComments(true);

        try {
            Document origRspecDoc = isSimilarXmlHelper_StringToDocument(a);
            Document reconstructedRspecDoc = isSimilarXmlHelper_StringToDocument(b);
//            String origRspecDocXml = XmlUtil.elementToXmlString(origRspecDoc.getDocumentElement());
//            String reconstructedRspecDocXml = XmlUtil.elementToXmlString(reconstructedRspecDoc.getDocumentElement());

            DetailedDiff diff = new DetailedDiff(XMLUnit.compareXML(
                    origRspecDoc,
                    reconstructedRspecDoc
            ));
            diff.overrideElementQualifier(new ElementNameAndAttributeQualifier());

            boolean similar = diff.similar();
            List<?> allDifferences = diff.getAllDifferences();
            if (!similar) {
                String diffString = "";
                for (Object o : allDifferences)
                    diffString += o + "\n";
                LOG.debug("Differences found: " + diffString + "\n\nA XML:\n" + a + "\n\nB XML\n" + b);
            }
            return similar;
        } catch (ParserConfigurationException e) {
            LOG.warn("StringRspec.isSimilarXml ParserConfigurationException. Will treat XML as different. " + e.getMessage() + "\n\nA XML:\n" + a + "\n\nB XML\n" + b + "\n---", e);
            return false;
        } catch (TransformerException e) {
            LOG.warn("StringRspec.isSimilarXml TransformerException. Will treat XML as different. " + e.getMessage() + "\n\nA XML:\n" + a + "\n\nB XML\n" + b + "\n---", e);
            return false;
        }
    }

    /**
     * @return true if the rspec xml is the same, except for the order of elements and other unimportant changes
     */
    public boolean isSimilarXml(StringRspec other) {
        return isSimilarXml(xmlString, other.xmlString);
    }
}
