package com.ozacc.mail.impl;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.List;

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.input.SAXBuilder;
import org.jdom.output.XMLOutputter;

import com.ozacc.mail.Mail;
import com.ozacc.mail.MailBuildException;
import com.ozacc.mail.XMLMailBuilder;

/**
 * <a href="http://www.jdom.org/">JDOM</a>を利用してXMLファイルからMailインスタンスを生成するクラス。
 * <p>
 * ソースXMLを読み込む際に、DTDバリデーションが実行されますので妥当なXMLデータ(Valid XML Document)でなければいけません。
 * 
 * @author Tomohiro Otsuka
 * @version $Id: JDomXMLMailBuilder.java,v 1.3 2004/09/05 21:51:44 otsuka Exp $
 */
public class JDomXMLMailBuilder extends XMLMailBuilder {

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

	/**
	 * 指定されたクラスパス上のXMLファイルからMailインスタンスを生成します。
	 * 
	 * @param classPath メール内容を記述したXMLファイルのパス
	 * @return 生成されたMailインスタンス
	 * @throws MailBuildException Mailインスタンスの生成に失敗した場合
	 */
	public Mail buildMail(String classPath) throws MailBuildException {
		Document doc = getDocumentFromClassPath(classPath);
		return build(doc);
	}

	/**
	 * 指定されたクラスパス上のXMLファイルからMailインスタンスを生成します。
	 * 指定されたVelocityContextを使って、XMLファイルの内容を動的に生成できます。
	 * 
	 * @param classPath メール内容を記述したXMLファイルのパス
	 * @param context VelocityContext
	 * @return 生成されたMailインスタンス
	 * @throws MailBuildException Mailインスタンスの生成に失敗した場合
	 */
	public Mail buildMail(String classPath, VelocityContext context) throws MailBuildException {
		Document doc = getDocumentFromClassPath(classPath);
		try {
			return build(doc, context);
		} catch (Exception e) {
			throw new MailBuildException("メールの生成に失敗しました。", e);
		}
	}

	/**
	 * 指定されたXMLファイルからMailインスタンスを生成します。
	 * 
	 * @param file メール内容を記述したXMLファイル
	 * @return 生成されたMailインスタンス
	 * @throws MailBuildException Mailインスタンスの生成に失敗した場合
	 */
	public Mail buildMail(File file) throws MailBuildException {
		Document doc = getDocumentFromFile(file);
		return build(doc);
	}

	/**
	 * 指定されたXMLファイルからMailインスタンスを生成します。
	 * 指定されたVelocityContextを使って、XMLファイルの内容を動的に生成できます。
	 * 
	 * @param file メール内容を記述したXMLファイル
	 * @param context VelocityContext
	 * @return 生成されたMailインスタンス
	 * @throws MailBuildException Mailインスタンスの生成に失敗した場合
	 */
	public Mail buildMail(File file, VelocityContext context) throws MailBuildException {
		Document doc = getDocumentFromFile(file);
		try {
			return build(doc, context);
		} catch (Exception e) {
			throw new MailBuildException("メールの生成に失敗しました。", e);
		}
	}

	/**
	 * 指定されたクラスパス上のファイルを読み込んで、XMLドキュメントを生成します。
	 * 
	 * @param classPath
	 * @return JDOM Document
	 */
	private Document getDocumentFromClassPath(String classPath) throws MailBuildException {
		InputStream is = getClass().getResourceAsStream(classPath);
		SAXBuilder builder = new SAXBuilder(true);
		Document doc;
		try {
			doc = builder.build(is);
			is.close();
		} catch (JDOMException e) {
			throw new MailBuildException("XMLのパースに失敗しました。" + e.getMessage(), e);
		} catch (IOException e) {
			throw new MailBuildException("XMLファイルの読み込みに失敗しました。", e);
		}
		return doc;
	}

	/**
	 * 指定されたファイルを読み込んで、XMLドキュメントを生成します。
	 * 
	 * @param file
	 * @return JDOM Document
	 */
	private Document getDocumentFromFile(File file) {
		SAXBuilder builder = new SAXBuilder(true);
		Document doc;
		try {
			doc = builder.build(file);
		} catch (JDOMException e) {
			throw new MailBuildException("XMLのパースに失敗しました。" + e.getMessage(), e);
		} catch (IOException e) {
			throw new MailBuildException("XMLファイルの読み込みに失敗しました。", e);
		}
		return doc;
	}

