package tools.xml;

import java.awt.Color;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;

/**
 * This class contains some helper functions to write XML files, using Java's
 * built-in JAXP.
 * 
 * @author Andreas Wenger
 */
public final class XMLWriter {

	/**
	 * private default constructor.
	 */
	private XMLWriter() {
	}

	/**
	 * Creates an empty DOM document.
	 * 
	 * @return returns created DOM document.
	 * @throws Exception
	 *             if DOM could not be builded
	 */
	public static Document createEmptyDocument() throws Exception {
		try {
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();
			return builder.newDocument();
		} catch (Exception ex) {
			throw new Exception("Creating a new DOM document failed: "
					+ ex.toString());
		}
	}

	/**
	 * Writes the given XML document into a file at the given path.
	 */
	public static void writeFile(final Document doc, final String path)
			throws IOException {
		File f = new File(path);
		XMLWriter.writeToFile(doc, f);
	}

	/**
	 * Writes the given XML document into given File.
	 */
	public static void writeToFile(final Document doc, final File f)
			throws IOException {
		FileOutputStream fos = new FileOutputStream(f);

		try {
			TransformerFactory tFactory = TransformerFactory.newInstance();
			Transformer transformer = tFactory.newTransformer();
			transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			transformer.setOutputProperty(
					"{http://xml.apache.org/xslt}indent-amount", "2");

			DOMSource source = new DOMSource(doc);
			StreamResult result = new StreamResult(fos);

			transformer.transform(source, result);
		} catch (Exception ex) {
			throw new IOException("Problem with XML-Transformer: "
					+ ex.toString());
		}
	}

	/**
	 * Adds and returns a new element with the given name as a child of the
	 * given element.
	 */
	public static Element addElement(final String name, final Node parentElement) {
		// get the parent document.
		// parentElement may be the document itself.
		Document parentDoc = parentElement.getOwnerDocument();
		Document doc = (parentDoc != null ? parentDoc
				: (Document) parentElement);
		// add element
		Node ret = doc.createElement(name);
		parentElement.appendChild(ret);
		return (Element) ret;
	}

	/**
	 * Adds and returns a new CDATASectionh with the given data as a child of
	 * the given element.
	 */
	public static CDATASection addCDATA(final Node parentElement,
			final String data) {
		// get the parent document.
		// parentElement may be the document itself.
		Document parentDoc = parentElement.getOwnerDocument();
		Document doc = (parentDoc != null ? parentDoc
				: (Document) parentElement);
		// add element
		Node ret = doc.createCDATASection(data);
		parentElement.appendChild(ret);
		return (CDATASection) ret;
	}

	/**
	 * Adds and returns a new CDATASectionh with the given data as a child of
	 * the given element.
	 */
	public static CDATASection addCDATA(final Node parentElement,
			final byte[] data) {
		// get the parent document.
		// parentElement may be the document itself.
		Document parentDoc = parentElement.getOwnerDocument();
		Document doc = (parentDoc != null ? parentDoc
				: (Document) parentElement);
		// add element
		String dataString = Base64.encodeBytes(data);
		Node ret = doc.createCDATASection(dataString);
		parentElement.appendChild(ret);
		return (CDATASection) ret;
	}

	/**
	 * Adds a new attribute with the given name and value to the given element.
	 */
	public static void addAttribute(final Node element, final String name,
			final String value) {
		((Element) element).setAttribute(name, value);
	}

	/**
	 * Adds a new double-attribute with the given name and value to the given
	 * element.
	 */
	public static void addAttributeDouble(final Node element,
			final String name, final double value) {
		((Element) element).setAttribute(name, Double.toString(value));
	}

	/**
	 * Adds a new float-attribute with the given name and value to the given
	 * element.
	 */
	public static void addAttributeFloat(final Node element, final String name,
			final float value) {
		((Element) element).setAttribute(name, Float.toString(value));
	}

	/**
	 * Adds a new int-attribute with the given name and value to the given
	 * element.
	 */
	public static void addAttributeInt(final Node element, final String name,
			final int value) {
		((Element) element).setAttribute(name, Integer.toString(value));
	}

	/**
	 * Adds a new boolean-attribute with the given name and value to the given
	 * element.
	 */
	public static void addAttributeBoolean(final Node element,
			final String name, final boolean value) {
		((Element) element).setAttribute(name, Boolean.toString(value));
	}

	/**
	 * Adds a new Color-attribute with the given name and value to the given
	 * element.
	 */
	public static void addAttributeColor(final Node element, final String name,
			final Color value) {
		((Element) element).setAttribute(name, "0x"
				+ XMLWriter.toHexff(value.getRed())
				+ XMLWriter.toHexff(value.getGreen())
				+ XMLWriter.toHexff(value.getBlue())
				+ XMLWriter.toHexff(value.getAlpha()));
	}

	private static String toHexff(final int i) {
		String s = Integer.toHexString(i);
		while (s.length() < 2) {
			s = "0" + s;
		}
		return s;
	}

}
