package com.r11.tools.xml;

import com.r11.tools.controller.internal.XPathQueryResult;
import org.apache.xpath.XPathAPI;
import org.apache.xpath.objects.XObject;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.traversal.NodeIterator;
import org.xml.sax.InputSource;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.*;

/**
 * Handler for Xalan engine
 * @author Wojciech Czop
 */
public class Xalan implements XmlEngine{

    /**
     * Transforms string containing xml document via xslt
     * @param data xml to be transformed
     * @param transform xslt
     * @return transformed xml
     * @throws Exception thrown on stylesheet or transformation errors
     */
    public String processXSLT(String data, String transform) throws Exception {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();
        Document document = builder.parse(new InputSource(new StringReader(data)));

        StreamSource stylesource = new StreamSource(new StringReader(transform));
        Transformer transformer = TransformerFactory.newInstance().newTransformer(stylesource);
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");

        Source source = new DOMSource(document);
        StringWriter sw = new StringWriter();
        Result outputTarget = new StreamResult(sw);
        transformer.transform(source, outputTarget);

        return sw.toString();
    }

    private boolean isTextNode(Node n) {
        if (n == null)
            return false;
        short nodeType = n.getNodeType();
        return nodeType == Node.CDATA_SECTION_NODE || nodeType == Node.TEXT_NODE;
    }

    /**
     * Process xpath and return either node or wrapped atomic value
     * @param data xml
     * @param transform xpath
     * @return xml processed using given xpath
     * @throws Exception thrown on node building errors or invalid xpath
     */
    public XPathQueryResult processXPath(String data, String transform, String version) throws Exception {

        // Set up a DOM tree to query.
        InputSource in = new InputSource(new StringReader(data));
        DocumentBuilderFactory dfactory = DocumentBuilderFactory.newInstance();
        dfactory.setNamespaceAware(true);
        Document doc = dfactory.newDocumentBuilder().parse(in);

        // Set up an identity transformer to use as serializer.
        Transformer serializer = TransformerFactory.newInstance().newTransformer();
        serializer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");

        // Use the simple XPath API to select a nodeIterator.
        try {
            NodeIterator nl = XPathAPI.selectNodeIterator(doc, transform);

            // Serialize the found nodes to result object.
            StringBuilder resultString = new StringBuilder();
            Node n;
            while ((n = nl.nextNode())!= null) {
                StringBuilder sb;
                if (isTextNode(n)) {
                    // DOM may have more than one node corresponding to a
                    // single XPath text node.  Coalesce all contiguous text nodes
                    // at this level
                    for (Node nn = n.getNextSibling(); isTextNode(nn); nn = nn.getNextSibling()) {
                        resultString.append(nn.getNodeValue());
                    }
                } else {
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    serializer.transform(new DOMSource(n), new StreamResult(new OutputStreamWriter(outputStream)));
                    resultString.append(outputStream);
                }
                resultString.append("\n");
            }
            return new XPathQueryResult(resultString.toString(), "node");
        } catch (TransformerException e) {
            String returnData = XPathAPI.eval(doc, transform).toString();
            return new XPathQueryResult(returnData, "string");
        }

    }

    /**
     * Returns version of the processor
     * @return version of the processor
     */
    public String getVersion(){
        return org.apache.xalan.Version.getVersion();
    }

    /**
     * Validates string representation of the xml document against xsd schema
     * @param data xml document
     * @param xsd xsd schema
     * @return statement of validity
     * @throws Exception thrown on invalid xsd schema or xml
     */
    public String validate(String data, String xsd) throws Exception {
        Source dataSource = new StreamSource(new StringReader(data));
        Source xsdSource = new StreamSource(new StringReader(xsd));
        SchemaFactory schemaFactory = SchemaFactory
                .newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Schema schema = schemaFactory.newSchema(xsdSource);
        Validator validator = schema.newValidator();
        validator.validate(dataSource);
        return "XML file is valid";
    }
}