	/**
	 * XMLドキュメントからMailインスタンスを生成します。
	 * 
	 * @param doc
	 * @return Mail
	 */
	private Mail build(Document doc) {
		Element root = doc.getRootElement();

		Mail mail = new Mail();
		setFrom(root, mail);
		setRecipients(root, mail);
		setSubject(root, mail);
		setBody(root, mail);
		setReplyTo(root, mail);
		setReturnPath(root, mail);

		return mail;
	}

	/**
	 * VelocityContextとXMLドキュメントをマージさせ、Mailインスタンスを生成します。
	 * 
	 * @param context
	 * @param doc
	 * @return Mail
	 * 
	 * @throws Exception
	 * @throws ParseErrorException
	 * @throws MethodInvocationException
	 * @throws ResourceNotFoundException
	 * @throws IOException
	 * @throws JDOMException 
	 */
	private Mail build(Document doc, VelocityContext context) throws Exception,
																ParseErrorException,
																MethodInvocationException,
																ResourceNotFoundException,
																IOException, JDOMException {
		XMLOutputter output = new XMLOutputter();
		String xmlContent = output.outputString(doc);

		Velocity.init();
		StringWriter w = new StringWriter();
		Velocity.evaluate(context, w, "xmlMail", xmlContent);
		StringReader reader = new StringReader(w.toString());

		SAXBuilder builder = new SAXBuilder(true);
		Document doc2 = builder.build(reader);

		return build(doc2);
	}

	/**
	 * @param root
	 * @param mail 
	 */
	private void setReturnPath(Element root, Mail mail) {
		Element returnPathElem = root.getChild("returnPath");
		if (returnPathElem != null && returnPathElem.getAttributeValue("email") != null) {
			mail.setReturnPath(returnPathElem.getAttributeValue("email"));
		}
	}

	/**
	 * @param root
	 * @param mail 
	 */
	private void setReplyTo(Element root, Mail mail) {
		Element replyToElem = root.getChild("replyTo");
		if (replyToElem != null && replyToElem.getAttributeValue("email") != null) {
			mail.setReplyTo(replyToElem.getAttributeValue("email"));
		}
	}

	/**
	 * @param root
	 * @param mail 
	 */
	private void setBody(Element root, Mail mail) {
		Element bodyElem = root.getChild("body");
		if (bodyElem != null) {
			mail.setText(bodyElem.getTextTrim());
		}
	}

	/**
	 * @param root
	 * @param mail 
	 */
	private void setSubject(Element root, Mail mail) {
		Element subjectElem = root.getChild("subject");
		if (subjectElem != null) {
			mail.setSubject(subjectElem.getTextTrim());
		}
	}

	/**
	 * @param root
	 * @param mail 
	 */
	private void setRecipients(Element root, Mail mail) {
		Element recipientsElem = root.getChild("recipients");
		if (recipientsElem == null) {
			return;
		}

		List recipientElemList = recipientsElem.getChildren();
		for (int i = 0, max = recipientElemList.size(); i < max; i++) {
			Element e = (Element)recipientElemList.get(i);
			if ("to".equals(e.getName())) { // to
				if (e.getAttributeValue("email") != null) {
					if (e.getAttributeValue("name") != null) {
						mail.addTo(e.getAttributeValue("email"), e.getAttributeValue("name"));
					} else {
						mail.addTo(e.getAttributeValue("email"));
					}
				}
			} else if ("cc".equals(e.getName())) { // cc
				if (e.getAttributeValue("email") != null) {
					if (e.getAttributeValue("name") != null) {
						mail.addCc(e.getAttributeValue("email"), e.getAttributeValue("name"));
					} else {
						mail.addCc(e.getAttributeValue("email"));
					}
				}
			} else {
				if (e.getAttributeValue("email") != null) { // bcc
					mail.addBcc(e.getAttributeValue("email"));
				}
			}
		}
	}

	/**
	 * @param root
	 * @param mail 
	 */
	private void setFrom(Element root, Mail mail) {
		Element fromElem = root.getChild("from");
		if (fromElem != null && fromElem.getAttributeValue("email") != null) {
			if (fromElem.getAttributeValue("name") != null) {
				mail.setFrom(fromElem.getAttributeValue("email"), fromElem
						.getAttributeValue("name"));
			} else {
				mail.setFrom(fromElem.getAttributeValue("email"));
			}
		}
	}

}