/**

 * @author Ibermatica

 * 

 * Fecha de Creaci�n: 31/01/2019

 */

package com.ejie.ad74a.util.pid;

import java.io.ByteArrayInputStream;
import java.io.File;

import java.io.FileInputStream;

import java.io.IOException;

import java.io.InputStream;

import java.util.HashMap;

import java.util.Map;

import javax.xml.parsers.DocumentBuilder;

import javax.xml.parsers.DocumentBuilderFactory;

import javax.xml.parsers.FactoryConfigurationError;

import javax.xml.parsers.ParserConfigurationException;

import javax.xml.transform.TransformerException;

import javax.xml.xpath.XPathException;

//import org.apache.xpath.XPathAPI;
//import it.svario.xpathapi.jaxp.XPathAPI;

import org.w3c.dom.Document;

import org.w3c.dom.Element;

import org.w3c.dom.Node;

import org.w3c.dom.NodeList;

import org.xml.sax.SAXException;

import com.sun.org.apache.xpath.internal.XPathAPI;

/** Representa un documento xml. */

public final class XmlDocument {

	private Document domDoc;

	/** Construye un objeto de la clase. */

	public XmlDocument() {

		domDoc = null;

	}

	/** Construye un objeto de la clase. */

	public XmlDocument(Document document) {

		domDoc = document;

	}

	/**
	 * 
	 * Crea el documento a partir de texto xml.
	 * 
	 * 
	 * 
	 * @param text
	 * 
	 *             Texto fuente.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public void createFromStringText(final String text) throws Exception {

		ByteArrayInputStream src;

		try {

			src = new ByteArrayInputStream(text.getBytes());

			domDoc = parse(src);

		}

		catch (Exception exc) {

			throw new Exception("" + XmlErrorCodes.EC_LOAD_XML);

		}

	}

	/**
	 * 
	 * Crea el documento a partir de texto xml.
	 * 
	 * 
	 * 
	 * @param text
	 * 
	 *             Texto fuente.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public void createFromUtf8StringText(final String text) throws Exception {

		ByteArrayInputStream src;

		try {

			src = new ByteArrayInputStream(text.getBytes("UTF8"));

			domDoc = parse(src);

		}

		catch (Exception exc) {

			throw new Exception("" + XmlErrorCodes.EC_LOAD_XML);

		}

	}

	/**
	 * 
	 * Crea el documento a partir de texto xml codificado en UTF-8.
	 * 
	 * 
	 * 
	 * @param text
	 * 
	 *             Texto fuente.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public void createFromUtf8Text(final byte[] text) throws Exception {

		ByteArrayInputStream src;

		try {

			src = new ByteArrayInputStream(text);

			domDoc = parse(src);

		}

		catch (Exception exc) {

			throw new Exception("" + XmlErrorCodes.EC_LOAD_XML);

		}

	}

	/**
	 * 
	 * Crea el documento a partir del contenido xml de un fichero de recursos.
	 * 
	 * 
	 * 
	 * @param pathname
	 * 
	 *                 Ruta de acceso y nombre del fichero fuente.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public void createFromFile(final String pathname)

			throws Exception, IOException {

		InputStream inputStream = null;

		try {

			inputStream = getClass().getResourceAsStream(pathname);

			domDoc = parse(inputStream);

		}

		catch (Exception exc) {

			throw new Exception("" + XmlErrorCodes.EC_LOAD_XML);

		}

		finally {

			if (inputStream != null) {

				inputStream.close();

			}

		}

	}

	/**
	 * 
	 * Crea el documento a partir del contenido xml de un fichero del sistema de
	 * 
	 * archivos.
	 * 
	 * 
	 * 
	 * @param pathname
	 * 
	 *                 Ruta de acceso y nombre del fichero fuente.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public void createFromSystemFile(final String pathname)

			throws Exception, IOException {

		try {

			File src = new File(pathname);

			FileInputStream fis = new FileInputStream(src);

			domDoc = parse(fis);

		}

		catch (Exception exc) {

			throw new Exception("" + XmlErrorCodes.EC_LOAD_XML);

		}

	}

	/**
	 * 
	 * Devuelve el texto xml del documento.
	 * 
	 * 
	 * 
	 * @param omitXmlHdr
	 * 
	 *                   Especifica si omitir la cabecera xml.
	 * 
	 * @return El texto mencionado.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public String getStringText(boolean omitXmlHdr) throws Exception {

		return XmlTransformer.transformXmlDocumentToXmlStringText(this,

				omitXmlHdr);

	}

	/**
	 * 
	 * Devuelve el texto xml del documento codificado en UTF-8.
	 * 
	 * 
	 * 
	 * @param omitXmlHdr
	 * 
	 *                   Especifica si omitir la cabecera xml.
	 * 
	 * @return El texto mencionado.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public byte[] getUtf8Text(boolean omitXmlHdr) throws Exception {

		return XmlTransformer.transformXmlDocumentToXmlUtf8Text(this,

				omitXmlHdr);

	}

	/**
	 * 
	 * Escribe el documento en un fichero.
	 * 
	 * 
	 * 
	 * @param pathname
	 * 
	 *                 Ruta de acceso y nombre del fichero.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public void writeToFile(String pathname) throws Exception {

		XmlTransformer.transformXmlDocumentToXmlFile(this, pathname);

	}

	/**
	 * 
	 * Devuelve el elemento ra�z del documento.
	 * 
	 * 
	 * 
	 * @return El elemento mencionado.
	 * 
	 */

