/*
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.util.fxdesigner.model.testing;

import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.reactfx.collection.LiveList;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.xml.sax.ErrorHandler;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import net.sourceforge.pmd.util.fxdesigner.util.DesignerUtil;

public class TestXmlDumper {


    public static final String SCHEMA_LOCATION = "http://pmd.sourceforge.net/rule-tests http://pmd.sourceforge.net/rule-tests_1_0_0.xsd";
    private static final String NS = "http://pmd.sourceforge.net/rule-tests";

    private void appendTests(Document doc, List<LiveTestCase> descriptors) {
        Element root = doc.getDocumentElement();

        for (LiveTestCase descriptor : descriptors) {
            Element elt = doc.createElementNS(NS, "test-code");
            appendSingle(elt, descriptor, doc);
            root.appendChild(elt);
            root.appendChild(doc.createTextNode("\n\n"));
        }
    }

    private void appendSingle(Element testCode, LiveTestCase descriptor, Document doc) {

        if (descriptor.isIgnored()) {
            testCode.setAttributeNS(NS, "regressionTest", "false");
        }


        Element descriptionElt = doc.createElementNS(NS, "description");
        descriptionElt.setTextContent(descriptor.getDescription());
        testCode.appendChild(descriptionElt);

        Map<String, String> properties = descriptor.getLiveProperties().getNonDefault();

        if (!properties.isEmpty()) {
            properties.forEach((k, v) -> {
                Element element = doc.createElementNS(NS, "rule-property");
                element.setAttribute("name", k);
                element.setTextContent(v);
                testCode.appendChild(element);
            });

        }

        LiveList<LiveViolationRecord> expectedViolations = descriptor.getExpectedViolations();

        Element numViolations = doc.createElementNS(NS, "expected-problems");
        numViolations.setTextContent(expectedViolations.size() + "");
        testCode.appendChild(numViolations);

        if (expectedViolations.size() > 0 && expectedViolations.stream().allMatch(it -> it.getMessage() != null)) {
            Element messages = doc.createElementNS(NS, "expected-messages");
            for (LiveViolationRecord record : expectedViolations) {
                Element r = doc.createElementNS(NS, "message");
                r.setTextContent(record.getMessage());
                messages.appendChild(r);
            }
            testCode.appendChild(messages);
        }

        if (expectedViolations.size() > 0 && expectedViolations.stream().allMatch(it -> it.getRange() != null)) {
            Element linenos = doc.createElementNS(NS, "expected-linenumbers");

            String joined = expectedViolations.stream().map(it -> it.getRange().startPos.line + "")
                                              .collect(Collectors.joining(","));
            linenos.setTextContent(joined);
            testCode.appendChild(linenos);

        }

        Element codeElement = doc.createElementNS(NS, "code");
        codeElement.appendChild(doc.createCDATASection("\n" + descriptor.getSource().trim() + "\n        "));
        testCode.appendChild(codeElement);


        boolean hasNonDefaultVersion = descriptor.languageVersionProperty()
                                                 .getOpt()
                                                 .filter(it -> it.getLanguage().getDefaultVersion() != it)
                                                 .isPresent();
        if (hasNonDefaultVersion) {
            Element sourceType = doc.createElementNS(NS, "source-type");
            sourceType.setTextContent(descriptor.getLanguageVersion().getTerseName());
            testCode.appendChild(sourceType);
        }
    }


    public static String dumpXmlTests(TestCollection collection) throws Exception {
        StringWriter out = new StringWriter();
        dumpXmlTests(out, collection);
        return out.toString();
    }


    public static void dumpXmlTests(Path path, TestCollection collection) throws Exception {
        try (OutputStream is = Files.newOutputStream(path);
             Writer out = new OutputStreamWriter(is)) {

            dumpXmlTests(out, collection);
        }
    }

    public static void dumpXmlTests(Writer outWriter, TestCollection collection) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        try {
            Schema schema = schemaFactory.newSchema(DesignerUtil.getResource("testschema/rule-tests_1_0_0.xsd"));
            dbf.setSchema(schema);
            dbf.setNamespaceAware(true);
            DocumentBuilder builder = getDocumentBuilder(dbf);


            Document doc = builder.newDocument();
            Element root = doc.createElementNS(NS, "test-data");
            doc.appendChild(root);

            root.setAttribute("xmlns", NS);
            root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
            root.setAttribute("xsi:schemaLocation", SCHEMA_LOCATION);

            new TestXmlDumper().appendTests(doc, collection.getStash());

            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
            transformer.setOutputProperty(OutputKeys.METHOD, "xml");
            transformer.setOutputProperty(OutputKeys.INDENT, "yes");
            transformer.setOutputProperty(OutputKeys.CDATA_SECTION_ELEMENTS, "{" + NS + "}code");
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
            // FIXME whatever i try this indents by 3 spaces which is not
            //  compatible with our style
            transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");


            transformer.transform(new DOMSource(doc), new StreamResult(outWriter));
        } finally {
            outWriter.close();
        }

    }

    private static DocumentBuilder getDocumentBuilder(DocumentBuilderFactory dbf) throws ParserConfigurationException {
        DocumentBuilder builder = dbf.newDocumentBuilder();
        builder.setErrorHandler(new ErrorHandler() {
            @Override
            public void warning(SAXParseException exception) throws SAXException {
                throw exception;
            }

            @Override
            public void fatalError(SAXParseException exception) throws SAXException {
                throw exception;
            }

            @Override
            public void error(SAXParseException exception) throws SAXException {
                throw exception;
            }
        });
        return builder;
    }
}