package routines;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

import javax.xml.parsers.DocumentBuilderFactory;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.w3c.dom.Node;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;

import testutils.LcagJunitUtils;

class LcagXmlTools35 extends LcagXmlTools4 {
	/**
	 * createDocumentFromTemplateFile: Creates a new Talend Document from a XML
	 * Template file. The fucntion replaces variables in the file donated by
	 * ${<varname>[#<format>]} by lookup of the <varname> in the map argument. For
	 * Date variables you may add a <format>. The format has the same syntax as
	 * other Java Date formats. If #milliseconds is given the Date is transformed to
	 * UNIX milliseconds format. Note: Replaces only variables listen in the map
	 * argument. Others stay as text and are not replaced by empty string (to apply
	 * the function on multiple maps using subsequent function calls.
	 * 
	 * {talendTypes} Document
	 * 
	 * {Category} LCAG-XML Tools
	 * 
	 * {param} object(fileName) fileName : Name and path of file to read {param}
	 * Map(globalMap) map : Maps keys to value objects.
	 */
	public static Document createDocumentFromTemplateFile(String fileName, Map<String, Object> map)
			throws DocumentException, IOException {
		String xml = LcagFileTools.readTextFile(fileName);

		xml = LcagStringTools.resolveVariables(xml, map);

		Document result = LcagXmlTools.getDocument(xml);

		return result;
	}

	/**
	 * createDocumentFromTemplateFile: Creates a new Talend Document from a XML
	 * Template file. The fucntion replaces variables in the file donated by
	 * ${<varname>[#<format>]} by lookup of the <varname> in the map argument. For
	 * Date variables you may add a <format>. The format has the same syntax as
	 * other Java Date formats. If #milliseconds is given the Date is transformed to
	 * UNIX milliseconds format. Note: Replaces only variables listen in the map
	 * argument. Others stay as text and are not replaced by empty string (to apply
	 * the function on multiple maps using subsequent function calls.
	 * 
	 * {talendTypes} Document
	 * 
	 * {Category} LCAG-XML Tools
	 * 
	 * {param} object(fileName) fileName : Name and path of file to read {param}
	 * Map(globalMap) map : Maps keys to value objects.
	 */
	public static Document createTalendUpdateReportForEntityFromTemplate(String resourceDir,
			Map<String, Object> globalMap, Map<String, Object> contextMap) throws DocumentException, IOException {
		if (globalMap == null)
			throw new IllegalArgumentException("globalMap argument must not be null");

		String fileName = resourceDir + "/Empty_MDM_TalendUpdateReportUI.xml";
		String entityType = (String) globalMap.get("EntityType");

		if (entityType == null && contextMap != null) {
			entityType = (String) contextMap.get("EntityType");
		}

		if (entityType == null)
			throw new IllegalArgumentException("globalMap or contextMap argument must contain EntityType key");

		String xml = LcagFileTools.readTextFile(fileName);
		xml = LcagStringTools.resolveVariables(xml, globalMap);
		xml = LcagStringTools.resolveVariables(xml, contextMap);

		Document result = LcagXmlTools.getDocument(xml);

		fileName = String.format("%s/Empty_MDM_%s.xml", resourceDir, entityType);
		xml = LcagFileTools.readTextFile(fileName);
		xml = LcagStringTools.resolveVariables(xml, globalMap);
		xml = LcagStringTools.resolveVariables(xml, contextMap);

		org.dom4j.Document entity = LcagXmlTools.getDocument(xml);
		result = LcagXmlTools.importXMLSubtree(result, "//item", entity, "/");

		return result;
	}
}

class LcagXmlToolsTest {

	static String InputXML = "  <exchange>\r\n" + "    <report>\r\n" + "      <Update>\r\n" + "        <UserName/>\r\n"
			+ "        <Source>ICargo</Source>\r\n" + "        <TimeInMillis>1539333442030</TimeInMillis>\r\n"
			+ "        <RevisionID/>\r\n" + "        <DataCluster/>\r\n" + "        <DataModel/>\r\n"
			+ "        <Concept>Currency</Concept>\r\n" + "        <Key/>\r\n" + "        <Item>"
			+ "			<path>Validity/ValidFrom</path>\r\n" + "			<oldValue>1999-09-19</oldValue>\r\n"
			+ "			<newValue>2008-12-31</newValue>\r\n" + "        </Item>\r\n" + "        <Item>\r\n"
			+ "			<path>Name</path>\r\n" + "			<oldValue>TEST</oldValue>\r\n"
			+ "			<newValue>AFGHANISTAN AFGHANI</newValue>\r\n" + "        </Item>\r\n"
			+ "        <OperationType>UPDATE</OperationType>\r\n" + "      </Update>\r\n" + "    </report>\r\n"
			+ "    <item>\r\n" + "      <Currency>\r\n" + "        <Code>AFA</Code>\r\n"
			+ "        <Name>AFGHANISTAN AFGHANI</Name>\r\n" + "        <Validity>\r\n"
			+ "          <ValidFrom>2008-12-31</ValidFrom>\r\n" + "          <ValidTo>2016-06-30</ValidTo>\r\n"
			+ "          <LastUpdateTimestamp>2016-06-28T14:20:23</LastUpdateTimestamp>\r\n"
			+ "          <SourceSystem>ICargo</SourceSystem>\r\n" + "          <Info>ICargo-Load</Info>\r\n"
			+ "        </Validity>\r\n" + "        <Id/>\r\n" + "      </Currency>\r\n" + "    </item>\r\n"
			+ "  </exchange>\r\n";