	public XmlElement getRootElement() {

		XmlElement elem = null;

		Element domElem;

		domElem = domDoc.getDocumentElement();

		elem = new XmlElement(domElem);

		return elem;

	}

	/**
	 * 
	 * Devuelve el documento Dom subyacente.
	 * 
	 * 
	 * 
	 * @return El documento mencionado.
	 * 
	 */

	public Document getDomDocument() {

		return domDoc;

	}

	/**
	 * 
	 * M�todo que obtiene el valor de un elemento a trav�s de una expresi�n
	 * 
	 * XPath.
	 * 
	 * 
	 * 
	 * @param xPathExpression
	 * 
	 *                        Expresi�n que obtiene el valor deseado.
	 * 
	 * @return El valor del elemento.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public String getValueOfElement(String xPathExpression)

			throws Exception {

		String value = null;

		XmlElement element;

		element = getXmlElement(xPathExpression);

		if (element == null) {

			throw new Exception("" + XmlErrorCodes.EC_INVALID_ELEMENT_NAME);

		}

		value = element.getValue();

		return value;

	}

	/**
	 * 
	 * M�todo que obtiene el nombre del elemento padre a trav�s de una expresi�n
	 * 
	 * XPath que apunta a uno de sus hijos.
	 * 
	 * 
	 * 
	 * @param xPathExpression
	 * 
	 *                        Expresi�n que obtiene el valor deseado.
	 * 
	 * @return El nombre del elemento.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public String getParentElementName(String xPathExpression)

			throws Exception {

		String value = null;

		Node node;

		try {

			node = XPathAPI.selectSingleNode((Node) getDomDocument(),

					xPathExpression);

			value = node.getParentNode().getNodeName().toString().trim();

			// if (value.equals("")) {

			if ("".equals(value)) {

				throw new Exception("" + XmlErrorCodes.EC_INVALID_ELEMENT_NAME);

			}

			return value;

		}

		catch (TransformerException exc) {

			throw new Exception("" + XmlErrorCodes.EC_IRRETRIEVABLE_VALUE,

					exc);

		}

	}

	/**
	 * 
	 * M�todo que obtiene un nodo a trav�s de una expresi�n XPath.
	 * 
	 * 
	 * 
	 * @param xpathExpression
	 * 
	 *                        Expresi�n que obtiene el valor deseado.
	 * 
	 * @return El valor del nodo elemento. Si no existe se devuelve nulo
	 * 
	 * @throws Exception
	 * 
	 */

	public XmlElement getXmlElement(String xpathExpression)

			throws Exception {

		Node node;

		XmlElement element = null;

		node = XPathAPI.selectSingleNode(getDomDocument(), xpathExpression);

		if (node != null) {

			element = new XmlElement((Element) node);

		}

		return element;

	}

	/**
	 * 
	 * M�todo que obtiene un conjunto de nodos a trav�s de una expresi�n XPath.
	 * 
	 * 
	 * 
	 * @param xpathExpression
	 * 
	 *                        Expresi�n que obtiene el valor deseado.
	 * 
	 * @return El valor de los nodos elemento. Si no existe se devuelve nulo
	 * 
	 * @throws Exception
	 * 
	 */

	public XmlElements getXmlElements(String xpathExpression)

			throws Exception {

		NodeList nodeList;

		int counter;

		XmlElements elements = null;

		XmlElement[] elem;

		nodeList = XPathAPI.selectNodeList(getDomDocument(), xpathExpression);

		elem = new XmlElement[nodeList.getLength()];

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

			elem[counter] = new XmlElement((Element) nodeList.item(counter)); // NOPMD by BICHMAOS, se crea dentro
																				// el objeto porque no se sabe antes
																				// la informaci�n

		}

		elements = new XmlElements(elem);

