/*
 * Copyright (c) 2009 The openGion Project.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.opengion.fukurou.mail;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.ByteArrayInputStream;
import java.io.UnsupportedEncodingException;
import java.io.IOException;

import javax.activation.DataHandler;
import javax.activation.DataSource;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeUtility;
import javax.mail.MessagingException;
import com.sun.mail.util.BASE64EncoderStream;
import java.nio.charset.Charset;						// 5.5.2.6 (2012/05/25)

import org.opengion.fukurou.system.OgRuntimeException ;		// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.util.UnicodeCorrecter;		// 5.9.3.3 (2015/12/26) package を、mail → util に移動のため

/**
 * MailCharsetFactory は、MailCharset インターフェースを実装したサブクラスを
 * 作成する ファクトリクラスです。
 *
 * 引数のキャラクタセット名が、Windows-31J 、MS932 の場合は、
 * <del>6.3.8.0 (2015/09/11) 『１．Windows-31J + 8bit 送信』 の実装である、Mail_Windows31J_Charset</del>
 * 『１．Windows-31J/UTF-8 + 8bit 送信』 の実装である、Mail_8bit_Charset
 * サブクラスを返します。
 * それ以外が指定された場合は、ISO-2022-JP を使用して、『２．ISO-2022-JP に独自変換 + 7bit 送信』
 * の実装である、Mail_ISO2022JP_Charset サブクラスを返します。
 *
 * @og.rev 6.3.8.0 (2015/09/11) キャラクタセットに、UTF-8 を追加します。
 *  Mail_Windows31J_Charset のクラス名を変更します。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
class MailCharsetFactory {

	/**
	 * インスタンスの生成を抑止します。
	 */
	private MailCharsetFactory() {
		// 何もありません。(PMD エラー回避)
	}

	/**
	 * キャラクタセットに応じた、MailCharset オブジェクトを返します。
	 *
	 * Windows-31J 、MS932 、Shift_JIS の場合は、Mail_Windows31J_Charset
	 * その他は、ISO-2022-JP として、Mail_ISO2022JP_Charset を返します。
	 *
	 * 注意：null の場合は、デフォルトではなく、Mail_ISO2022JP_Charset を返します。
	 *
	 * @og.rev 6.3.8.0 (2015/09/11) キャラクタセットに、UTF-8 を追加します。
	 *
	 * @param  charset キャラクタセット[Windows-31J/MS932/Shift_JIS/その他]
	 *
	 * @return MailCharsetオブジェクト
	 */
	/* default */ static MailCharset newInstance( final String charset ) {
		final MailCharset mcset;

		if( "MS932".equalsIgnoreCase( charset ) ||
			"Shift_JIS".equalsIgnoreCase( charset ) ||
			"Windows-31J".equalsIgnoreCase( charset ) ||
			"UTF-8".equalsIgnoreCase( charset ) ) {					// 6.3.8.0 (2015/09/11)
				mcset = new Mail_8bit_Charset( charset );			// 6.3.8.0 (2015/09/11)
		}
		else {
			mcset = new Mail_ISO2022JP_Charset();
		}
		return mcset ;
	}

	/**
	 * MailCharset インターフェースを実装した Windwos-31J/UTF-8 エンコード時のサブクラスです。
	 *
	 * 『１．Windows-31J/UTF-8 + 8bit 送信』 の実装です。
	 *
	 * @og.rev 6.3.8.0 (2015/09/11) キャラクタセットに、UTF-8 を追加します。
	 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	private static final class Mail_8bit_Charset implements MailCharset {
		private final String charset ;			// "Windows-31J" or "MS932"

		/**
		 * 引数に、エンコード方式を指定して、作成するコンストラクタです。
		 *
		 * @og.rev 6.3.8.0 (2015/09/11) キャラクタセットに、UTF-8 を追加します。
		 *
		 * @param charset エンコード
		 */
		public Mail_8bit_Charset( final String charset ) {
			this.charset = charset;
		}

		/**
		 * テキストをセットします。
		 * Part#setText() の代わりにこちらを使うようにします。
		 *
		 * ※ 内部で、MessagingException が発生した場合は、RuntimeException に変換されて throw されます。
		 *
		 * @param mimeMsg MimeMessageオブジェクト
		 * @param text    テキスト
		 */
		public void setTextContent( final MimeMessage mimeMsg, final String text ) {
			try {
				mimeMsg.setText( text,charset );		// "text/plain" Content
			}
			catch( final MessagingException ex ) {
				final String errMsg = "指定のテキストをセットできません。"
										+ "text=" + text + " , charset=" + charset ;
				throw new OgRuntimeException( errMsg,ex );
			}
		}

		/**
		 * 日本語を含むヘッダ用テキストを生成します。
		 * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress
		 * のパラメタとして使用してください。
		 *
		 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
		 *
		 * @param text    テキスト
		 *
		 * @return	日本語を含むヘッダ用テキスト
		 * @og.rtnNotNull
		 */
		public String encodeWord( final String text ) {
			try {
				return MimeUtility.encodeText( text, charset, "B" );
			}
			catch( final UnsupportedEncodingException ex ) {
				final String errMsg = "指定のエンコードが出来ません。"
										+ "text=" + text + " , charset=" + charset ;
				throw new OgRuntimeException( errMsg,ex );
			}
		}

		/**
		 * 日本語を含むアドレスを生成します。
		 * personal に、日本語が含まれると想定しています。
		 * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。
		 *
		 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
		 *
		 * @param address    RFC822形式のアドレス
		 * @param personal   個人名
		 *
		 * @return InternetAddressオブジェクト
		 * @og.rtnNotNull
		 */
		public InternetAddress getAddress( final String address,final String personal ) {
			try {
				return new InternetAddress( address,personal,charset );
			}
			catch( final UnsupportedEncodingException ex ) {
				final String errMsg = "指定のエンコードが出来ません。"
										+ "address=" + address + " , charset=" + charset ;
				throw new OgRuntimeException( errMsg,ex );
			}
		}

		/**
		 * Content-Transfer-Encoding を指定する場合の ビット数を返します。
		 *
		 * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。
		 *
		 * @return	ビット数("8bit" 固定)
		 * @og.rtnNotNull
		 */
		public String getBit() {
			return "8bit" ;
		}
	}

	/**
	 * MailCharset インターフェースを実装した ISO-2022-JP エンコード時のサブクラスです。
	 *
	 * 『２．ISO-2022-JP に独自変換 + 7bit 送信』 の実装です。
	 *
	 *
	 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	private static final class Mail_ISO2022JP_Charset implements MailCharset {

		/**
		 * プラットフォーム依存のデフォルトの Charset です。
		 * プラットフォーム依存性を考慮する場合、エンコード指定で作成しておく事をお勧めします。
		 *
		 * @og.rev 5.5.2.6 (2012/05/25) findbugs対応
		 */
		private static final Charset DEFAULT_CHARSET = Charset.defaultCharset() ;

		/**
		 * テキストをセットします。
		 * Part#setText() の代わりにこちらを使うようにします。
		 *
		 * ※ 内部で、MessagingException が発生した場合は、RuntimeException に変換されて throw されます。
		 *
		 * @param mimeMsg MimeMessageオブジェクト
		 * @param text    テキスト
		 */
		public void setTextContent( final MimeMessage mimeMsg, final String text ) {
			try {
				// mimeMsg.setText(text, "ISO-2022-JP");
				mimeMsg.setDataHandler(new DataHandler(new JISDataSource(text)));
			}
			catch( final MessagingException ex ) {
				final String errMsg = "指定のテキストをセットできません。"
										+ "text=" + text ;
				throw new OgRuntimeException( errMsg,ex );
			}
		}

		/**
		 * 日本語を含むヘッダ用テキストを生成します。
		 * 変換結果は ASCII なので、これをそのまま setSubject や InternetAddress
		 * のパラメタとして使用してください。
		 *
		 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
		 *
		 * @param text    テキスト
		 *
		 * @return	日本語を含むヘッダ用テキスト
		 * @og.rtnNotNull
		 */
		public String encodeWord( final String text ) {
			try {
				return "=?ISO-2022-JP?B?" +
					new String(
						BASE64EncoderStream.encode(
							CharCodeConverter.sjisToJis(
								UnicodeCorrecter.correctToCP932(text).getBytes("Windows-31J")
							)
						)
					,DEFAULT_CHARSET ) + "?=";		// 5.5.2.6 (2012/05/25) findbugs対応
			}
			catch( final UnsupportedEncodingException ex ) {
				final String errMsg = "指定のエンコードが出来ません。"
									+ "text=" + text + " , charset=Windows-31J" ;
				throw new OgRuntimeException( errMsg,ex );
			}
		}

		/**
		 * 日本語を含むアドレスを生成します。
		 * personal に、日本語が含まれると想定しています。
		 * サブクラスで、日本語処理を行う場合の方法は、それぞれ異なります。
		 *
		 * ※ 内部で、UnsupportedEncodingException が発生した場合は、RuntimeException に変換されて throw されます。
		 *
		 * @param address    RFC822形式のアドレス
		 * @param personal   個人名
		 *
		 * @return InternetAddressオブジェクト
		 * @og.rtnNotNull
		 */
		public InternetAddress getAddress( final String address,final String personal ) {
			try {
				return new InternetAddress( address,encodeWord( personal ) );
			}
			catch( final UnsupportedEncodingException ex ) {
				final String errMsg = "指定のエンコードが出来ません。"
									+ "address=" + address ;
				throw new OgRuntimeException( errMsg,ex );
			}
		}

		/**
		 * Content-Transfer-Encoding を指定する場合の ビット数を返します。
		 *
		 * Windows系は、8bit / ISO-2022-JP 系は、7bit になります。
		 *
		 * @return	ビット数("7bit" 固定)
		 * @og.rtnNotNull
		 */
		public String getBit() {
			return "7bit" ;
		}
	}

	/**
	 * テキストの本文を送信するための DataSource です。
	 *
	 * Windows-31J でバイトコードに変換した後、独自エンコードにて、
	 * Shift-JIS ⇒ JIS 変換しています。
	 *
	 * @og.rev 6.3.9.1 (2015/11/27) 修飾子を、なし → private に変更。
	 *
	 * @version  4.0
	 * @author   Kazuhiko Hasegawa
	 * @since    JDK5.0,
	 */
	private static final class JISDataSource implements DataSource {
		private final byte[] data;

		/**
		 * JIS(Windows-31J) に対応した DataSource オブジェクトのコンストラクタ
		 *
		 * Windows-31J でバイトコードに変換した後、独自エンコードにて、
		 * Shift-JIS ⇒ JIS 変換しています。
		 *
		 * @param	str 変換する文字列
		 */
		public JISDataSource( final String str ) {
			try {
				data = CharCodeConverter.sjisToJis(
					UnicodeCorrecter.correctToCP932(str).getBytes("Windows-31J"));
			} catch( final UnsupportedEncodingException ex ) {
				final String errMsg = "Windows-31J でのエンコーディングが出来ません。" + str;
				throw new OgRuntimeException( errMsg,ex );
			}
		}

		/**
		 * データの MIME タイプを文字列の形で返します。
		 * かならず有効なタイプを返すべきです。
		 * DataSource の実装がデータタイプを 決定できない場合は、
		 * getContentType は "application/octet-stream" を返すことを 提案します。
		 *
		 * @return	MIMEタイプ("text/plain; charset=ISO-2022-JP" 固定)
		 * @og.rtnNotNull
		 */
		public String getContentType() {
			return "text/plain; charset=ISO-2022-JP";
		}

		/**
		 * データを表す InputStreamオブジェクト を返します。
		 * それができない場合は適切な例外をスローします。
		 *
		 * @return InputStreamオブジェクト
		 * @throws IOException ※ このメソッドからは、IOException は throw されません。
		 * @og.rtnNotNull
		 */
		public InputStream getInputStream() throws IOException {
			return new ByteArrayInputStream( data );
		}

		/**
		 * データが書込可能なら OutputStreamオブジェクト を返します。
		 * それができない場合は適切な例外をスローします。
		 *
		 * ※ このクラスでは実装されていません。
		 *
		 * @return OutputStreamオブジェクト
		 * @throws IOException ※ このメソッドを実行すると、必ず throw されます。
		 */
		public OutputStream getOutputStream() throws IOException {
			final String errMsg = "このクラスでは実装されていません。";
		//	throw new UnsupportedOperationException( errMsg );
			throw new IOException( errMsg );
		}

		/**
		 * このオブジェクトの '名前' を返します。
		 * この名前は下層のオブジェクトの性質によります。
		 * ファイルをカプセル化する DataSource なら オブジェクトの
		 * ファイル名を返すようにするかもしれません。
		 *
		 * @return	オブジェクトの名前( "JISDataSource" 固定)
		 * @og.rtnNotNull
		 */
		public String getName() {
			return "JISDataSource";
		}
	}
}