	class GeoLoc {
		public String Longitude;
		public String Latitude;
		public Double X;
		public Double Y;

		public GeoLoc(String lon, String lat, double x, double y) {
			this.Longitude = lon;
			this.Latitude = lat;
			this.X = x;
			this.Y = y;
		}
	}

	private String prettyPrint(Object aXML, Boolean includeXmlDeclaration) {
		String xml = aXML.toString();
		// @TODO: Functions used are not very compatible to dom4j. Causes problems when
		// moving this
		// implementation to Talend
		try {
			final InputSource src = new InputSource(new StringReader(xml));
			final Node document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(src)
					.getDocumentElement();

			final DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
			final DOMImplementationLS impl = (DOMImplementationLS) registry.getDOMImplementation("LS");
			final LSSerializer writer = impl.createLSSerializer();

			writer.getDomConfig().setParameter("format-pretty-print", Boolean.TRUE); // Set this to true if the output
																						// needs to be beautified.
			writer.getDomConfig().setParameter("xml-declaration", includeXmlDeclaration); // Set this to true if the
																							// declaration is needed to
																							// be outputted.

			return writer.writeToString(document);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	public String prettyPrintWithXmlHeader(Object aXML) {
		return prettyPrint(aXML, true);
	}

	public String prettyPrintWithOutXmlHeader(Object aXML) {
		return prettyPrint(aXML, false);
	}

	/**
	 * Find leaf elements that have no text and return their xPath location
	 * 
	 * @param aXml
	 * @return
	 * @throws DocumentException
	 */
	public List<String> getEmptyLeafElementPathes(Object aXml) throws DocumentException {
		List<String> result = new ArrayList<String>();
		Element elem = LcagXmlTools.getElement(aXml);

		String text = elem.getTextTrim();
		List<Element> elements = elem.elements();

		// Add path if
		if (elements.isEmpty() && "".equals(text)) {
			result.add(elem.getPath());
		}

		// Recurse through all sub elements:
		for (Element e : elements) {
			List<String> childResults = getEmptyLeafElementPathes(e);
			result.addAll(childResults);
		}
		return result;
	}

	/**
	 * Delete all leaf elements that have no text and return the result document
	 * 
	 * @param aXml
	 * @return
	 * @throws DocumentException
	 */
	public Document deleteEmptyLeafElement(Object aXml) throws DocumentException {
		org.dom4j.Document doc = LcagXmlTools.getDocument(aXml);
		List<String> tagList = getEmptyLeafElementPathes(doc);

		Document result = LcagXmlTools.xpathDeleteTags(doc, tagList);

		return result;
	}

	@Test
	void testAsXmlTag() {
		assertEquals("<myTag>Hello World!</myTag>", LcagXmlTools.asXmlTag("myTag", "Hello World!").trim());
		assertEquals("<myTag>28</myTag>", LcagXmlTools.asXmlTag("myTag", 28).trim());
	}

	@Test
	void testGetEmptyLeafElementPathes() throws DocumentException {
		List<String> tagList = getEmptyLeafElementPathes(InputXML);
		String[] expected = { "/exchange/report/Update/UserName", "/exchange/report/Update/RevisionID",
				"/exchange/report/Update/DataCluster", "/exchange/report/Update/DataModel",
				"/exchange/report/Update/Key", "/exchange/item/Currency/Id" };
		assertArrayEquals(expected, tagList.toArray());
	}

	@Test
	void testDeleteEmptyLeafElement() throws DocumentException {
		Document result = deleteEmptyLeafElement(InputXML);

		assertNotNull(result);

		org.dom4j.Document resultDoc = result.getDocument();
		assertNotNull(resultDoc);

		org.dom4j.Element resultElem = resultDoc.getRootElement();
		assertNotNull(resultElem);

		boolean isPresent = LcagXmlTools.xpathHasElement(result, "//UserName");
		assertFalse(isPresent);
	}

	@Test
	void testPrettyPrintWithOutXmlHeader() {
		String result;

		result = prettyPrintWithOutXmlHeader(InputXML);
		assertFalse(result.startsWith("<?xml"));
	}

	@Test
	void testPrettyPrintWithXmlHeader() {
		String result;

		result = prettyPrintWithXmlHeader(InputXML);
		assertTrue(result.startsWith("<?xml"));
	}

	@BeforeAll
	static void setUpBeforeClass() throws Exception {
	}

	@AfterAll
	static void tearDownAfterClass() throws Exception {
	}

	@BeforeEach
	void setUp() throws Exception {
	}

	@AfterEach
	void tearDown() throws Exception {
	}

	@Test
	void testGetDocument() throws DocumentException {
		org.dom4j.Document doc = LcagXmlTools.getDocument(InputXML);
		assertNotNull(doc);

		assertTrue(LcagXmlTools.xpathHasElement(doc, "/exchange"));
		// ensure we get the same document, not just a copy
		org.dom4j.Document doc2 = LcagXmlTools.getDocument(doc);
		assert doc == doc2;
	}

	@Test
	void testgetDocument() throws DocumentException {
		Document doc;

		doc = LcagXmlTools.getDocument(InputXML);
		assert doc != null;

		// ensure we get the same document, not just a copy
		Document doc2 = LcagXmlTools.getDocument(doc);
		assert doc == doc2;
	}

	private void assertCorrectDocument(String expectedTag, Document result) {
		assertNotNull(result);

		org.dom4j.Document resultDoc = result.getDocument();
		assertNotNull(resultDoc);

		org.dom4j.Element resultElem = resultDoc.getRootElement();
		assertNotNull(resultElem);

		String name = resultElem.getName();
		assertEquals(expectedTag, name);
	}

	@Test
	void testCreateDocumentFromTemplateFile() throws DocumentException, IOException {
		Map<String, Object> globalMap = new HashMap<String, Object>();
		String value;
		Date aDate = new Date();

		globalMap.put("MDM_Cargo_MDM_DataModel", "LCAG_MDM_4_TST1_REG");
		globalMap.put("MDM_Cargo_MDM_DataContainer", "LCAG_MDM_4_TST1_REG");
		globalMap.put("env_jobTimestamp", aDate);
		globalMap.put("EntityType", "Station");
		globalMap.put("Code", "PAD");

		Document result = LcagXmlTools4.createDocumentFromTemplateFile(
				LcagFileTools.getResourceFilePath("Empty_MDM_Station_TalendUpdateReportUI.xml"), globalMap);

		assertFalse(result.toString().contains("${"), "Not all variables are resolved");

		value = LcagXmlTools.xpathGetValue(result, "//Concept");
		assertEquals("Station", value);

		value = LcagXmlTools.xpathGetValue(result, "//TimeInMillis");
		Long ms = Long.valueOf(aDate.getTime());
		assertEquals(ms.toString(), value);
	}

	@Test
	void testCreateEntityFromTemplate() throws DocumentException, IOException {
		Map<String, Object> globalMap = new HashMap<String, Object>();

		Document result = LcagXmlTools4.createEntityFromTemplate("src/test/resources", "Station", globalMap, null);

		assertFalse(result.toString().contains("${"), "Not all variables are resolved");

		assertTrue(LcagXmlTools.xpathHasElement(result, "//Longitude"));
	}

	@Test
	void testCreateTalendUpdateReportForEntityFromTemplate() throws DocumentException, IOException {
		Map<String, Object> globalMap = new HashMap<String, Object>();
		String value;
		Date aDate = new Date();

		globalMap.put("MDM_Cargo_MDM_DataModel", "LCAG_MDM_4_TST1_REG");
		globalMap.put("MDM_Cargo_MDM_DataContainer", "LCAG_MDM_4_TST1_REG");
		globalMap.put("env_jobTimestamp", aDate);
		globalMap.put("EntityType", "Station");
		globalMap.put("Code", "PAD");

		Document result = LcagXmlTools4.createTalendUpdateReportForEntityFromTemplate("src/test/resources", globalMap,
				null);

		assertFalse(result.toString().contains("${"), "Not all variables are resolved");

		value = LcagXmlTools.xpathGetValue(result, "//Concept");
		assertEquals("Station", value);

		value = LcagXmlTools.xpathGetValue(result, "//TimeInMillis");
		Long ms = Long.valueOf(aDate.getTime());
		assertEquals(ms.toString(), value);
	}

	@Test
	void testXpathTalendDocument() throws DocumentException {
		Document result = LcagXmlTools4.xpathCreateDocument("Update");

		assertCorrectDocument("Update", result);

		result = LcagXmlTools4.xpathCreateDocument("/message/exchange/path");
		assertCorrectDocument("message", result);

		assertTrue(LcagXmlTools.xpathHasElement(result, "/message/exchange/path"));
	}

	@Test
	void testWriteXMLToFile() throws DocumentException, IOException {
		LcagXmlTools.writeXMLToFile(InputXML, LcagFileTools.getResourceFilePath("expected/tc-writeXMLToFile.xml"));
	}

	@Test
	void testGetDocument_FromFile() throws DocumentException, IOException {
		org.dom4j.Document result = LcagXmlTools.getDocument(LcagFileTools.readResourceFile("TestFile.xml"));

		assertEquals("AFA", LcagXmlTools.xpathGetValue(result, "//Currency/Code"));
	}

	@Test
	void testXpathHasElement() throws DocumentException {
		boolean s;

		s = LcagXmlTools.xpathHasElement(InputXML, "/exchange/report/Update/Source");
		assertTrue(s);

		s = LcagXmlTools.xpathHasElement(InputXML, "/exchange/report/Update/XXXXX");
		assertFalse(s);

		// Test null pointer
		s = LcagXmlTools.xpathHasElement(null, "/exchange/report/Update/Source");
		assertFalse(s);
	}

	// Not yet used in EBX
	@Test
	void testXpathGetTagName() throws DocumentException, IOException {
		String s;

		s = LcagXmlTools.xpathGetTagName("<Station/>", ".");
		assert Objects.equals(s, "Station");

		s = LcagXmlTools.xpathGetTagName(InputXML, "/exchange/report/Update/Source");
		assert Objects.equals(s, "Source");

		s = LcagXmlTools.xpathGetTagName(InputXML, "/exchange/report/Update/XXXXX");
		assertNull(s);

		s = LcagXmlTools.xpathGetTagName(LcagFileTools.readResourceFile("MDS_City.xml"), "/*[1]/*[1]");
		assertTrue(Objects.equals(s, "city"),
				"Must work, used in V3.5+ to detect entity type in source system messages");

		s = LcagXmlTools.xpathGetTagName(InputXML, "/*[1]/*[1]");
		assertTrue(Objects.equals(s, "report"),
				"Must work, used in V3.5+ to detect entity type in source system messages");

		String xml = "<A><B></B><B></B></A>";
		s = LcagXmlTools.xpathGetTagName(xml, "/*[1]/*[1]");
		assertTrue(Objects.equals(s, "B"), "Must work, used in V3.5+ to detect entity type in source system messages");

		s = LcagXmlTools.xpathGetTagName(xml, "/*/*");
		assertNull(s, "Should also find B although looking for a single Node in implementation");

		// Test null pointer
		s = LcagXmlTools.xpathGetTagName(null, "/exchange/report/Update/Source");
		assertNull(s);

	}

	@Test
	void testXpathGetValue() throws DocumentException {
		String s;

		s = LcagXmlTools.xpathGetValue(InputXML, "/exchange/report/Update/Source");
		assert Objects.equals(s, "ICargo");

		s = LcagXmlTools.xpathGetValue(InputXML, "/exchange/report/Update/XXXXX");
		assertNull(s);

		// Test retrieving value of empty tag
		s = LcagXmlTools.xpathGetValue(InputXML, "//UserName");
		assert "".equals(s);

	}

	@Test
	void testXpathGetListOfValues() throws DocumentException, IOException {
		String InputXML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
				+ "<Attribute><Name>Name</Name><SourcesPriority>ICargo</SourcesPriority><SourcesPriority>MAD</SourcesPriority><SourcesPriority>MDS</SourcesPriority><ComparePolicy><CompareFunction>ignore-case</CompareFunction><EmptyIsOk>true</EmptyIsOk></ComparePolicy><AutoChangeRule>prefer case-sensitive</AutoChangeRule><Restricted>false</Restricted><SyncChecked>true</SyncChecked><Quality>M</Quality></Attribute>";
		Document doc = LcagXmlTools.getDocument(LcagFileTools.readResourceFile("MDM_UI_DST_Update.xml"));

		List<String> result;

		result = LcagXmlTools4.xpathGetListOfValues(doc, "//Update/Item/path");
		assertArrayEquals(new String[] { "DST_StartLocal", "DST_EndLocal" }, result.toArray(),
				"Should return all values in same sequence as in XML");

		result = LcagXmlTools4.xpathGetListOfValues(InputXML, "Attribute/SourcesPriority");
		assertArrayEquals(new String[] { "ICargo", "MAD", "MDS" }, result.toArray(),
				"Should return all values in same sequence as in XML");

		result = LcagXmlTools4.xpathGetListOfValues(InputXML, "//ComparePolicy/EmptyIsOk");
		assertArrayEquals(new String[] { "true" }, result.toArray(),
				"Should return one value for simple (non-list elments) too");

		result = LcagXmlTools4.xpathGetListOfValues(InputXML, "Attribute/EmptyIsOk");
		assertTrue(result.isEmpty(), "Should not find the value if the Xpath is wrong");

		result = LcagXmlTools4.xpathGetListOfValues(InputXML, "Attribute/NotThere");
		assertTrue(result.isEmpty(), "Should return empty List if not avaialble in XML");

		result = LcagXmlTools4.xpathGetListOfValues(null, "Attribute/NotThere");
		assertTrue(result.isEmpty(), "Should return empty List if not avaialble in XML");
	}

	@Test
	void testXpathSetValue() throws DocumentException {
		Document result = LcagXmlTools.xpathSetValue(InputXML, "/exchange/report/Update/Source", "123");

		String val = LcagXmlTools.xpathGetValue(result, "/exchange/report/Update/Source");

		assert "123".equals(val);

		result = LcagXmlTools.xpathSetValue(InputXML, "/exchange/report/Update/Source", "");
		val = LcagXmlTools.xpathGetValue(result, "/exchange/report/Update/Source");
		assert "".equals(val);

		// FIXME: Should throw an exception
		result = LcagXmlTools.xpathSetValue(InputXML, "/exchange/report/Update/XXXX", "123");
		assertNotNull(val, "Setting the value of a tag that does not exist returns the document");

		val = LcagXmlTools.xpathGetValue(result, "/exchange/report/Update/XXXX");
		assertNull(val, "Setting the value of a tag that does not exist returns null value");
	}

	/**
	 * xpathSetValueOrAddTag: Sets the value of the first element located at a xpath
	 * expression xp to a new value. Creates the Tag if it does not yet exist at the
	 * same position as within the reference XML. Returns the changed document if
	 * successful, else throws DocumentException Version: since 3.4
	 * 
	 * @param aXML         output: Talend or Dom4J or String xml document to be
	 *                     changed
	 * @param xp           input: xpath lookup string
	 * @param value        input value to set
	 * @param referenceXML input: to create the tag if missing
	 * @return the changed Talend Document
	 * @throws DocumentException
	 *
	 *                           {talendTypes} Document {Category} LCAG-XML Tools
	 * 
	 *                           {param} Object(xmlObject) aXML: output: Talend or
	 *                           Dom4J xml document to be changed {param}
	 *                           Object(xpath) xp: input: xpath lookup string
	 *                           {param} Object(value) value: input: value to set
	 *                           (uses toString) {param} Object(referenceXML) ref:
	 *                           input: to create the tag if missing
	 * 
	 */
	public static Document xpathSetValueOrAddTag(Object aXML, String xp, Object value, final Object referenceXML)
			throws DocumentException {
		Document result = LcagXmlTools.getDocument(aXML);

		if (result == null)
			throw new IllegalArgumentException(String.format("Input document is null, cannot set %s to %s", xp, value));

		if (value == null)
			throw new IllegalArgumentException(String.format("Seting %s to null value is not supported", xp, value));

		String text = value.toString();

		org.dom4j.Document doc = result.getDocument();

		if (doc == null || xp == null)
			throw new IllegalArgumentException(
					String.format("Input Talend document content is empty, cannot set %s to %s", xp, value));

		String pathList[] = xp.split("/");

		if (pathList.length < 2)
			throw new IllegalArgumentException(
					String.format("Xpath must indicate parent, cannot set %s to %s", xp, value));

		org.dom4j.Element inputElem = (org.dom4j.Element) doc.selectSingleNode(xp);

		if (inputElem == null) {
			org.dom4j.Document refDoc = LcagXmlTools.getDocument(referenceXML);

			if (refDoc == null)
				throw new IllegalArgumentException(
						String.format("Reference document is null, cannot set %s to %s", xp, value));

			org.dom4j.Element refElem = (org.dom4j.Element) refDoc.selectSingleNode(xp);

			if (refElem == null)
				throw new IllegalArgumentException(String.format("Reference document does not contain tag %s", xp));

			String lookupParentTag = pathList[pathList.length - 2];
			org.dom4j.Element refParentElem = refElem.getParent();
			String refPath = "/" + refParentElem.getPath();

			// Get Parent node in input document:
			inputElem = (org.dom4j.Element) doc.selectSingleNode(refPath);

			if (inputElem == null) {
				throw new IllegalArgumentException(
						String.format("Input document parent tag %s does does not exist at xpath %s",
								refParentElem.getName(), lookupParentTag));
			} else if (!java.util.Objects.equals(lookupParentTag, refParentElem.getName())) {
				throw new IllegalArgumentException(
						String.format("Reference document parent tag %s does not match parent tag in xpath %s",
								refParentElem.getName(), lookupParentTag));
			} else if (!java.util.Objects.equals(lookupParentTag, inputElem.getName())) {
				throw new IllegalArgumentException(
						String.format("Input document parent tag %s does not match parent tag in xpath %s",
								inputElem.getName(), lookupParentTag));
			} else {
				// Find position to add in input document:
				List<org.dom4j.Element> elems = refParentElem.elements();

				int position = elems.indexOf(refElem);

				elems = inputElem.elements();
				org.dom4j.Element newElem = org.dom4j.DocumentHelper.createElement(refElem.getName());
				newElem.setText(text);

				elems.add(position, newElem); // adds after position
			}

		} else {
			inputElem.setText(text);
		}

		return result;
	}

	@Test
	void testXpathSetValueOrAddTag() throws DocumentException {
		Document refDoc = LcagXmlTools.getDocument(InputXML);
		Document doc = LcagXmlTools.getDocument(InputXML);
		String tagName = "//Update/Key";
		String val = null;

		// TC1 create in middle of structure
		Document result = LcagXmlTools.xpathDeleteTags(doc, tagName);
		assertFalse(LcagXmlTools.xpathHasElement(result, tagName));

		doc = result;

		result = xpathSetValueOrAddTag(doc, tagName, "123", refDoc);
		val = LcagXmlTools.xpathGetValue(result, tagName);
		assertEquals("123", val, "Value should be set correctly if the tag was just created.");

		result = xpathSetValueOrAddTag(doc, tagName, "abc", refDoc);
		val = LcagXmlTools.xpathGetValue(result, tagName);
		assertEquals("abc", val, "Value should be set correctly if set again.");

		// TC2 create at beginning of structure
		tagName = "//Currency/Code";
		result = LcagXmlTools.xpathDeleteTags(doc, tagName);
		assertFalse(LcagXmlTools.xpathHasElement(result, tagName));

		result = xpathSetValueOrAddTag(doc, tagName, "ABC", refDoc);
		val = LcagXmlTools.xpathGetValue(result, tagName);
		assertEquals("ABC", val, "Value should be set correctly if the tag was just created.");

		// TC3 Create element where parent does not exist yet:
		// Note: Only possible if element is at first position of missing parent tag =>
		// useless, not implement
		tagName = "//Validity/Info";
		result = LcagXmlTools.xpathDeleteTags(doc, "//Validity");
		assertFalse(LcagXmlTools.xpathHasElement(result, tagName));

		// result = xpathSetValueOrAddTag(doc, tagName, "Recreated", refDoc);
		// val = LcagXmlTools.xpathGetValue(result, tagName);
		// assertEquals("Recreated", val, "Value should be set correctly if the tag was
		// just created.");

	}

	Document xpathSetValueAndUpdateChangeList(Object aXML, String xp, String newValue) throws DocumentException {
		// TODO: To implement for Version 4.0
		Document doc = LcagXmlTools.getDocument(aXML);
		String oldValue = LcagXmlTools.xpathGetValue(doc, xp);

		Document result = LcagXmlTools.xpathSetValue(doc, xp, newValue);

		return doc;
	}

	@Test
	void testXpathSetValueAndUpdateChangeList() throws DocumentException, IOException {
		Document doc = LcagXmlTools.getDocument(LcagFileTools.readResourceFile("MDM_UI_DST_Update.xml"));
		Document result = xpathSetValueAndUpdateChangeList(doc, "//DST/DST_StartUTC", "2021-06-09 14:00");

		String val = LcagXmlTools.xpathGetValue(result, "//DST/DST_StartUTC");
		assert "2021-06-09 14:00".equals(val);

		val = LcagXmlTools.xpathGetValue(result, "//Update/Item[path=\"DST_StartUTC\"]/oldValue");
		assert "".equals(val);
		val = LcagXmlTools.xpathGetValue(result, "//Update/Item[path=\"DST_StartUTC\"]/newValue");
		assert "2021-06-09 14:00".equals(val);
	}

	@Test
	void testXpathAddTag() throws DocumentException {
		org.dom4j.Document doc;
		org.dom4j.Element elem;
		org.dom4j.Document inputDoc = LcagXmlTools.getDocument(InputXML);
		String tag = "//item/Currency/Id2";
		String ebxXml = "<xsd:element xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:osd=\"urn:ebx-schemas:common_1.0\" xmlns:fmt=\"urn:ebx-schemas:format_1.0\" xmlns:ebxbnd=\"urn:ebx-schemas:binding_1.0\" xmlns:ebxs=\"urn:ebx-schemas:session_1.0\" name=\"ProductCode\" type=\"ProductFKeyType\"> \r\n"
				+ "        <xsd:annotation><xsd:documentation xml:lang=\"en-US\"><osd:label>Product Code</osd:label></xsd:documentation></xsd:annotation> \r\n"
				+ "      </xsd:element>";

		String xml, value;
		List<String> valueList;

		tag = "xsd:annotation/xsd:documentation[@xml:lang=\"en-US\"]/osd:label";
		elem = LcagXmlTools.getDocument(ebxXml).getRootElement();
		LcagXmlTools.xpathAddTag(elem, tag, "Product Code");

		xml = elem.asXML();
		valueList = LcagXmlTools4.xpathGetListOfValues(elem, tag);

		assert valueList.size() == 1;
		assert "Product Code".equals(valueList.get(0));

		tag = "//item/Currency/Id2";
		doc = LcagXmlTools.xpathAddTag(InputXML, tag, 2);
		value = LcagXmlTools.xpathGetValue(doc, tag);
		assert "2".equals(value);

		tag = "//report/Update/Item[@newAttr=\"New\"]/test";
		doc = LcagXmlTools.xpathAddTag(inputDoc, tag, "Hello");
		value = LcagXmlTools.xpathGetValue(doc, tag);
		assert "Hello".equals(value);

		tag = "//report/Update/Item[path=\"New\"]/test";
		doc = LcagXmlTools.xpathAddTag(inputDoc, tag, "Hello");
		value = LcagXmlTools.xpathGetValue(doc, tag);
		assert "Hello".equals(value);

		tag = "//report/Update/Item[path=\"Name\"]/test";
		doc = LcagXmlTools.xpathAddTag(inputDoc, tag, "Hello");
		value = LcagXmlTools.xpathGetValue(doc, tag);
		assert "Hello".equals(value);

		doc = null;
		try {
			doc = LcagXmlTools.xpathAddTag(inputDoc, tag, null);
		} catch (IllegalArgumentException e) {
		}
		assertTrue(doc == null);

		// Test correct behavior when reusing input document as result
		tag = "//item/*/Id2";
		inputDoc = LcagXmlTools.xpathAddTag(inputDoc, tag, 2);
		value = LcagXmlTools.xpathGetValue(inputDoc, tag);
		assert "2".equals(value);

		// Test inserting second time (already exists case => set new value)
		inputDoc = LcagXmlTools.xpathAddTag(inputDoc, tag, 3);
		value = LcagXmlTools.xpathGetValue(inputDoc, tag);
		assert "3".equals(value);

	}

	@Test
	void testXpathEnrich() throws DocumentException, IOException {
		Map<String, Object> globalMap = new HashMap<String, Object>();

		Document xml = LcagXmlTools4.createDocumentFromTemplateFile(
				LcagFileTools.getResourceFilePath("Empty_MDM_Station_TalendUpdateReportUI.xml"), globalMap);
		// String stationXml =
		// LcagFileTools.readTextFile(LcagFileTools.readResourceFile("ICargo_Station-PAD-InMDM.xml"));

		// LcagXmlTools.xpathEnrich(xml, stationXml);
		// LcagXmlTools.xpathEnrich(InputXML, "<Validity><UID>ABC</UID></Validity>");
	}

	@Test
	void testUpdateXML() throws DocumentException, IOException {
		Map<String, Object> globalMap = new HashMap<String, Object>();

		Document xml = LcagXmlTools4.createDocumentFromTemplateFile(
				LcagFileTools.getResourceFilePath("Empty_MDM_Station_TalendUpdateReportUI.xml"), globalMap);
		String stationXml = LcagFileTools.readResourceFile("ICargo_Station-PAD-InMDM.xml");

		LcagXmlTools4.updateXML(xml, stationXml);
	}

	@Test
	void testEnrichXMLValues() throws DocumentException, IOException {
		// Used by MDM_check_Validities_gerneric_val
		Document emptyStationUpdateReport = LcagXmlTools
				.getDocument(LcagFileTools.readResourceFile("EmptyUpdateReport4StationInputMsg.xml"));
		Document inputStationMsg = LcagXmlTools
				.getDocument(LcagFileTools.readResourceFile("ICargo_Station-PAD-InputMsg.xml"));

		Document inputStationExchangeMsg = LcagXmlTools.xpathGetAsDoc(inputStationMsg, "//exchange");
		Document result = LcagXmlTools4.enrichXMLValues(emptyStationUpdateReport, inputStationExchangeMsg);

		// Check result returned:

		// Check changes in resultDoc argument:

		boolean hasUID = LcagXmlTools.xpathHasElement(result, "//Validity/UID");
		assertFalse(hasUID, "Fields in inputXML should not be merged into the resultDoc");
	}

	@Test
	void testDiffXMLAndCreateChangeList() throws DocumentException, IOException {
		String value;
		List<String> valueList;

		Document inputDoc = LcagXmlTools.getDocument(LcagFileTools.readResourceFile("BuildListInputMessageEntity.xml"));
		Document mdmDoc = LcagXmlTools.getDocument(LcagFileTools.readResourceFile("BuildListInput_MDM_Entity.xml"));

		Document resultDoc = LcagXmlTools.xpathGetAsDoc(inputDoc, "//Validity/..");
		Document mdmItem = LcagXmlTools.xpathGetAsDoc(mdmDoc, "//Validity/..");

		String excludeFieldsCSV = "Validity/LastUpdateTimestamp";
		String lastUpdate = LcagXmlTools.xpathGetValue(resultDoc, "//" + excludeFieldsCSV);
		String inputName = LcagXmlTools.xpathGetValue(resultDoc, "//Name");

		// TC1: (Used in MDM_build* job) Return a new change list, Do not add it to the
		// rsultDoc:
		String insertResultSection = "Update";
		Document result = LcagXmlTools4.diffXMLAndCreateChangeList(resultDoc, insertResultSection, mdmItem,
				excludeFieldsCSV);

		valueList = LcagXmlTools4.xpathGetListOfValues(resultDoc, "//Item/path");
		assertTrue(valueList.isEmpty(), "Change List should not be added to resultDoc parameter content");

		valueList = LcagXmlTools4.xpathGetListOfValues(result, "//Item/path");
		assertFalse(valueList.contains(excludeFieldsCSV), "Ignored fields change not be listed in change list");
		assertFalse(valueList.contains("LocationType"), "Update operation should not enrich empty text in Change List");

		value = LcagXmlTools.xpathGetValue(resultDoc, "//" + excludeFieldsCSV);
		assertEquals(lastUpdate, value, "Ignored field should not have been changed in resultDoc parameter content");

		value = LcagXmlTools.xpathGetValue(result, insertResultSection + "/Item[path='Name']/newValue");
		assertEquals("DUCKTOWN AIRPORT", value, "Changed new Value should be listed in Change List.");
		value = LcagXmlTools.xpathGetValue(result, insertResultSection + "/Item[path='Name']/oldValue");
		assertEquals("PADDELBORN", value, "Old Value should be listed in Change List.");
		value = LcagXmlTools.xpathGetValue(resultDoc, "//Name");
		assertEquals(inputName, value,
				"Changes should only appear in Change List, but not be applied to resultDoc parameter content");

		// TC3: Like TC2, but switching source and target:
		result = LcagXmlTools4.diffXMLAndCreateChangeList(mdmItem, insertResultSection, resultDoc, excludeFieldsCSV);

		valueList = LcagXmlTools4.xpathGetListOfValues(mdmItem, "//Item/path");
		assertTrue(valueList.isEmpty(), "Change List should not be added to resultDoc parameter content");

		value = LcagXmlTools.xpathGetValue(result, insertResultSection + "/Item[path='Name']/newValue");
		assertEquals("PADDELBORN", value, "Changed new Value should be listed in Change List.");

		// TC3: Add change List to the resultDoc:
		insertResultSection = "//report/Update"; // add differences section into this tag
		resultDoc = inputDoc;

		result = LcagXmlTools4.diffXMLAndCreateChangeList(resultDoc, insertResultSection, mdmItem, excludeFieldsCSV);

		valueList = LcagXmlTools4.xpathGetListOfValues(resultDoc, "//Item/path");
		assertFalse(valueList.isEmpty(), "Change List should be added to resultDoc parameter content");

		valueList = LcagXmlTools4.xpathGetListOfValues(result, "//Item/path");
		assertFalse(valueList.contains(excludeFieldsCSV), "Ignored fields change not be listed in change list");
		value = LcagXmlTools.xpathGetValue(resultDoc, "//" + excludeFieldsCSV);
		assertEquals(lastUpdate, value, "Ignored field should not have been changed in resultDoc parameter content");

		value = LcagXmlTools.xpathGetValue(result, insertResultSection + "/Item[path='Name']/newValue");
		assertEquals("DUCKTOWN AIRPORT", value, "Changed Value should be listed in Change List.");
		value = LcagXmlTools.xpathGetValue(result, insertResultSection + "/Item[path='Name']/oldValue");
		assertEquals("PADDELBORN", value, "Old Value should be listed in Change List.");
		value = LcagXmlTools.xpathGetValue(resultDoc, "//Name");
		assertEquals(inputName, value,
				"Changes should only appear in Change List, but not be applied to resultDoc parameter content");

		boolean hasUID = LcagXmlTools.xpathHasElement(result, "//Validity/UID");
		assertFalse(hasUID, "Fields in inutXML should not be merged into the resultDoc");

		// FIXME: TC4: diff with repeated //Item elements (still fails)
		result = LcagXmlTools.getDocument(LcagFileTools.readResourceFile("TestFile.xml"));
		Document cmpDoc = LcagXmlTools.getDocument(InputXML);

		Document diff = LcagXmlTools4.diffXMLAndCreateChangeList(result, "/ChangeList", cmpDoc, null);
		assertFalse(LcagXmlTools.xpathHasElement(diff, "//Item"),
				"Repeated Elements should be compared corrected and show they are equal");
	}

	@Test
	void testMergeInto() throws DocumentException, IOException {
		GeoLoc input_row = new GeoLoc("008.32.13E", "50.02.31N", 8.537013461376182f, 50.0418942745052f);
		String update = "<MDM_Message><report><Update> <Source>UI</Source> <TimeInMillis/> <OperationType>UPDATE</OperationType> <RevisionID/> <DataModel/> <Concept>%s</Concept> <Key/> </Update></report></MDM_Message>";

		String stationXML = LcagFileTools.readResourceFile("OneUpdateReportForStationWithoutXYGeo.xml");

		String geoLocation = String.format(
				"<GeoLocation><Latitude>%s</Latitude><Longitude>%s</Longitude><X>%f</X><Y>%f</Y></GeoLocation>",
				input_row.Latitude, input_row.Longitude, input_row.X, input_row.Y);

		Document updateReport = LcagXmlTools.mergeInto(stationXML, geoLocation);

		LcagJunitUtils.assertEqualsToFileContent(updateReport, "testMergeInto", "addGeoXY.xml");

	}
}
