import java.io.*;
import java.util.*;

import org.jdom2.*;
import org.jdom2.filter.Filters;
import org.jdom2.input.SAXBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.jdom2.xpath.*;



/**
 * Contains methods based on JDOM and Jaxen library that help processing XML files and other misc ones.
 */
public class XMLHelper {
	
	/**
	 * Reads an .xml file from file path and returns a JDOM document.
	 * 
	 * @param src			The .xml file path
	 * @return JDOM document
	 * @throws JDOMException
	 * @throws IOException
	 */
	public static Document readXml(String src) throws JDOMException, IOException {
		File file = new File(src);
		SAXBuilder saxBuilder = new SAXBuilder();
		Document doc = saxBuilder.build(file);

		return doc;
	}
	
	/**
	 * Saves the given Document as XML to the given path.
	 * 
	 * @param oDoc			The JDOM document
	 * @param sPath			The output path
	 * @throws IOException
	 */
	public static void saveAsXml(Document oDoc, String sPath) throws IOException {
		FileOutputStream dest = new FileOutputStream(sPath);
		XMLOutputter xmlOutput = new XMLOutputter();		
		xmlOutput.setFormat(Format.getPrettyFormat());
		xmlOutput.output(oDoc, dest);
	}
	
	/**
	 * Takes a given directory path and lists all files directly in it.
	 * (Does not visit sub-directories recursively)
	 * 
	 * @param path			Path to the directory
	 * @return ArrayList with all file names
	 */
	public static ArrayList<String> getFileNames(String path) {
		ArrayList<String> fileNames = new ArrayList<String>();
		File directory = new File(path);
		File[] contentList = directory.listFiles();

		for (File file : contentList) {
			if (file.isFile()) fileNames.add(file.getName());
		}
		
		return fileNames;
	}
	
	/**
	 * Executes given XPath expression for elements with JDOM Document as source.
	 * 
	 * @param doc			The JDOM Document
	 * @param xPath			XPath expression
	 * @return List with all elements found or empty list if no match found
	 */
	public static List<Element> xQueryElements(Document doc, String xPath) {
		XPathFactory xFactory = XPathFactory.instance();
		XPathExpression<Element> expr = xFactory.compile(xPath, Filters.element());
		List<Element> result = expr.evaluate(doc);
		
		return result;
	}
	
	/**
	 * Executes given XPath expression for elements with Element as source.
	 * (Expression is executed on the entire Document the Element is attached to!)
	 * 
	 * @param elem			The Element
	 * @param xPath			XPath expression
	 * @return List with all elements found or empty list if no match found
	 */
	public static List<Element> xQueryElements(Element elem, String xPath) {
		XPathFactory xFactory = XPathFactory.instance();
		XPathExpression<Element> expr = xFactory.compile(xPath, Filters.element());
		List<Element> result = expr.evaluate(elem);
		
		return result;
	}
	
	/**
	 * Executes given XPath expression for attributes with JDOM Document as source.
	 * 
	 * @param doc			The JDOM Document
	 * @param xPath			XPath expression
	 * @return List with all attributes found or empty list if no match found
	 */
	public static List<Attribute> xQueryAttributes(Document doc, String xPath) {
		XPathFactory xFactory = XPathFactory.instance();
		XPathExpression<Attribute> expr = xFactory.compile(xPath, Filters.attribute());
		List<Attribute> result = expr.evaluate(doc);
		
		return result;
	}
	
	/**
	 * Executes given XPath expression for attributes with Element as source.
	 * (Expression is executed on the entire Document the Element is attached to!)
	 * 
	 * @param elem			The Element
	 * @param xPath			XPath expression
	 * @return List with all attributes found or empty list if no match found
	 */
	public static List<Attribute> xQueryAttributes(Element elem, String xPath) {
		XPathFactory xFactory = XPathFactory.instance();
		XPathExpression<Attribute> expr = xFactory.compile(xPath, Filters.attribute());
		List<Attribute> result = expr.evaluate(elem);
		
		return result;
	}
	