		return elements;

	}

	/*
	 * 
	 * NO FUNCIONA BIEN: public void insertCommentNodeBefore(String
	 * 
	 * commentToInsert, String referenceNodeName) throws Exception { Node
	 * 
	 * comment; Node referenceNode; NodeList nodeList;
	 * 
	 * 
	 * 
	 * try { comment = (Node)domDoc.createComment(commentToInsert); referenceNode =
	 * 
	 * XPathAPI.selectSingleNode((Node)getDomDocument(), "//" +
	 * 
	 * referenceNodeName); comment = domDoc.insertBefore(comment, referenceNode); }
	 * 
	 * catch(TransformerException te) { throw new
	 * 
	 * Exception(XmlErrorCodes.EC_INVALID_ELEMENT_NAME, te); } }
	 * 
	 */

	/**
	 * 
	 * M�todo que obtiene una colecci�n de elementos a partir de su nombre.
	 * 
	 * 
	 * 
	 * @param name
	 * 
	 *             Nombre de los elementos a recuperar.
	 * 
	 * @return Colecci�n de elementos. Si no existe se devuelve nulo
	 * 
	 * @throws Exception
	 * 
	 */

	public XmlElements getElementsByTagName(String name)

			throws Exception {

		NodeList nodeList = null;

		XmlElements elements = null;

		XmlElement[] elem;

		try {

			nodeList = getDomDocument().getElementsByTagName(name);

			if (nodeList == null) {

				throw new Exception("" + XmlErrorCodes.EC_INVALID_ELEMENT_NAME);

			}

			else {

				elem = new XmlElement[nodeList.getLength()];

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

					elem[i] = new XmlElement((Element) nodeList.item(i)); // NOPMD by BICHMAOS, se crea dentro el objeto
																			// porque no se sabe antes la informaci�n

				}

				elements = new XmlElements(elem);

			}

			return elements;

		}

		catch (Exception exc) {

			throw new Exception("" + XmlErrorCodes.EC_TRANSFORM_EXCEPTION,

					exc);

		}

	}

	/**
	 * 
	 * Selecciona el nodo del documento XML, el cual es dato miembro de la
	 * 
	 * presente clase, que determina una expresi�n XPath y devuelve en un objeto
	 * 
	 * de tipo Map el contenido de dicho nodo.
	 * 
	 * 
	 * 
	 * @param xPathExpression
	 * 
	 *                        Expresi�n que obtiene el nodo deseado.
	 * 
	 * @return El conjunto de pares nombre de elemento - valor correspondiente a
	 * 
	 *         dicho nodo.
	 * 
	 * @throws Exception
	 * 
	 *                   si se produce alg�n error.
	 * 
	 */

	public Map getNodeValuesById(String xPathExpression) throws Exception {

		Map nodeMap;

		Node actualNode;

		NodeList nodeList = null;

		try {

			nodeMap = new HashMap();

			nodeList = XPathAPI.selectNodeList(domDoc, xPathExpression);

			if (nodeList == null) {

				throw new Exception("" + XmlErrorCodes.EC_INVALID_ELEMENT_NAME);

			}

			else {

				int nodeCounter;

				if (nodeList.getLength() == 1) {

					actualNode = nodeList.item(0);

					nodeList = actualNode.getChildNodes();

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

						actualNode = nodeList.item(nodeCounter);

						setNodeValuesIntoMap(actualNode, nodeMap);

					}

				}

				else

				if (nodeList.getLength() > 1) {

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

						NodeList actualNodeList;

						actualNodeList = nodeList.item(nodeCounter)

								.getChildNodes();

						for (nodeCounter = 0; nodeCounter < actualNodeList.getLength(); nodeCounter++) {

							actualNode = actualNodeList.item(nodeCounter);

							setNodeValuesIntoMap(actualNode, nodeMap);

						}

					}

				}

			}

			return nodeMap;

		}

		catch (Exception exc) {

			throw new Exception("" + XmlErrorCodes.EC_TRANSFORM_EXCEPTION,

					exc);

		}

	}

	/*
	 * 
	 * M�todo recursivo que permite introducir los pares nombre de elemento -
	 * 
	 * valor en el objeto Map correspondiente.
	 * 
	 */

	private void setNodeValuesIntoMap(Node node, Map nodes) {

		if (node.getNodeType() == Node.TEXT_NODE) {

			if (!node.getNodeValue().trim().equals("")) {

				// Caso Base 1:

				nodes.put(node.getParentNode().getNodeName().toString().trim(),

						node.getNodeValue().toString().trim());

			}

		}

		else

		if ((node.getNodeType() == Node.ELEMENT_NODE) && ((node.getFirstChild() != null))) {

			if ((node.getFirstChild().getNodeType() == Node.TEXT_NODE) && !node.getFirstChild()

					.getNodeValue()

					.trim()

					.equals("")) {

				// Caso Base 2:

				nodes.put(node.getNodeName().toString().trim(),

						node.getFirstChild().getNodeValue().toString().trim());

			}

			else

			if (node.getFirstChild().getNextSibling().getNodeType() == Node.ELEMENT_NODE) {

				// Caso Recursivo:

				NodeList childNodeList;

				Node actualChildNode;

				Map childMap;

				childMap = new HashMap();

				childNodeList = node.getChildNodes();

				int childCounter;

				for (childCounter = 0; childCounter < childNodeList.getLength(); childCounter++) {

					actualChildNode = childNodeList.item(childCounter);

					setNodeValuesIntoMap(actualChildNode, childMap);

				}

				nodes.put(node.getNodeName().toString().trim(), childMap);

			}

		}

	}

	private synchronized static Document parse(InputStream is)
			throws ParserConfigurationException, FactoryConfigurationError, SAXException, IOException {

		if (db == null) {

			db = DocumentBuilderFactory.newInstance().newDocumentBuilder();

		}

		return db.parse(is);

	}

	private static DocumentBuilder db;

}
