package samples;

import java.io.File;
import java.io.StringReader;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.logging.XMLFormatter;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.XPath;
import org.dom4j.io.SAXReader;



public class mergeXML {
	
	private static String DIFF_ROOT_NAME = "diff";
	
	private static String printNode(Element e, String xp, String xmlStr) {
		if (e == null) {
			System.out.println("XPath not valid");
			return "";
		}
		
		String myXMLString;
		
		for (Object obj: e.elements()) {
			Element elem = (Element)obj;
			System.out.println("Nodes  : " + elem.nodeCount());
			System.out.println("Text?  : " + elem.isTextOnly());
			System.out.println("Content: '" + elem.getText().trim() + "'");
			System.out.println("Element: " + elem.getName());
			System.out.println("XPath  : " + xp);
			xmlStr = addElementToXMLString(elem, xmlStr);
			System.out.println("XMLStr : " + xmlStr);
			System.out.println("-----------------------------------------");
			
			if (elem != null) {
        		String path = (xp == null) ? elem.getName() : xp + "/" + elem.getName();
        		xmlStr = printNode(elem, path, xmlStr);
			}
		}
		
		System.out.println("Fertig mit Node <" + e.getName() + ">");
		
		xmlStr = addClosingElementTagToXMLString(e, xmlStr);
		//System.out.println("XML-String nach Parent-Node-Ende: " + xmlStr);
		
		return xmlStr;
	}

	public static routines.system.Document xpathGetAsDoc(Object aXML, String xp) throws DocumentException
    {
    	Document 	doc = null;
    	Node		node;
    	routines.system.Document result = null;
    	
    	doc = getDocument(aXML);
    	
    	if (doc == null || xp == null) return result;
    	
    	node = doc.selectSingleNode( xp );

    	if (node != null) {
    		result = new routines.system.Document();
    		doc = getDocument(node);
    		result.setDocument(doc);
    		
    		System.out.println("=== " + xp + " =========================================================");
    		System.out.println(doc.asXML());
    	}
    	
    	return result;
    }
	
