package org.phosphoresce.commons.util;

import java.io.IOException;
import java.io.StringWriter;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.w3c.dom.CDATASection;
import org.w3c.dom.Comment;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ProcessingInstruction;
import org.w3c.dom.Text;
import org.xml.sax.SAXException;

/**
 * ドキュメント操作におけるユーティリティクラス。<br>
 * 
 * @author Kitagawa<br>
 * 
 *<!--
 * 更新日		更新者			更新内容
 * 2010/01/27	Kitagawa		新規作成
 * 2010/03/10	Kitagawa		createDocumentメソッド追加
 *-->
 */
public final class DocumentUtil {

	/** XMLフォーマットスタイルシート */
	public static String FORMAT_XSLT = "/" + DocumentUtil.class.getPackage().getName().replace('.', '/') + "/XmlFormat.xsl";

	/**
	 * コンストラクタ<br>
	 */
	private DocumentUtil() {
		super();
	}

	/**
	 * 新規のドキュメントオブジェクトを生成します。<br>
	 * @return ドキュメントオブジェクト
	 * @throws ParserConfigurationException 正常にドキュメントオブジェクトを生成できなかった場合にスローされます
	 */
	public static Document createNewDocument() throws ParserConfigurationException {
		return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
	}

	/**
	 * 指定されたXMLファイルよりドキュメントオブジェクトを生成します。<br>
	 * @param xmlFile XMLリソースパス
	 * @return ドキュメントオブジェクト
	 * @throws FactoryConfigurationError ドキュメントビルダファクトリの生成に失敗した場合にスローされます
	 * @throws ParserConfigurationException 正常にドキュメントオブジェクトを生成できなかった場合にスローされます
	 * @throws IOException XMLリソースの読み込みに失敗した場合にスローされます
	 * @throws SAXException 不正な形式のXMLリソースを読み込もうとした場合にスローされます
	 */
	public static Document createDocument(String xmlFile) throws SAXException, IOException, ParserConfigurationException, FactoryConfigurationError {
		return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(FileUtil.getFile(xmlFile));
	}

