package com.ozacc.mail.impl;

import java.io.UnsupportedEncodingException;
import java.util.Date;
import java.util.Properties;

import javax.mail.AuthenticationFailedException;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.MimeMessage;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.ozacc.mail.Mail;
import com.ozacc.mail.MailAuthenticationException;
import com.ozacc.mail.MailBuildException;
import com.ozacc.mail.MailException;
import com.ozacc.mail.MailSendException;
import com.ozacc.mail.SendMail;

/**
 * SendMailインターフェースの実装クラス。
 * 
 * @author Tomohiro Otsuka
 * @version $Id: SendMailImpl.java,v 1.5 2004/09/05 16:16:23 otsuka Exp $
 */
public class SendMailImpl implements SendMail {

	private static Log log = LogFactory.getLog(SendMailImpl.class);

	/** デフォルトのプロトコル。「smtp」 */
	public static final String DEFAULT_PROTOCOL = "smtp";

	/**
	 * デフォルトのポート。「-1」<br>
	 * -1はプロトコルに応じた適切なポートを設定する特別な値。
	 * */
	public static final int DEFAULT_PORT = -1;

	/** デフォルトのSMTPサーバ。「localhost」 */
	public static final String DEFAULT_HOST = "localhost";

	/** ISO-2022-JP */
	public static final String JIS_CHARSET = "ISO-2022-JP";

	private static final String RETURN_PATH_KEY = "mail.smtp.from";

	private String protocol = DEFAULT_PROTOCOL;

	private String host = DEFAULT_HOST;

	private int port = DEFAULT_PORT;

	private String username;

	private String password;

	private String charset = JIS_CHARSET;

	private String returnPath;

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

	/**
	 * コンストラクタ。使用するSMTPサーバを指定します。
	 * 
	 * @param host SMTPサーバのホスト名、またはIPアドレス
	 */
	public SendMailImpl(String host) {
		this();
		setHost(host);
	}

	/**
	 * @see com.ozacc.mail.SendMail#send(com.ozacc.mail.Mail)
	 */
	public void send(Mail mail) throws MailException {
		send(new Mail[] { mail });
	}

	/**
	 * @see com.ozacc.mail.SendMail#send(com.ozacc.mail.Mail[])
	 */
	public void send(Mail[] mails) throws MailException {
		MimeMessageWrapper[] mmws = new MimeMessageWrapper[mails.length];
		Session session = Session.getInstance(new Properties());
		for (int i = 0; i < mails.length; i++) {
			Mail mail = mails[i];

			// MimeMessageを生成
			MimeMessage message = createMimeMessage(session);
			MimeMessageBuilder builder = new MimeMessageBuilder(message);
			try {
				builder.buildMimeMessage(mail);
			} catch (UnsupportedEncodingException e) {
				throw new MailBuildException("サポートされていない文字コードが指定されました。", e);
			} catch (MessagingException e) {
				throw new MailBuildException("MimeMessageの生成に失敗しました。", e);
			}

			// Return-Pathを取得
			String returnPath;
			if (mail.getReturnPath() != null) {
				returnPath = mail.getReturnPath().getAddress();
			} else {
				returnPath = this.returnPath;
			}

			mmws[i] = new MimeMessageWrapper(message, returnPath);
		}
		processSend(mmws);
	}

	/**
	 * @see com.ozacc.mail.SendMail#send(javax.mail.internet.MimeMessage)
	 */
	public void send(MimeMessage message) throws MailException {
		send(new MimeMessage[] { message });
	}

	/**
	 * @see com.ozacc.mail.SendMail#send(javax.mail.internet.MimeMessage[])
	 */
	public void send(MimeMessage[] messages) throws MailException {
		MimeMessageWrapper[] mmws = new MimeMessageWrapper[messages.length];
		for (int i = 0; i < messages.length; i++) {
			mmws[i] = new MimeMessageWrapper(messages[i], returnPath);
		}
		processSend(mmws);
	}