	private static Document getDocument(Object aXML) {
		Document doc = null;
		
		if (aXML instanceof routines.system.Document) {
    		//doc = ((routines.system.Document)aXML).getDocument();
			routines.system.Document myDoc = (routines.system.Document)aXML;
			doc = myDoc.getDocument();
    	} else if (aXML instanceof Document) {
    		doc = (Document)aXML;
    	} else if (aXML instanceof Element) {
    		Element elem = (Element)aXML;
    		Element newElem = (Element)elem.clone();
    		
    		doc = DocumentHelper.createDocument(newElem);
    	} else if (aXML instanceof String) {
    		String xml = (String)aXML;
    		
    		// Check if the argument is a valid path to a file:
			try {
				Path path = Paths.get(xml);
    		
				if (Files.isReadable(path)) {
					// Yes, it is a file, now assume its a XML file and read the contents:
    				xml = new String(Files.readAllBytes(path));
    			} 		
			} catch (Exception e) {
			}

    		SAXReader reader = new SAXReader();
    		StringReader stringReader = new StringReader(xml);
    		
    		//log.debug("getDocument: XML to parse: " + xml);
    		try {
				doc = reader.read(stringReader);
			} catch (DocumentException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
    	}
		return doc;
	}
	
	private static void addItem(Element targetElem, String path, String newVal, String oldVal, boolean ignoreEmptyOrNull) {
    	List elems = targetElem.elements();
    	Node insertBeforeElem = targetElem.selectSingleNode("OperationType");
    	int insertBeforePos = elems.indexOf(insertBeforeElem);
    	Element item;

    	System.out.println("<" + path + "> Comparing Old/New: " + oldVal + "/" + newVal);
    	
    	if (ignoreEmptyOrNull && "".equals(newVal) && !("".equals(oldVal))) {
    		//System.out.println("Ignoring change on " + path + ": " + oldVal + "<=>" + newVal);
    		return;
    	}
    	
    	if ("".equals(newVal) && "".equals(oldVal)) {
    		return;
    	}
    	
    	System.out.println("<" + path + "> AddItem-Values Old/New: " + oldVal + "/" + newVal);
    	
    	if (insertBeforePos < 0) {
    		// Simply add a new item to target.
    		item = targetElem.addElement("Item");
    	} else {
    		// Insert int MDM update structure directly before the OperationType tag:
    		item = DocumentHelper.createElement("Item");
    		elems.add(insertBeforePos, item);
    	}
    			
		item.addElement("path").setText(path);
		if (newVal != null)
			item.addElement("newValue").setText(newVal);
		if (oldVal != null)
			item.addElement("oldValue").setText(oldVal);
    }
	
	
	public static routines.system.Document mergeEmptyXMLNodes(Element targetElement, Element existingXmlNode, Element inputXmlNode, String excludeCSV, String xp) {
    	routines.system.Document result = null;
    	
    	if (existingXmlNode == null) {
    		// shall never happen
    		throw new IllegalArgumentException("existingXmlNode not set");
    	} else if (inputXmlNode == null) {
    		// shall never happen    		
    		throw new IllegalArgumentException("inputXmlNode not set");
    	} else if (xp == null && ! targetElement.elements("Item").isEmpty()){
    		// Target already contains Items (e.g. it may come from the MDM Web UI.) => Just return the target as document
    		result = new routines.system.Document();
    		result.setDocument(targetElement.getDocument());
    	} else {
    		String existingTagName  = existingXmlNode.getName();
    		String inputTagName = inputXmlNode.getName();
    		
    		// check if the current element shall be excluded from comparison:
    		if (xp != null && excludeCSV != null) {
    			List<String> tagNames = Arrays.asList(excludeCSV.split(";"));
    			if (tagNames.contains(xp)) {
    				System.out.println("Omitting " + xp);
    				return result;
    			}
    		}

    		System.out.println("--- " + existingTagName + "/" + inputTagName + " ---");
    		    		
    		if (!existingTagName.equals(inputTagName)) {
    			String p1 = existingXmlNode.getPath();
    			String p2 = inputXmlNode.getPath();
    			throw new IllegalArgumentException(String.format("Comparing incompatible XML tags (%s <=> %s)", p1, p2));
    		}
    		
    		String existingTagValue = existingXmlNode.getText().trim();
    		String inputTagValue = inputXmlNode.getText().trim();
    		
    		// Compare the current element values:
    		if (! Objects.equals(existingTagValue,  inputTagValue)) {
    			Document doc;
	    		result = new routines.system.Document();
	    		
	    		if (targetElement == null) {
    	    		// caller wants to have new document to show the differences => create the document
    	    		doc = DocumentHelper.createDocument();
    	    		
    	    		targetElement = doc.addElement(DIFF_ROOT_NAME);
    	    	} else {
    	    		// set result to the document passed by caller (where the differences shall be put into):
    	    		doc = targetElement.getDocument();
    	    	}
    	    	
	    		result.setDocument(doc);

	    		// Append the difference found to the target element: 
    			addItem(targetElement, xp, inputTagValue, existingTagValue, true);
    		}
    		
    		for (Object obj: inputXmlNode.elements()) {
        		Element e1 = (Element)obj;
        		
        		inputTagName = e1.getName();
        		System.out.println("<" + inputTagName + "> Examining Tag");
        		
        		Element e2 = existingXmlNode.element(inputTagName);
        		
        		routines.system.Document subResult;
        		String path;
        		
        		// Node exists in original and insert
        		if (e2 != null) {
            		path = (xp == null) ? inputTagName : xp + "/" + inputTagName;

        			// recursively walk through entire subtree:
            		subResult = mergeEmptyXMLNodes(targetElement, e2, e1, excludeCSV, path);
        		} else { 
        			// Node has to be created in original from insert with empty sub nodes
        			// at the same time xpath and values are added to the target
        			path = (xp == null) ? inputTagName : xp + "/" + inputTagName;
        			e2 = (Element)e1.clone();
        			for (Object o: e2.elements()) {
        				((Element)o).setText("");
        			}
        			
        			System.out.println("Tag " + inputTagName + " does not exist in mdmItem, creating...");
        			addItem(targetElement, path, inputTagValue, existingTagValue, true);
        			
        			// recursively walk through entire subtree:
            		//subResult = mergeEmptyXMLNodes(targetElement, e1, e2, excludeCSV, path);
            		subResult = mergeEmptyXMLNodes(targetElement, e2, e1, excludeCSV, path);
        		}
        		
        		// prevent return null although a first difference was found earlier:
        		if (result == null)
        			result = subResult;

        	}
    	}

    	return result;
	}
	
	
	
	public static routines.system.Document mergeEmptyXMLNodes(Object targetDoc, String resultXp, Element existingXmlNode, Element newXmlNode, String excludeCSV) throws DocumentException {
		routines.system.Document result = null;
    	
    	if (targetDoc != null) {
    		Document doc = getDocument(targetDoc);
    		
    		if (resultXp == null) 
    			throw new IllegalArgumentException("resultXp xpath string may not be null when targetDoc is not null");
    		
    		org.dom4j.XPath insertXPath = doc.createXPath(resultXp);
    				
    		Element insertIntoNode = (Element)insertXPath.selectSingleNode(doc);
        	result = mergeEmptyXMLNodes(insertIntoNode, existingXmlNode, newXmlNode, excludeCSV, null);
    	} else {
    		result = mergeEmptyXMLNodes(null, existingXmlNode, newXmlNode, excludeCSV, null);
    	}

    	return result;
    }
	
	public static routines.system.Document mergeEmptyXMLNodes(Object existingXML, Object inputXML, String excludeCSV, Object resultXML, String resultXp) throws DocumentException 
    {
    	routines.system.Document  result = null;
    	Document existingDoc = getDocument(existingXML);
    	Document inputDoc = getDocument(inputXML);
  
    	Object resultDoc = (resultXML instanceof routines.system.Document) ? resultXML : getDocument(resultXML);
    	
    	result = mergeEmptyXMLNodes(resultDoc, resultXp, existingDoc.getRootElement(), inputDoc.getRootElement(), excludeCSV);
    	
    	return result;
    }
	
	
	private static String addClosingElementTagToXMLString(Element elem, String xmlStr) {
    	xmlStr = xmlStr.replace("{X}", "</" + elem.getName() + ">{X}");
    	
		return xmlStr;
	}
    
    private static String addElementToXMLString(Element elem, String xmlStr) {
    	if ("".equals(xmlStr)) {
			xmlStr = "<" + elem.getName() + ">" + elem.getText().trim() + "{X}";
		} else {
			xmlStr = xmlStr.replace("{X}", "<" + elem.getName() + ">" + elem.getText().trim() + "{X}");
		}
		
		return xmlStr;
	}

	public static void main(String [ ] args)
    {
    	/*SAXReader reader = new SAXReader();
    	//File inputFile = new File("D:/desktop/projekte/LHCargo/xml/ICargo_Currency_Single.xml");
    	File inputFile = new File("D:/desktop/projekte/LHCargo/xml/TDS_Station.xml");
    	String xp = "/row/entity/exchange/item";
    	
    	Document document;
		try {
			document = reader.read(inputFile);
						
			XPath insertXPath = document.createXPath(xp);
			
			Element element = (Element)insertXPath.selectSingleNode(document);

			String xmlStr = "";
			String output = printNode(element, xp, xmlStr);
			output = output.replace("{X}", "");
			
			System.out.println(output);
			
		} catch (DocumentException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}*/
		
		SAXReader reader = new SAXReader();
		File inputFile = new File("Y:/resources/TDS_Station.xml");
		
		Document document;
		
		try {
			document = reader.read(inputFile);
			XPath rootXPath = document.createXPath("/row/entity");
			
			Element element = (Element)rootXPath.selectSingleNode(document);
			//System.out.println(element.asXML());
			
			routines.system.Document inputItem = xpathGetAsDoc(element, "entity/exchange/item/Station");
			routines.system.Document mdmItem = xpathGetAsDoc(element, "entity/exchange/mdmItem/Station");
			routines.system.Document resultItem = xpathGetAsDoc(element, "entity/exchange");

			String excludeCSV = "Validity/LastUpdateTimestamp;Validity/SourceSystem;Validity/Info;Id";
			String insertResultSection = "exchange/report/Update";
			
			routines.system.Document result = mergeEmptyXMLNodes(mdmItem, inputItem, excludeCSV, resultItem, insertResultSection);
			if (result != null) {
				System.out.println("=== Result =========================================================");
				System.out.println(result.toString());
			}
			
		} catch (DocumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}