	/**
	 * 指定された他のドキュメントノードを自身のドキュメントノードとして新規に作成します。<br>
	 * @param current カレントノード
	 * @param node コピー対象元ノート
	 * @return カレントノードから生成されたノード
	 */
	public static Node copyNode(Document current, Node node) {
		Node newNode = null;

		/*
		 * ノードタイプごとのインスタンス生成
		 */
		switch (node.getNodeType()) {
			case Node.ATTRIBUTE_NODE: {
				newNode = current.createAttribute(node.getNodeName());
				break;
			}
			case Node.CDATA_SECTION_NODE: {
				newNode = current.createCDATASection(node.getNodeName());
				((CDATASection) newNode).setData(((CDATASection) node).getData());
				break;
			}
			case Node.COMMENT_NODE: {
				newNode = current.createComment(node.getNodeName());
				((Comment) newNode).setData(((Comment) node).getData());
				break;
			}
			case Node.DOCUMENT_FRAGMENT_NODE: {
				newNode = current.createDocumentFragment();
				break;
			}
			case Node.DOCUMENT_NODE: {
				//newNode = current;
				//((Document) newNode).setDocumentURI(((Document) node).getDocumentURI());
				//((Document) newNode).setStrictErrorChecking(((Document) node).getStrictErrorChecking());
				//((Document) newNode).setXmlStandalone(((Document) node).getXmlStandalone());
				//((Document) newNode).setXmlVersion(((Document) node).getXmlVersion());
				Logger.getLogger(DocumentUtil.class.getName()).log(Level.WARNING, "Document node ignored");
				break;
			}
			case Node.DOCUMENT_TYPE_NODE: {
				//newNode = current.getDoctype();
				Logger.getLogger(DocumentUtil.class.getName()).log(Level.WARNING, "DocumentType node ignored");
				break;
			}
			case Node.ELEMENT_NODE: {
				newNode = current.createElement(node.getNodeName());
				for (int i = 0; node.getAttributes() != null && i <= node.getAttributes().getLength() - 1; i++) {
					Node attrNode = node.getAttributes().item(i);
					if (attrNode != null) {
						((Element) newNode).setAttribute(attrNode.getNodeName(), attrNode.getNodeValue());
					}
				}
				break;
			}
			case Node.ENTITY_NODE: {
				Logger.getLogger(DocumentUtil.class.getName()).log(Level.WARNING, "Entity node ignored");
				break;
			}
			case Node.ENTITY_REFERENCE_NODE: {
				newNode = current.createEntityReference(node.getNodeName());
				break;
			}
			case Node.NOTATION_NODE: {
				Logger.getLogger(DocumentUtil.class.getName()).log(Level.WARNING, "Notation node ignored");
				break;
			}
			case Node.PROCESSING_INSTRUCTION_NODE: {
				newNode = current.createProcessingInstruction(node.getNodeName(), ((ProcessingInstruction) node).getData());
				break;
			}
			case Node.TEXT_NODE: {
				newNode = current.createTextNode(((Text) node).getData());
				break;
			}
			default: {
				break;
			}
		}

		if (newNode == null) {
			return newNode;
		}

		//		// Value設定
		//		if (!StringUtil.isEmpty(node.getNodeValue())) {
		//			newNode.setNodeValue(node.getNodeValue());
		//		}
		//
		//		// Prefix設定
		//		if (!StringUtil.isEmpty(node.getPrefix())) {
		//			newNode.setPrefix(node.getPrefix());
		//		}
		//
		//		// TextContent設定
		//		if (!StringUtil.isEmpty(node.getTextContent())) {
		//			newNode.setTextContent(node.getTextContent());
		//		}

		// 子ノード設定
		if (node.getChildNodes() != null && node.getChildNodes().getLength() > 0) {
			for (int i = 0; i <= node.getChildNodes().getLength() - 1; i++) {
				Node child = node.getChildNodes().item(i);
				newNode.appendChild(copyNode(current, child));
			}
		}

		return newNode;
	}

	/**
	 * 指定されたノードの不要なスペース、改行を取り除きます。<br>
	 * @param node フォーマット対象ノード
	 */
	private static void preformatNode(Node node) {
		switch (node.getNodeType()) {
			case Node.ATTRIBUTE_NODE: {
				break;
			}
			case Node.CDATA_SECTION_NODE: {
				String data = ((CDATASection) node).getData();
				data = data.replaceAll("^\n*", "");
				data = data.replaceAll("^\t*", "");
				data = data.replaceAll("^[ ]*", "");
				data = data.replaceAll("[ ]*$", "");
				data = data.replaceAll("\t*$", "");
				data = data.replaceAll("\n*$", "");
				if (StringUtil.isEmpty(data)) {
					node.getParentNode().removeChild(node);
				} else {
					((CDATASection) node).setData(data);
				}
				break;
			}
			case Node.COMMENT_NODE: {
				String data = ((Comment) node).getData();
				data = data.replaceAll("^\n*", "");
				data = data.replaceAll("^\t*", "");
				data = data.replaceAll("^[ ]*", "");
				data = data.replaceAll("[ ]*$", "");
				data = data.replaceAll("\t*$", "");
				data = data.replaceAll("\n*$", "");
				if (StringUtil.isEmpty(data)) {
					node.getParentNode().removeChild(node);
				} else {
					((Comment) node).setData(data);
				}
				break;
			}
			case Node.DOCUMENT_FRAGMENT_NODE: {
				break;
			}
			case Node.DOCUMENT_NODE: {
				break;
			}
			case Node.DOCUMENT_TYPE_NODE: {
				break;
			}
			case Node.ELEMENT_NODE: {
				//				String data = node.getTextContent();
				//				data = data.replaceAll("^\n*", "");
				//				data = data.replaceAll("^\t*", "");
				//				data = data.replaceAll("^[ ]*", "");
				//				data = data.replaceAll("[ ]*$", "");
				//				data = data.replaceAll("\t*$", "");
				//				data = data.replaceAll("\n*$", "");
				break;
			}
			case Node.ENTITY_NODE: {
				break;
			}
			case Node.ENTITY_REFERENCE_NODE: {
				break;
			}
			case Node.NOTATION_NODE: {
				break;
			}
			case Node.PROCESSING_INSTRUCTION_NODE: {
				break;
			}
			case Node.TEXT_NODE: {
				String data = ((Text) node).getData();
				data = data.replaceAll("^\n*", "");
				data = data.replaceAll("^\t*", "");
				data = data.replaceAll("^[ ]*", "");
				data = data.replaceAll("[ ]*$", "");
				data = data.replaceAll("\t*$", "");
				data = data.replaceAll("\n*$", "");
				if (StringUtil.isEmpty(data)) {
					node.getParentNode().removeChild(node);
				} else {
					((Text) node).setData(data);
				}
				break;
			}
			default: {
			}
		}
		// 子ノード設定
		if (node.getChildNodes() != null && node.getChildNodes().getLength() > 0) {
			for (int i = 0; i <= node.getChildNodes().getLength() - 1; i++) {
				Node child = node.getChildNodes().item(i);
				preformatNode(child);
			}
		}
	}

