package com.ejie.ab39a.utils;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.ejie.ab39a.constantes.Constantes;

/**
 * Utilidades para trabajar con un XML
 * 
 *  
 * 
 */
public class XMLAccessor {

	private Document xmlDocument;
	private XPath xPath;

	/**
	 * Constructor del XMLAccessor
	 * 
	 * @param xmlValue
	 *            xml a parsear
	 * @throws UnsupportedEncodingException
	 *             ue excepcion de tipo UnsupportedEncodingException
	 * @throws ParserConfigurationException
	 *             pa excepcion de tipo ParserConfigurationException
	 * @throws SAXException
	 *             sax excepcion de tipo SAXException
	 * @throws IOException
	 *             io excepcion de tipo IOException
	 */
	public XMLAccessor(String xmlValue)
			throws UnsupportedEncodingException, ParserConfigurationException, SAXException, IOException {

		DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
		this.xmlDocument = builder.parse(new ByteArrayInputStream(xmlValue.getBytes(Constantes.UTF_8)));

		this.xPath = XPathFactory.newInstance().newXPath();

	}

	/**
	 * Obtiene un valor del XML
	 * 
	 * @param path
	 *            ruta xpath a obtener
	 * @return value de la ruta xpath
	 * @throws XPathExpressionException
	 *             e e
	 * 
	 * 
	 */
	public String getValue(String path) throws XPathExpressionException {
		return this.xPath.compile(path).evaluate(this.xmlDocument);
	}

	/**
	 * Obtiene el contenido del XML
	 * 
	 * @param path
	 *            ruta xpath a obtener
	 * @return XML obtenido
	 * @throws XPathExpressionException
	 *             e e
	 * @throws TransformerException
	 *             e e
	 * 
	 */
	public String getXMLValue(String path) throws XPathExpressionException, TransformerException {
		String xmlValue;

		Node result = (Node) this.xPath.evaluate(path, this.xmlDocument, XPathConstants.NODE);

		StringWriter buf = new StringWriter();
		Transformer xform = TransformerFactory.newInstance().newTransformer();
		xform.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
		xform.transform(new DOMSource(result), new StreamResult(buf));

		xmlValue = buf.toString();

		return xmlValue;
	}

	/**
	 * Obtiene una propiedad
	 * 
	 * @param path
	 *            path de la propiedad
	 * @param key
	 *            nombre del tag key
	 * @param value
	 *            nombre del tag value
	 * @return Property Property
	 * 
	 * @throws XPathExpressionException
	 *             e e
	 * 
	 */
	public Property getProperty(String path, String key, String value) throws XPathExpressionException {

		NodeList nodeList = (NodeList) this.xPath.compile(path).evaluate(this.xmlDocument, XPathConstants.NODESET);
		Node node;
		Property property = null;

		if (nodeList.getLength() != 0) {

			// obtenemos el primero y lo devolvemos
			NodeList childrenNodeList = nodeList.item(0).getChildNodes();

			property = new Property();

			for (int index = 0; index < childrenNodeList.getLength(); index++) {

				node = nodeList.item(index);
				if (key.equals(node.getNodeName())) {
					property.setKey(node.getNodeValue());
				} else if (value.equals(node.getNodeName())) {
					property.setValue(node.getNodeValue());
				}

			}
		}

		return property;

	}

	/**
	 * Obtiene una propiedad
	 * 
	 * @param path
	 *            path de la propiedad
	 * @param key
	 *            nombre del tag key
	 * @param value
	 *            nombre del tag value
	 * @return List<Property> List
	 * @throws XPathExpressionException
	 *             e e
	 * 
	 */
	public List<Property> getProperties(String path, String key, String value) throws XPathExpressionException {

		NodeList nodeList = (NodeList) this.xPath.compile(path).evaluate(this.xmlDocument, XPathConstants.NODESET);
		Node nodeChild;
		Node node;
		Property property = null;
		NodeList childrenNodeList;
		List<Property> properties = new ArrayList<Property>();

		for (int i = 0; i < nodeList.getLength(); i++) {

			node = nodeList.item(i);
			property = new Property(); // NOPMD: es necesario crear el objeto

			// obtenemos el primero y lo devolvemos
			childrenNodeList = node.getChildNodes();

			for (int index = 0; index < childrenNodeList.getLength(); index++) {

				nodeChild = childrenNodeList.item(index);

				if (key.equals(nodeChild.getNodeName()) && nodeChild.hasChildNodes()) {
					property.setKey(nodeChild.getFirstChild().getNodeValue());
				} else if (value.equals(nodeChild.getNodeName()) && nodeChild.hasChildNodes()) {
					property.setValue(nodeChild.getFirstChild().getNodeValue());
				}

			}

			// anadida la propiedad
			properties.add(property);

		} // fin del for

		return properties;

	}

	/**
	 * Clase interna.
	 * 
	 *  
	 * 
	 */
	public class Property {

		private String key;
		private String value;

		/**
		 * Constructor por defecto
		 */
		public Property() {
			super();
		}

		/**
		 * Constructor Property
		 * 
		 * @param key
		 *            clave clave del elemento
		 * @param value
		 *            valor del elemento
		 */
		public Property(String key, String value) {
			this.key = key;
			this.value = value;
		}

		/**
		 * get Key()
		 * 
		 * @return key String
		 */
		public String getKey() {
			return this.key;
		}

		/**
		 * set Key()
		 * 
		 * @param key
		 *            key
		 */
		public void setKey(String key) {
			this.key = key;
		}

		/**
		 * get Value()
		 * 
		 * @return value cadena con el valor
		 */
		public String getValue() {
			return this.value;
		}

		/**
		 * set Value()
		 * 
		 * @param value
		 *            value cadena con el valor
		 */
		public void setValue(String value) {
			this.value = value;
		}

	}
}