package jp.co.headwaters.webappos.common;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

import org.apache.commons.lang3.StringUtils;

public class JavaMailer {
	protected final Pattern ADDRESS_PATTERN = Pattern.compile("(.+)?<(.+)>");

	protected String charset = "ISO-2022-JP";

	protected String host;
	protected String port;
	protected String username;
	protected String password;
	protected Integer connectiontimeout;
	protected Integer timeout;

	protected InternetAddress from;
	protected InternetAddress[] to;
	protected InternetAddress[] cc;
	protected InternetAddress[] bcc;
	protected String subject;
	protected String body;

	/**
	 * 文字セットを設定します。デフォルトはISO-2022-JP
	 * @param charset 文字セットの識別名
	 */
	public void setCharset(String charset) {
		this.charset = charset;
	}

	/**
	 * サーバ情報を設定します
	 *
	 * @param host ホスト名
	 * @param port ポート名
	 * @param username ユーザ名。NULLまたは空白の場合は認証を行わない
	 * @param password パスワード。NULLまたは空白の場合は認証を行わない
	 */
	public void setServerInfo(String host, String port, String username, String password) {
		this.host = host;
		this.port = port;
		this.username = username;
		this.password = password;
	}

	/**
	 * タイムアウトを設定します
	 *
	 * @param connectionTimeout 接続時のタイムアウトミリ秒。NULLの場合タイムアウト判定を行わない
	 * @param sendTimeout サーバへの送受信時のタイムアウトミリ秒。NULLの場合タイムアウト判定を行わない
	 */
	public void setTimeout(Integer connectionTimeout, Integer sendTimeout) {
		connectiontimeout = connectionTimeout;
		timeout = sendTimeout;
	}

	/**
	 * Fromを設定します
	 * @param address 送信者アドレス
	 * @param name 送信者名
	 * @throws AddressException
	 */
	public void setFrom(String address, String name) throws UnsupportedEncodingException, AddressException {
		if (name != null && !"".equals(name)) {
			from = new InternetAddress(address, name, charset);
		} else {
			from = new InternetAddress(address);
		}
	}

	/**
	 * Toを追加します
	 * @param addresses 送信先アドレス<br>
	 * 表示名を指定する際は「表示名<アドレス>」の形式<br>
	 * 複数を一度に登録する際はカンマ(,)区切り
	 * @throws UnsupportedEncodingException
	 * @throws AddressException
	 */
	public void addTo(String addresses) throws AddressException, UnsupportedEncodingException {
		to = addInternetAddress(to, addresses);
	}

	/**
	 * Ccを追加します
	 * @param addresses ccアドレス<br>
	 * 表示名を指定する際は「表示名<アドレス>」の形式<br>
	 * 複数を一度に登録する際はカンマ(,)区切り
	 * @throws UnsupportedEncodingException
	 * @throws AddressException
	 */
	public void addCc(String addresses) throws AddressException, UnsupportedEncodingException {
		cc = addInternetAddress(cc, addresses);
	}

	/**
	 * BCcを追加します
	 * @param addresses bccアドレス<br>
	 * 表示名を指定する際は「表示名<アドレス>」の形式<br>
	 * 複数を一度に登録する際はカンマ(,)区切り
	 * @throws UnsupportedEncodingException
	 * @throws AddressException
	 */
	public void addBcc(String addresses) throws AddressException, UnsupportedEncodingException {
		bcc = addInternetAddress(bcc, addresses);
	}

	/**
	 * 題名を設定します
	 * @param subject 題名
	 */
	public void setSubject(String subject) {
		this.subject = subject;
	}

	/**
	 * 本文を設定します
	 * @param body 本文
	 */
	public void setBody(String body) {
		this.body = body;
	}

	/**
	 * プロパティの値に基づきメールを送信します
	 *
	 * @throws MessagingException
	 * @throws UnsupportedEncodingException
	 */
	public void send() throws MessagingException, UnsupportedEncodingException {
		Properties props = new Properties();
		props.setProperty("mail.smtp.host", host);
		props.setProperty("mail.smtp.port", port);
		if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
			props.setProperty("mail.smtp.auth", "true");
		}
		if (connectiontimeout != null) {
			props.put( "mail.smtp.connectiontimeout", connectiontimeout);
		}
		if (timeout != null) {
			props.put( "mail.smtp.timeout", timeout);
		}
//		props.put("mail.smtp.socketFactory.class", "javax.net.ssl.SSLSocketFactory");  // for ssl
//		props.put("mail.smtp.socketFactory.fallback", "false");  // for ssl
//		props.put("mail.smtp.socketFactory.port", port);      // for ssl

		Session session = Session.getDefaultInstance(props);
		MimeMessage message = new MimeMessage(session);

		try {
			message.setRecipients(MimeMessage.RecipientType.TO, to);
			if (cc != null) {
				message.setRecipients(MimeMessage.RecipientType.CC, cc);
			}
			if (bcc != null) {
				message.setRecipients(MimeMessage.RecipientType.BCC, bcc);
			}
			if (from != null) {
				message.setFrom(from);
			}
			message.setSubject(subject, charset);
			message.setText(body, charset);

			// 設定の保存
			message.saveChanges();

			// 送信
			if (!StringUtils.isEmpty(username) && !StringUtils.isEmpty(password)) {
				Transport transport = session.getTransport("smtp");
				transport.connect(username, password);
				transport.sendMessage(message, message.getAllRecipients());
				transport.close();
			} else {
				Transport.send(message);
				message.writeTo(System.out);
			}
		} catch (MessagingException e) {
			e.printStackTrace();
			throw e;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	/**
	 * InternetAddress配列にアドレスを追加します
	 *
	 * @param prop 追加対象のプロパティ
	 * @param address 追加アドレス<br>
	 * 表示名を指定する際は<>で囲む<br>
	 * 複数を一度に登録する際はカンマ(,)区切り
	 * @return
	 * @throws AddressException
	 * @throws UnsupportedEncodingException
	 */
	private InternetAddress[] addInternetAddress(InternetAddress[] prop, String addresses)
			throws AddressException, UnsupportedEncodingException {
		List<InternetAddress> tmpAddress = new ArrayList<InternetAddress>();
		if (prop != null && prop.length > 0) {
			tmpAddress.addAll(Arrays.asList(prop));
		} else {
			prop = new InternetAddress[1];
		}
		String[] array = addresses.split(",");

		for (String one : array) {
			Matcher m = ADDRESS_PATTERN.matcher(one);
			if (m.find()) {
				tmpAddress.add(new InternetAddress(m.group(2).trim(), m.group(1).trim(), charset));
			} else {
				tmpAddress.add(new InternetAddress(one.trim()));
			}
		}

		return tmpAddress.toArray(prop);
	}
}
