package tools.xml;

import java.awt.Color;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import logging.Log;
import logging.LogLevel;

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

/**
 * This class contains some helper functions to parse XML files and read values
 * out of XML documents with JAXP.
 * 
 * @author Andreas Wenger
 */
public class XMLReader {

	/**
	 * Reads and returns the XML document at the given path.
	 */
	public static Document readFile(final String file)

	throws ParserConfigurationException, SAXException, IOException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		builder.setEntityResolver(new NoResolver());

		return builder.parse(file);
	}

	/**
	 * Reads and returns the XML document at the given input stream.
	 */
	public static Document readFile(final InputStream stream)
			throws ParserConfigurationException, SAXException, IOException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		builder.setEntityResolver(new NoResolver());

		return builder.parse(stream);
	}

	/**
	 * Reads and returns the XML document from given String
	 * 
	 * @param s
	 *            String
	 * @return Document
	 * @throws ParserConfigurationException
	 * @throws SAXException
	 * @throws IOException
	 */
	public static Document readString(final String s)
			throws ParserConfigurationException, SAXException, IOException {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = factory.newDocumentBuilder();
		builder.setEntityResolver(new NoResolver());

		StringReader reader = new StringReader(s);
		InputSource inSource = new InputSource(reader);

		return builder.parse(inSource);
	}

	/**
	 * Gets the root element of the given document.
	 */
	public static Element root(final Document doc) {
		return doc.getDocumentElement();
	}

	/**
	 * Gets the text of the given element, or "".
	 */
	public static String text(final Element element) {
		if (element == null) {
			return "";
		} else {
			return element.getTextContent();
		}
	}

	/**
	 * Gets the trimmed text of the given element, or "".
	 */
	public static String textTrim(final Element element) {
		if (element == null) {
			return "";
		} else {
			return element.getTextContent().trim();
		}
	}

	/**
	 * Reads and returns the value of the attribute with the given name of the
	 * given element, or null if not found.
	 */
	public static String attribute(final Element element, final String name) {
		if (element == null) {
			return null;
		}
		NamedNodeMap attributes = element.getAttributes();
		if (attributes == null) {
			return null;
		}
		Node value = attributes.getNamedItem(name);
		if (value == null) {
			return null;
		}
		return value.getTextContent();
	}

	/**
	 * Reads and returns the value of the attribute with the given name of the
	 * given element, or "" if not found.
	 */
	public static String attributeNotNull(final Element element,
			final String name) {
		String ret = XMLReader.attribute(element, name);
		if (ret == null) {
			ret = "";
		}
		return ret;
	}

	/**
	 * Gets the first child element of the given node with the given name, or
	 * null if not found.
	 */
	public static Element element(final Node parent, final String name) {
		NodeList children = parent.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			String nodeName = children.item(i).getNodeName();
			if (nodeName.equals(name)) {
				return (Element) children.item(i);
			}
		}
		return null;
	}

	/**
	 * Gets the a list of all child elements of the given node with the given
	 * name. If no such elements are found, the returned list is empty.
	 */
	public static List<Element> elements(final Node parent, final String name) {
		ArrayList<Element> ret = new ArrayList<Element>();
		NodeList children = parent.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			if (children.item(i).getNodeName().equals(name)) {
				ret.add((Element) children.item(i));
			}
		}
		return ret;
	}

	/**
	 * Gets the a list of all child elements of the given node. If no such
	 * elements are found, the returned list is empty.
	 */
	public static List<Element> elements(final Node parent) {
		ArrayList<Element> ret = new ArrayList<Element>();
		NodeList children = parent.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			if (children.item(i) instanceof Element) {
				ret.add((Element) children.item(i));
			}
		}
		return ret;
	}

	/**
	 * Gets the text of the given child element with the given name of the given
	 * parent element, or null.
	 */
	public static String elementText(final Element parentElement,
			final String elementName) {
		Element e = XMLReader.element(parentElement, elementName);
		if (e == null) {
			return null;
		} else {
			return XMLReader.text(e);
		}
	}

	/**
	 * Gets the text of the given child element with the given name of the given
	 * parent element, or "".
	 */
	public static String elementTextNotNull(final Element parentElement,
			final String elementName) {
		return XMLReader.text(XMLReader.element(parentElement, elementName));
	}

	/**
	 * Reads and returns the value of the attribute with the given name of the
	 * given element, or null if not found or not of format double.
	 */
	public static Double elementDoubleAttribute(final Element element,
			final String name) {
		String s;
		Double d = null;
		s = XMLReader.attribute(element, name);
		if (s != null) {
			try {
				d = Double.parseDouble(s);
			} catch (NumberFormatException e) {
				Log.outl("XMLReader.elementDoubleAttribute: " + s
						+ " is not a double value", LogLevel.warning);
			}
		}
		return d;
	}

	/**
	 * Reads and returns the value of the attribute with the given name of the
	 * given element, or null if not found or not of format Float.
	 */
	public static Float elementFloatAttribute(final Element element,
			final String name) {
		String s;
		Float f = null;
		s = XMLReader.attribute(element, name);
		if (s != null) {
			try {
				f = Float.parseFloat(s);
			} catch (NumberFormatException e) {
				Log.outl("XMLReader.elementFloatAttribute: " + s
						+ " is not a Float value", LogLevel.warning);
			}
		}
		return f;
	}

	/**
	 * Reads and returns the value of the attribute with the given name of the
	 * given element, or null if not found or not of format Integer.
	 */
	public static Integer elementIntegerAttribute(final Element element,
			final String name) {
		String s;
		Integer f = null;
		s = XMLReader.attribute(element, name);
		if (s != null) {
			try {
				f = Integer.parseInt(s);
			} catch (NumberFormatException e) {
				Log.outl("XMLReader.elementIntegerAttribute: " + s
						+ " is not a Integer value", LogLevel.warning);
			}
		}
		return f;
	}

	/**
	 * Reads and returns the value of the attribute with the given name of the
	 * given element, or null if not found or not of format Color. Format is
	 * 0xrrggbbaa rr: red (00 .. ff) gg: green (00 .. ff) bb: blue (00 .. ff)
	 * aa: alpha (00 .. ff) Alpha-value may be missing
	 */
	public static Color elementColorAttribute(final Element element,
			final String name) {
		String s;
		Color c = null;
		s = XMLReader.attribute(element, name);
		if (s != null) {
			try {
				int r = Integer.parseInt(s.substring(2, 4), 16);
				int g = Integer.parseInt(s.substring(4, 6), 16);
				int b = Integer.parseInt(s.substring(6, 8), 16);
				int alpha = Integer.parseInt(s.substring(8, 10), 16);

				c = new Color(r, g, b, alpha);
			} catch (NumberFormatException e) {
				Log.outl("XMLReader.elementFloatAttribute: " + s
						+ " is not a Float value", LogLevel.warning);
			}
		}
		return c;
	}

	/**
	 * Reads and returns the value of the attribute with the given name of the
	 * given element, or null if not found or not of format Boolean. Returns
	 * true if string representation of attribute is "True" or "true", else
	 * returns false
	 */
	public static Boolean elementBooleanAttribute(final Element element,
			final String name) {
		String s;
		Boolean f = null;
		s = XMLReader.attribute(element, name);
		if (s != null) {
			try {
				f = Boolean.parseBoolean(s);
			} catch (NumberFormatException e) {
				Log.outl("XMLReader.elementBooleanAttribute: " + s
						+ " is not a Boolean value", LogLevel.warning);
			}
		}
		return f;
	}

}