package com.ozacc.mail;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;

/**
 * メール。
 * 
 * @author Tomohiro Otsuka
 * @version $Id: Mail.java,v 1.4 2004/09/10 22:41:42 otsuka Exp $
 */
public class Mail {

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

	private String charset = JIS_CHARSET;

	private String text;

	private InternetAddress from;

	private String subject;

	private List to;

	private List cc;

	private List bcc;

	private InternetAddress returnPath;

	private InternetAddress replyTo;

	private String importance;

	private Map xHeaders;

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

	/**
	 * コンストラクタ。
	 * 宛先や差出人の名前をエンコードする時に使用する文字コードを指定します。
	 * デフォルトは<code>ISO-2022-JP</code>です。
	 * <p>
	 * 日本語環境で利用する場合は通常変更する必要はありません。
	 * 
	 * @param charset エンコードに使用する文字コード
	 */
	public Mail(String charset) {
		this();
		this.charset = charset;
	}

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

	/**
	 * メールの重要度をセットします。
	 * 引数で指定可能な値は「high」、「normal」、「low」のいずれかです。
	 * 
	 * @param importance メールの重要度。「high」、「normal」、「low」のいずれか。
	 * @throws IllegalArgumentException 指定可能な値以外が指定された場合
	 * 
	 * @see Mail.Importance
	 */
	public void setImportance(String importance) throws IllegalArgumentException {
		if ("high".equals(importance) || "normal".equals(importance) || "low".equals(importance)) {
			this.importance = importance;
		} else {
			throw new IllegalArgumentException("'" + importance + "'は、メール重要度には指定できない値です。");
		}
	}

	/**
	 * メールの重要度を返します。
	 * 値は「high」、「normal」、「low」のいずれかです。
	 * 
	 * @return メールの重要度。「high」、「normal」、「low」のいずれか。
	 */
	public String getImportance() {
		return importance;
	}

	/**
	 * メールの送信先アドレスを追加します。
	 * 
	 * @param address 送信先アドレス
	 */
	public void addTo(InternetAddress address) {
		if (to == null) {
			to = new ArrayList();
		}
		to.add(address);
	}