	private void processSend(MimeMessageWrapper[] mmws) throws MailException {

		Session session = Session.getInstance(new Properties());

		Transport transport = null;
		try {
			// SMTPサーバに接続
			log.debug("SMTPサーバ[" + host + "]に接続します。");
			transport = session.getTransport(protocol);
			transport.connect(host, port, username, password);
			log.debug("SMTPサーバ[" + host + "]に接続しました。");

			for (int i = 0; i < mmws.length; i++) {
				MimeMessage mimeMessage = mmws[i].getMimeMessage();
				String returnPath = mmws[i].getReturnPath();

				// Return-Pathをセット
				if (returnPath != null) {
					session.getProperties().put(RETURN_PATH_KEY, returnPath);
					log.debug("Return-Path[" + returnPath + "]を設定しました。");
				}

				// 送信日時をセット
				mimeMessage.setSentDate(new Date());
				mimeMessage.saveChanges();
				// 送信
				log.debug("メールを送信します。");
				transport.sendMessage(mimeMessage, mimeMessage.getAllRecipients());
				log.debug("メールを送信しました。");

				// Return-Pathを解除
				if (returnPath != null) {
					session.getProperties().remove(RETURN_PATH_KEY);
					log.debug("Return-Path設定をクリアしました。");
				}
			}
		} catch (AuthenticationFailedException ex) {
			log.error("SMTPサーバ[" + host + "]への接続認証に失敗しました。", ex);
			throw new MailAuthenticationException(ex);
		} catch (MessagingException ex) {
			log.error("メールの送信に失敗しました。", ex);
			throw new MailSendException("メールの送信に失敗しました。", ex);
		} finally {
			if (transport != null && transport.isConnected()) {
				log.debug("SMTPサーバ[" + host + "]との接続を切断します。");
				try {
					// SMTPサーバとの接続を切断
					transport.close();
				} catch (MessagingException e) {
					log.error("SMTPサーバ[" + host + "]との接続切断に失敗しました。", e);
					throw new MailException("SMTPサーバ[" + host + "]との接続切断に失敗しました。");
				}
				log.debug("SMTPサーバ[" + host + "]との接続を切断しました。");
			}
		}
	}

	/**
	 * 新しいMimeMessageオブジェクトを生成します。
	 * 
	 * @return 新しいMimeMessageオブジェクト
	 */
	private MimeMessage createMimeMessage(Session session) {
		return new MimeMessage(session);
	}

	/**
	 * エンコーディングに使用する文字コードを返します。
	 * 
	 * @return エンコーディングに使用する文字コード
	 */
	public String getCharset() {
		return charset;
	}

	/**
	 * メールの件名や本文のエンコーディングに使用する文字コードを指定します。
	 * デフォルトは<code>ISO-2022-JP</code>です。
	 * <p>
	 * 日本語環境で利用する場合は通常変更する必要はありません。
	 * 
	 * @param charset エンコーディングに使用する文字コード
	 */
	public void setCharset(String charset) {
		this.charset = charset;
	}

	/**
	 * セットされたSMTPサーバのホスト名、またはIPアドレスを返します。
	 * 
	 * @return SMTPサーバのホスト名、またはIPアドレス
	 */
	public String getHost() {
		return host;
	}

	/**
	 * SMTPサーバのホスト名、またはIPアドレスをセットします。
	 * デフォルトは localhost です。
	 * 
	 * @param host SMTPサーバのホスト名、またはIPアドレス
	 */
	public void setHost(String host) {
		this.host = host;
	}

	/**
	 * @return SMTPサーバ認証パスワード
	 */
	public String getPassword() {
		return password;
	}

	/**
	 * SMTPサーバの接続認証が必要な場合にパスワードをセットします。
	 * 
	 * @param password SMTPサーバ認証パスワード
	 */
	public void setPassword(String password) {
		this.password = password;
	}

	/**
	 * @return SMTPサーバのポート番号
	 */
	public int getPort() {
		return port;
	}

	/**
	 * SMTPサーバのポート番号をセットします。
	 * 
	 * @param port SMTPサーバのポート番号
	 */
	public void setPort(int port) {
		this.port = port;
	}

	/**
	 * @return Returns the protocol.
	 */
	public String getProtocol() {
		return protocol;
	}

	/**
	 * @param protocol The protocol to set.
	 */
	public void setProtocol(String protocol) {
		this.protocol = protocol;
	}

	/**
	 * @return Return-Pathアドレス
	 */
	public String getReturnPath() {
		return returnPath;
	}

	/**
	 * Return-Pathアドレスをセットします。
	 * <p>
	 * 送信するMailインスタンスに指定されたFromアドレス以外のアドレスをReturn-Pathとしたい場合に使用します。
	 * ここでセットされたReturn-Pathより、MailインスタンスにセットされたReturn-Pathが優先されます。
	 * 
	 * @param returnPath Return-Pathアドレス
	 */
	public void setReturnPath(String returnPath) {
		this.returnPath = returnPath;
	}

	/**
	 * @return SMTPサーバ認証ユーザ名
	 */
	public String getUsername() {
		return username;
	}

	/**
	 * SMTPサーバの接続認証が必要な場合にユーザ名をセットします。
	 * 
	 * @param username SMTPサーバ認証ユーザ名
	 */
	public void setUsername(String username) {
		this.username = username;
	}

	/**
	 * MimeMessageインスタンスと、そのメールに対応するReturn-Pathをラップするクラス。
	 * 
	 * @author Tomohiro Otsuka
	 * @version $Id: SendMailImpl.java,v 1.5 2004/09/05 16:16:23 otsuka Exp $
	 */
	private static class MimeMessageWrapper {

		private MimeMessage mimeMessage;

		private String returnPath;

		public MimeMessageWrapper(MimeMessage mimeMessage, String returnPath) {
			this.mimeMessage = mimeMessage;
			this.returnPath = returnPath;
		}

		public MimeMessage getMimeMessage() {
			return mimeMessage;
		}

		public String getReturnPath() {
			return returnPath;
		}

	}

}