/**
 * XMLNodeAsXQueryParameterTest.java
 *
 * 2005 by O2 IT Engineering
 * Zurich,  Switzerland (CH)
 */

package org.exist.xquery;

import org.exist.test.ExistXmldbEmbeddedServer;
import org.exist.xmldb.XmldbURI;
import org.junit.ClassRule;
import org.junit.Test;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xmldb.api.base.ResourceSet;
import org.xmldb.api.base.XMLDBException;
import org.xmldb.api.modules.XMLResource;

import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
import java.util.HashMap;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import static org.junit.Assert.fail;

/**
 * Class to test eXist's capability to handle XML Nodes as XQuery parameter.
 *
 * @author Tobias Wunden
 * @version 1.0
 */

public class XMLNodeAsXQueryParameterTest {

	@ClassRule
	public final static ExistXmldbEmbeddedServer existEmbeddedServer = new ExistXmldbEmbeddedServer(false, true);

	/**
	 * This test passes a W3C dom node as an xquery parameter to eXist and tries
	 * to read it back in:
	 * <ul>
	 * <li>Register a database instance</li>
	 * <li>Write a document to the database using the XQueryService</li>
	 * <li>Read the document from the database using XmlDB</li>
	 * <li>Check for the document content</li>
	 * </ul>
	 */
	@Test
	public final void xmlNodeAsXQueryParameter() throws XMLDBException, ParserConfigurationException, IOException, SAXException, IllegalAccessException, InstantiationException, ClassNotFoundException {
		final String document = "test.xml";

		// create document
		final String xmlDocument = "<XmlNodeTest/>";

		// write document to the database
        store(xmlDocument, document);


		// add content using XUpdate
		final String xmlData = "<content/>";
		try(final Reader reader = new StringReader(xmlData)) {
			final InputSource is = new InputSource(reader);
			final DocumentBuilder docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			final Document doc = docBuilder.parse(is);
			xupdate(doc.getFirstChild());

			// read document back from database
			final Node root = load(document);
			if (root == null) {
				fail("Document " + document + " was not found in the database!");
			}

			// issue xpath query
			final Node node = root.getFirstChild();
			if (node == null) {
				fail("XUpdate:append using w3c dom node failed! Content node was not returned.");
			}
		}
	}

	/**
	 * Stores the given xml fragment into the database.
	 *
	 * @param xml the xml document
	 * @param document the document name
	 * @throws XMLDBException on database error
	 */
	private void store(final String xml, final String document) throws XMLDBException {
		final StringBuilder query = new StringBuilder();
		query.append("xquery version \"1.0\";");
		query.append("declare namespace xmldb=\"http://exist-db.org/xquery/xmldb\";");
		query.append("declare variable $local:document external;");
		query.append("declare variable $local:data external;");
		query.append("let $loggedIn := xmldb:login(\"" + XmldbURI.ROOT_COLLECTION + "\", \"admin\", \"\"),");
		query.append("$doc := xmldb:store(\"" + XmldbURI.ROOT_COLLECTION + "\", $local:document, $local:data)");
		query.append("return <result/>");

		final Map<String, Object> externalVariables = new HashMap<>();
		externalVariables.put("local:document", document);
		externalVariables.put("local:data", xml);
		existEmbeddedServer.executeQuery(query.toString(), externalVariables);
	}

	/**
	 * Updates the given xml fragment in the database using XUpdate.
	 *
	 * @param data the data node
	 * @throws XMLDBException on database error
	 */
	private void xupdate(final Object data) throws XMLDBException {
		if (data == null) {
			fail("Cannot update because data is 'null'");
		}

		final StringBuilder query = new StringBuilder();
		query.append("xquery version \"1.0\";");
		query.append("declare namespace xmldb=\"http://exist-db.org/xquery/xmldb\";");
		query.append("declare variable $local:data external;");
		query.append("declare variable $xupdate {");
		query.append("<xu:modifications version=\"1.0\" xmlns:xu=\"http://www.xmldb.org/xupdate\">");
		query.append("<xu:append select=\"xmldb:xcollection('" + XmldbURI.ROOT_COLLECTION + "')/XmlNodeTest\">");
		query.append("{$local:data}");
		query.append("</xu:append>");
		query.append("</xu:modifications>");
		query.append("};");
		query.append("let $isLoggedIn := xmldb:login('" + XmldbURI.ROOT_COLLECTION + "', \"admin\", \"\"),");
		query.append("$mods := xmldb:update(\"" + XmldbURI.ROOT_COLLECTION + "\", $xupdate)");
		query.append("return <modifications>{$mods}</modifications>");

		final Map<String, Object> externalVariables = new HashMap<>();
		externalVariables.put("local:data", data);
		existEmbeddedServer.executeQuery(query.toString(), externalVariables);
	}

	/**
	 * Loads the xml document identified by <code>document</code> from the database.
	 *
	 * @param document the document to load
	 * @throws XMLDBException on database error
	 */
	private Node load(final String document) throws XMLDBException {
		final StringBuilder query = new StringBuilder();
		query.append("xquery version \"1.0\";");
		query.append("declare variable $local:document external;");
		query.append("let $survey := xmldb:document(string-join(('" + XmldbURI.ROOT_COLLECTION + "', $local:document), '/'))");
		query.append("return $survey");

		final Map<String, Object> externalVariables = new HashMap<>();
		externalVariables.put("local:document", document);
		final ResourceSet results = existEmbeddedServer.executeQuery(query.toString(), externalVariables);
		if (results != null && results.getSize() > 0) {
			return ((XMLResource)results.getIterator().nextResource()).getContentAsDOM();
		}
		return null;
	}
}