	/**
	 * メールの送信先アドレスを追加します。
	 * 
	 * @param email 送信先アドレス
	 * @throws IllegalArgumentException 不正なフォーマットのアドレスが指定された場合
	 */
	public void addTo(String email) throws IllegalArgumentException {
		try {
			addTo(new InternetAddress(email));
		} catch (AddressException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/**
	 * メールの送信先名とアドレスを追加します。
	 * 名前はJIS_CHARSETでエンコードされます。
	 * 
	 * @param email 送信先アドレス
	 * @param name 送信先名
	 * @throws IllegalArgumentException 不正なフォーマットのアドレスが指定された場合
	 */
	public void addTo(String email, String name) throws IllegalArgumentException {
		try {
			addTo(new InternetAddress(email, name, charset));
		} catch (UnsupportedEncodingException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/**
	 * メールの送信先アドレスの配列を返します。
	 * 送信先アドレスが一件もセットされていないときは空の配列を返します。
	 * 
	 * @return 送信先アドレスの配列
	 */
	public InternetAddress[] getTo() {
		if (to == null) {
			return new InternetAddress[0];
		}
		return (InternetAddress[])to.toArray(new InternetAddress[to.size()]);
	}

	/**
	 * CCアドレスを追加します。
	 * 
	 * @param address CCのアドレス
	 */
	public void addCc(InternetAddress address) {
		if (cc == null) {
			cc = new ArrayList();
		}
		cc.add(address);
	}

	/**
	 * CCアドレスを追加します。
	 * 
	 * @param email CCのアドレス
	 * @throws IllegalArgumentException 不正なフォーマットのアドレスが指定された場合
	 */
	public void addCc(String email) throws IllegalArgumentException {
		try {
			addCc(new InternetAddress(email));
		} catch (AddressException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/**
	 * CCの宛名とアドレスを追加します。
	 * 名前はJIS_CHARSETでエンコードされます。
	 * 
	 * @param email CCのアドレス
	 * @param name CCの宛名
	 * @throws IllegalArgumentException 不正なフォーマットのアドレスが指定された場合
	 */
	public void addCc(String email, String name) throws IllegalArgumentException {
		try {
			addCc(new InternetAddress(email, name, charset));
		} catch (UnsupportedEncodingException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/**
	 * メールのCCアドレス配列を返します。
	 * CCアドレスが一件もセットされていないときは空の配列を返します。
	 * 
	 * @return CCアドレスの配列
	 */
	public InternetAddress[] getCc() {
		if (cc == null) {
			return new InternetAddress[0];
		}
		return (InternetAddress[])cc.toArray(new InternetAddress[cc.size()]);
	}

	/**
	 * BCCアドレスを追加します。
	 * 
	 * @param address BCCのアドレス
	 */
	public void addBcc(InternetAddress address) {
		if (bcc == null) {
			bcc = new ArrayList();
		}
		bcc.add(address);
	}

	/**
	 * BCCアドレスを追加します。
	 * 
	 * @param email BCCのアドレス
	 * @throws IllegalArgumentException 不正なフォーマットのアドレスが指定された場合
	 */
	public void addBcc(String email) throws IllegalArgumentException {
		try {
			addBcc(new InternetAddress(email));
		} catch (AddressException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/**
	 * メールのBCCアドレスの配列を返します。
	 * BCCアドレスが一件もセットされていないときは空の配列を返します。
	 * 
	 * @return BCCアドレスの配列
	 */
	public InternetAddress[] getBcc() {
		if (bcc == null) {
			return new InternetAddress[0];
		}
		return (InternetAddress[])bcc.toArray(new InternetAddress[bcc.size()]);
	}

	/**
	 * メールの差出人アドレスをセットします。
	 * 
	 * @param address 差出人アドレス
	 */
	public void setFrom(InternetAddress address) {
		from = address;
	}

	/**
	 * メールの差出人アドレスをセットします。
	 * 
	 * @param email 差出人アドレス
	 * @throws IllegalArgumentException 不正なフォーマットのアドレスが指定された場合
	 */
	public void setFrom(String email) throws IllegalArgumentException {
		try {
			setFrom(new InternetAddress(email));
		} catch (AddressException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/**
	 * メールの差出人名とアドレスをセットします。
	 * 名前はJIS_CHARSETでエンコードされます。
	 * 
	 * @param email 差出人アドレス
	 * @param name 差出人名
	 * @throws IllegalArgumentException 不正なフォーマットのアドレスが指定された場合
	 */
	public void setFrom(String email, String name) throws IllegalArgumentException {
		try {
			setFrom(new InternetAddress(email, name, charset));
		} catch (UnsupportedEncodingException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/**
	 * メールの差出人アドレスを返します。セットされていない場合はnullを返します。
	 * 
	 * @return メールの差出人アドレス
	 */
	public InternetAddress getFrom() {
		return from;
	}

	/**
	 * Return-Pathアドレスをセットします。
	 * 
	 * @param address Return-Pathアドレス
	 */
	public void setReturnPath(InternetAddress address) {
		returnPath = address;
	}

	/**
	 * Return-Pathアドレスをセットします。
	 * 
	 * @param email Return-Pathアドレス
	 * @throws IllegalArgumentException 不正なフォーマットのアドレスが指定された場合
	 */
	public void setReturnPath(String email) throws IllegalArgumentException {
		try {
			setReturnPath(new InternetAddress(email));
		} catch (AddressException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/**
	 * Return-Pathアドレスを返します。
	 * 
	 * @return Return-Pathアドレス
	 */
	public InternetAddress getReturnPath() {
		return returnPath;
	}

	/**
	 * 返信先アドレスをセットします。
	 * 
	 * @param address 返信先アドレス
	 */
	public void setReplyTo(InternetAddress address) {
		replyTo = address;
	}

	/**
	 * 返信先アドレスをセットします。
	 * 
	 * @param email 返信先アドレス
	 * @throws IllegalArgumentException 不正なフォーマットのアドレスが指定された場合
	 */
	public void setReplyTo(String email) throws IllegalArgumentException {
		try {
			setReplyTo(new InternetAddress(email));
		} catch (AddressException e) {
			throw new IllegalArgumentException(e.getMessage());
		}
	}

	/**
	 * メールの返信先アドレスを返します。セットされていない場合はnullを返します。
	 * 
	 * @return 返信先アドレス
	 */
	public InternetAddress getReplyTo() {
		return replyTo;
	}

	/**
	 * メールの件名を返します。セットされていない場合は空文字列を返します。
	 * 
	 * @return メールの件名
	 */
	public String getSubject() {
		if (subject == null) {
			return "";
		}
		return subject;
	}

	/**
	 * メールの件名をセットします。
	 * 
	 * @param subject メールの件名
	 */
	public void setSubject(String subject) {
		this.subject = subject;
	}

	/**
	 * メール本文を返します。
	 * 本文セットされていない場合は空文字列を返します。
	 * 
	 * @return メール本文
	 */
	public String getText() {
		if (text == null) {
			return "";
		}
		return text;
	}

	/**
	 * メール本文をセットします。
	 * 
	 * @param text メール本文
	 */
	public void setText(String text) {
		this.text = text;
	}

	/**
	 * メールヘッダに任意のヘッダを追加します。
	 * 任意ヘッダは「X-key: value」のフォーマットでメールヘッダに組み込まれます。
	 *  
	 * @param key 任意ヘッダ名。頭が"X-"で始まっていなければ、自動的に付与されます。
	 * @param value 任意ヘッダの値
	 */
	public void addXHeader(String key, String value) {
		if (xHeaders == null) {
			xHeaders = new HashMap();
		}
		if (key.startsWith("X-")) {
			xHeaders.put(key, value);
		} else {
			xHeaders.put("X-" + key, value);
		}
	}

	/**
	 * メールの任意ヘッダ名と値のMapインスタンスを返します。
	 * 任意ヘッダが一件もセットされていないときはnullを返します。
	 * <p>
	 * このMapインスタンスへの修正はできません。(unmodifiableMapになっています。)
	 * 
	 * @return メールの任意ヘッダ名と値のMapインスタンス。またはnull。
	 */
	public Map getXHeaders() {
		if (xHeaders == null) {
			return null;
		}
		return Collections.unmodifiableMap(xHeaders);
	}

	/**
	 * メール内容を出力します。<br>
	 * メールのソースに似たフォーマットで出力されます。
	 * 
	 * @see java.lang.Object#toString()
	 */
	public String toString() {
		StringBuffer buf = new StringBuffer(1000);
		buf.append("Mail\n");
		buf.append("Return-Path: ").append(returnPath).append("\n");
		buf.append("From: ").append(from != null ? from.toUnicodeString() : null).append("\n");
		buf.append("To: ").append(arrayToCommaDelimitedString(to)).append("\n");
		buf.append("Cc: ").append(arrayToCommaDelimitedString(cc)).append("\n");
		buf.append("Bcc: ").append(arrayToCommaDelimitedString(bcc)).append("\n");
		buf.append("Subject: ").append(subject).append("\n");

		if (xHeaders != null) {
			for (Iterator itr = xHeaders.keySet().iterator(); itr.hasNext();) {
				String header = (String)itr.next();
				String value = (String)xHeaders.get(header);
				buf.append(header).append(": ").append(value).append("\n");
			}
		}

		buf.append("\n");
		buf.append(text);

		return buf.toString();
	}

	/**
	 * @param list
	 * @return 
	 */
	private String arrayToCommaDelimitedString(List list) {
		if (list == null) {
			return "null";
		} else {
			StringBuffer sb = new StringBuffer();
			for (int i = 0, num = list.size(); i < num; i++) {
				if (i > 0) {
					sb.append(", ");
				}
				sb.append(((InternetAddress)list.get(i)).toUnicodeString());
			}
			return sb.toString();
		}
	}

	/**
	 * メールの重要度。定数のみを定義。
	 * 
	 * @author Tomohiro Otsuka
	 * @version $Id: Mail.java,v 1.4 2004/09/10 22:41:42 otsuka Exp $
	 */
	public static class Importance {

		/** 重要度「高」 */
		public static final String HIGH = "high";

		/** 重要度「中」 */
		public static final String NORMAL = "normal";

		/** 重要度「低」 */
		public static final String LOW = "low";

	}
}