	/**
	 * Searches a document for the first occurrence of an XML tag with JDOM Document as source.
	 * 
	 * @param doc			The JDOM Document
	 * @param elementName	Name of the XML tag
	 * @return First element found or NULL if no match found
	 */
	public static Element searchFirstElement(Document doc, String elementName) {
		//XPath index starts with 1; [] has a higher precedence than //
		List<Element> query= xQueryElements(doc, "(//"+elementName+")[1]");
		
		if(query.isEmpty()) return null;
		return query.get(0);
	}
	
	/**
	 * Searches a document for the first occurrence of an XML tag with Element as source.
	 * (Expression is executed on the entire Document the Element is attached to!)
	 * 
	 * @param elem			The Element
	 * @param elementName	Name of the XML tag
	 * @return First element found or NULL if no match found
	 */
	public static Element searchFirstElement(Element elem, String elementName) {
		//XPath index starts with 1; [] has a higher precedence than //
		List<Element> query= xQueryElements(elem, "(//"+elementName+")[1]");
		
		if(query.isEmpty()) return null;
		return query.get(0);
	}
	
	/**
	 * Searches a document for all occurrences of an XML tag with JDOM Document as source.
	 * 
	 * @param doc			The JDOM Document
	 * @param elementName	Name of the XML tag
	 * @return List with all elements found or empty list if no match found
	 */
	public static List<Element> searchAllElements(Document doc, String elementName) {
		return xQueryElements(doc, "//"+elementName);
	}
	
	/**
	 * Searches a document for all occurrences of an XML tag with Element as source.
	 * (Expression is executed on the entire Document the Element is attached to!)
	 * 
	 * @param elem			The Element
	 * @param elementName	Name of the XML tag
	 * @return List with all elements found or empty list if no match found
	 */
	public static List<Element> searchAllElements(Element elem, String elementName) {
		return xQueryElements(elem, "//"+elementName);
	}
	
	/**
	 * Checks whether two elements have an equal XML tag.
	 * (Name of the XML tag and all attributes including their order must be equal)
	 * 
	 * @param element1		Element1
	 * @param element2		Element2
	 * @return true if of equal tag or false if not
	 */
	public static boolean equalTag(Element element1, Element element2) {
		List<Attribute> attributeList1 = element1.getAttributes();
		List<Attribute> attributeList2 = element2.getAttributes();
		
		if(!element1.getName().equals(element2.getName())) return false;
		if(attributeList1.size()!=attributeList2.size()) return false;
		for(int i=0; i<attributeList1.size(); i++) {
			if(!attributeList1.get(i).getName().equals(attributeList2.get(i).getName())) return false;
			if(!attributeList1.get(i).getValue().equals(attributeList2.get(i).getValue())) return false;
		}
		
		return true;
	}
	
	/**
	 * Recursive replacement of all text values in destElements with their corresponding 
	 * new ones from srcElements.
	 * (srcElements and destElements must be matching tags and have the same depth!)
	 * 
	 * @param srcElements		Element list with new values
	 * @param destElements		Element list with values to be replaced
	 * @return true if reached deepest Element or false if not
	 */
	public static boolean replace(List<Element> srcElements, List<Element> destElements) {
		if(srcElements.isEmpty()&&destElements.isEmpty()) return true;
		if(srcElements.isEmpty()^destElements.isEmpty()) {
			System.err.println("Error during value replacement: Sensor xml trees have mismatching deph (maybe tags with no value)");
			System.exit(-1);
		}
		for(Element src : srcElements) {
			for(Element dest : destElements) {		
				if(equalTag(src, dest)) {
					boolean deadEnd = replace(src.getChildren(), dest.getChildren());
					if(deadEnd) dest.setText(src.getText());
					break;
				}
			}
		}
		
		return false;
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
	}
}