package com.ozacc.mail.xml.impl;

import java.io.File;
import java.util.Properties;

import javax.mail.internet.InternetAddress;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
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 org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.ozacc.mail.Mail;
import com.ozacc.mail.xml.XMLBuildException;
import com.ozacc.mail.xml.XMLBuilder;

/**
 * JDK 1.4以降の標準XMLライブラリを使用して実装されたXMLBuilder。
 * 
 * @since 1.0
 * @author Tomohiro Otsuka
 * @version $Id: XMLBuilderImpl.java,v 1.4.2.1 2005/01/21 22:15:07 otsuka Exp $
 */
public class XMLBuilderImpl implements XMLBuilder {

	private String charset = "UTF-8";

	/**
	 * コンストラクタ。
	 */
	public XMLBuilderImpl() {}

	/**
	 * コンストラクタ。
	 * 出力XMLファイルの文字コードを指定します。デフォルトはUTF-8。
	 * 
	 * @param charset 出力XMLファイルの文字コード
	 */
	public XMLBuilderImpl(String charset) {
		super();
		this.charset = charset;
	}

	/**
	 * 出力XMLファイルの文字コードを返します。
	 * 
	 * @return 出力XMLファイルの文字コード
	 */
	public String getCharset() {
		return charset;
	}

	/**
	 * 出力XMLファイルの文字コードを指定します。デフォルトはUTF-8。
	 * 
	 * @param charset 出力XMLファイルの文字コード
	 */
	public void setCharset(String charset) {
		this.charset = charset;
	}

	/**
	 * @see com.ozacc.mail.xml.XMLBuilder#buildDocument(com.ozacc.mail.Mail)
	 */
	public Document buildDocument(Mail mail) throws XMLBuildException {
		Document doc = createNewDocument();

		/*DOMImplementation domImpl = doc.getImplementation();
		 DocumentType docType = domImpl.createDocumentType("mail", Mail.DOCTYPE_PUBLIC, Mail.DOCTYPE_SYSTEM);
		 doc.appendChild(docType);*/

		Element mailElem = doc.createElement("mail");

		// Return-Path
		if (mail.getReturnPath() != null) {
			InternetAddress returnPath = mail.getReturnPath();
			Element returnPathElem = convertInternetAddressIntoElement(returnPath, "returnPath",
					doc);
			mailElem.appendChild(returnPathElem);
		}

		// From
		if (mail.getFrom() != null) {
			InternetAddress from = mail.getFrom();
			Element fromElem = convertInternetAddressIntoElement(from, "from", doc);
			mailElem.appendChild(fromElem);
		}

		if (mail.getTo().length > 0 || mail.getCc().length > 0 || mail.getBcc().length > 0) {
			Element recipientsElem = doc.createElement("recipients");

			// To
			if (mail.getTo().length > 0) {
				for (int i = 0; i < mail.getTo().length; i++) {
					InternetAddress to = mail.getTo()[i];
					Element toElem = convertInternetAddressIntoElement(to, "to", doc);
					recipientsElem.appendChild(toElem);
				}
			}
			// Cc
			if (mail.getCc().length > 0) {
				for (int i = 0; i < mail.getCc().length; i++) {
					InternetAddress cc = mail.getCc()[i];
					Element ccElem = convertInternetAddressIntoElement(cc, "cc", doc);
					recipientsElem.appendChild(ccElem);
				}
			}
			// Bcc
			if (mail.getBcc().length > 0) {
				for (int i = 0; i < mail.getBcc().length; i++) {
					InternetAddress bcc = mail.getBcc()[i];
					Element bccElem = convertInternetAddressIntoElement(bcc, "bcc", doc);
					recipientsElem.appendChild(bccElem);
				}
			}
			mailElem.appendChild(recipientsElem);
		}

		// Reply-To
		if (mail.getReplyTo() != null) {
			InternetAddress replyTo = mail.getReplyTo();
			Element replyToElem = convertInternetAddressIntoElement(replyTo, "replyTo", doc);
			mailElem.appendChild(replyToElem);
		}

		// Subject
		if (mail.getSubject() != null) {
			Element subjectElem = doc.createElement("subject");
			subjectElem.appendChild(doc.createTextNode(mail.getSubject()));
			mailElem.appendChild(subjectElem);
		}

		// Body
		if (mail.getText() != null) {
			Element bodyElem = doc.createElement("body");
			bodyElem.appendChild(doc.createTextNode(mail.getText()));
			mailElem.appendChild(bodyElem);
		}

		// Html
		if (mail.isHtmlMail()) {
			Element htmlElem = doc.createElement("html");
			htmlElem.appendChild(doc.createCDATASection(mail.getHtmlText()));
			mailElem.appendChild(htmlElem);
		}

		doc.appendChild(mailElem);

		return doc;
	}

	public static Document createNewDocument() throws FactoryConfigurationError {
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder db = dbf.newDocumentBuilder();
			Document doc = db.newDocument();
			return doc;
		} catch (ParserConfigurationException e) {
			// never be thrown
			throw new XMLBuildException("", e);
		}
	}

	private Element convertInternetAddressIntoElement(InternetAddress address, String elemName,
														Document doc) {
		Element element = doc.createElement(elemName);
		element.setAttribute("email", address.getAddress());
		if (address.getPersonal() != null) {
			element.setAttribute("name", address.getPersonal());
		}
		return element;
	}

	/**
	 * 指定されたMailインスタンスからXMLドキュメントを生成し、
	 * 指定されたファイルに保存します。
	 * 
	 * このメソッド内部で使用されるTransformerFactoryがスレッドセーフではないため、synchronzedメソッドになっています。
	 * 
	 * @see com.ozacc.mail.xml.XMLBuilder#saveDocument(com.ozacc.mail.Mail, java.io.File)
	 * @see TransformerFactory
	 */
	public synchronized void saveDocument(Mail mail, File destFile) throws XMLBuildException {
		Document doc = buildDocument(mail);

		Transformer t;
		try {
			t = TransformerFactory.newInstance().newTransformer();
		} catch (Exception e) {
			// never be thrown
			throw new XMLBuildException(e.getMessage());
		}
		t.setOutputProperties(getOutputProperties());

		DOMSource source = new DOMSource(doc);
		StreamResult result = new StreamResult(destFile);
		try {
			t.transform(source, result);
		} catch (TransformerException e) {
			throw new XMLBuildException("XMLファイルの保存に失敗しました。", e);
		}
	}

	/**
	 * 出力プロパティを生成。
	 * @return 出力プロパティを設定したPropertiesインスタンス
	 */
	private Properties getOutputProperties() {
		Properties p = new Properties();
		p.put(OutputKeys.ENCODING, charset);
		p.put(OutputKeys.INDENT, "yes");
		p.put(OutputKeys.DOCTYPE_PUBLIC, Mail.DOCTYPE_PUBLIC);
		p.put(OutputKeys.DOCTYPE_SYSTEM, Mail.DOCTYPE_SYSTEM);
		return p;
	}

}