	/**
	 * 指定されたノードをフォーマットした文字列として提供します。<br>
	 * @param node ドキュメントオブジェクト
	 * @return フォーマットされたXML文字列
	 * @throws TransformerFactoryConfigurationError XSLT環境初期化に失敗した場合にスローされます
	 * @throws TransformerException フォーマットに失敗した場合にスローされます
	 */
	public static String formatNode(Node node) throws TransformerFactoryConfigurationError, TransformerException {
		preformatNode(node);
		StringWriter writer = new StringWriter();
		StreamSource xslt = new StreamSource(DocumentUtil.class.getResourceAsStream(FORMAT_XSLT));
		Transformer transformer = TransformerFactory.newInstance().newTransformer(xslt);
		transformer.transform(new DOMSource(node), new StreamResult(writer));
		return writer.toString();
	}

	/**
	 * ターゲットノード配下の指定された名称のノードを取得します。<br>
	 * @param target ターゲットノード
	 * @param name 取得対象ノード名
	 * @return ノードオブジェクトリスト
	 */
	public static List findNodes(Node target, String name) {
		List result = new LinkedList();
		if (target == null) {
			return result;
		}
		NodeList list = target.getChildNodes();
		for (int i = 0; i <= list.getLength() - 1; i++) {
			Node node = list.item(i);
			if (node != null) {
				if ((name.equals(node.getNodeName()))) {
					result.add(node);
				} else if (node.getChildNodes().getLength() > 0) {
					result.addAll(findNodes(node, name));
				}
			}
		}
		return result;
	}

	/**
	 * ターゲットノード配下の指定された名称の先頭のノードを取得します。<br>
	 * @param target ターゲットノード
	 * @param name 取得対象ノード名
	 * @return 指定条件に合致した先頭ノード
	 */
	public static Node findNode(Node target, String name) {
		Node result = null;
		if (target == null) {
			return result;
		}
		NodeList list = target.getChildNodes();
		for (int i = 0; i <= list.getLength() - 1; i++) {
			Node node = list.item(i);
			if (node != null) {
				if (name.equals(node.getNodeName())) {
					result = node;
				} else if (node.getChildNodes().getLength() > 0) {
					result = findNode(node, name);
				}
			}
			if (result != null) {
				break;
			}
		}
		return result;
	}

	/**
	 * 指定されたドキュメントのルートエレメントオブジェクトを取得します。<br>
	 * @param document ドキュメントオブジェクト
	 * @return ルートエレメントオブジェクト
	 */
	public static Element getRootElement(Document document) {
		NodeList list = document.getChildNodes();
		for (int i = 0; i <= list.getLength() - 1; i++) {
			Node node = list.item(i);
			if (node.getNodeType() == Node.ELEMENT_NODE) {
				return (Element) node;
			}
		}
		return null;
	}
}
