/*
 * 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 org.opengion.fukurou.util.Closer ;

import javax.mail.MessagingException;
import javax.mail.Part;
import javax.mail.BodyPart;
import javax.mail.Multipart;
import java.io.File;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;
import java.util.ArrayList;
import java.util.Set;
import java.util.HashSet;

/**
 * メール添付ファイル処理クラス
 *
 * このクラスは、添付ファイルを処理するためのクラスです。
 * 添付ファイルは、マルチパートに含まれている為、再帰的に探す必要があります。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class MailAttachFiles {
	private final List<Part> files ;
	private final String[] names ;

	/**
	 * デフォルトコンストラクター
	 *
	 * 内部変数の初期化を行います。
	 *
	 * @param	part	Partオブジェクト
	 */
	public MailAttachFiles( final Part part ) {
		files = new ArrayList<Part>();
		names = makeNames( part );
	}

	/**
	 * 添付ファイルの名称を文字列配列として求めます。
	 *
	 * @return 添付ファイルの名称を文字列配列
	 */
	public String[] getNames() {
		String[] rtn = null ;

		if( names != null ) { rtn = names.clone(); }

		return rtn ;
	}

	/**
	 * 添付ファイルの名称を文字列配列として求めます。
	 *
	 * この処理の中で、添付ファイルを持つ Part を見つけて内部配列(List)に登録します。
	 * ファイル名が未指定の場合は、"noNameFile" + i + ".tmp" というファイル名をつけます。
	 * i は、添付ファイルの連番です。
	 * また、同一添付ファイル名が存在する場合は、頭に添付ファイルの連番を付加して、
	 * ファイル名としてユニーク化します。
	 *
	 * @og.rev 4.3.3.5 (2008/11/08) 日本語添付ファイルが処理できるように修正
	 *
	 * @param part Partオブジェクト
	 *
	 * @return 添付ファイルの名称を文字列配列
	 */
	private String[] makeNames( final Part part ) {
		final String[] nms;
		try {
			final Set<String> set = new HashSet<String>();

			fileSearch( part );
			nms = new String[files.size()];
			for( int i=0; i<nms.length; i++ ) {
				final String name = files.get(i).getFileName();
				if( name == null ) {	// message か、ファイル名未指定のケース
					nms[i] = "noNameFile" + i + ".tmp" ;
				}
				// 4.3.3.5 (2008/11/08) 日本語添付ファイルが処理できるように修正
				else {
					nms[i] = MailMessage.mimeDecode( name );
				}

				// 重複チェック
				if( !set.add( nms[i] ) ) {
					nms[i] = i + "_" + nms[i] ;		// 重複時に名称変更します。
				}
			}
		}
		catch( MessagingException ex ) {
			final String errMsg = "メッセージ情報のハンドリングに失敗しました。"
							+ ex.getMessage();				// 5.1.8.0 (2010/07/01) errMsg 修正
			throw new RuntimeException( errMsg,ex );
		}
		catch( IOException ex ) {
			final String errMsg = "テキスト情報の取り出しに失敗しました。"
							+ ex.getMessage();				// 5.1.8.0 (2010/07/01) errMsg 修正
			throw new RuntimeException( errMsg,ex );
		}
		return nms;
	}

	/**
	 * 添付ファイルが存在するかどうかをサーチします。
	 *
	 * 添付ファイルは、マルチパートで指定されると、再帰的に検索する必要が
	 * 出てきます。このメソッドでは、再帰的にファイルかどうかを検索し、
	 * ファイルであれば、内部変数(List)に追加(add)していきます。
	 *
	 * @param part Partオブジェクト
	 *
	 * @return 再帰検索終了 true
	 * @throws MessagingException javax.mail 関連のエラーが発生したとき
	 * @throws IOException 入出力エラーが発生したとき
	 *
	 */
	private boolean fileSearch( final Part part ) throws MessagingException ,IOException {
		if( part.isMimeType( "multipart/*" ) ) {
			final Multipart mpt = (Multipart)part.getContent();

			final int count = mpt.getCount();
			for(int i = 0; i < count; i++) {
				final BodyPart bpt = mpt.getBodyPart(i);
				fileSearch( bpt );
			}
		}
		else {
			if( part.isMimeType( "message/*" )	||
				part.getFileName() != null		||
				Part.INLINE.equalsIgnoreCase( part.getDisposition() ) ) {
					files.add( part );
			}
		}
		return true;
	}

	/**
	 * 添付ファイルを指定のフォルダにセーブします。
	 *
	 * 内部変数List の 添付ファイルを持つ Part について、ファイルを抜出し、
	 * 指定のディレクトリに保存していきます。
	 * ファイル名は、基本的に添付ファイル名そのものですが、
	 * 同一名称の添付ファイルが複数登録されている場合は、その重複ファイルの番号を
	 * 頭につけ、番号 + "_" + 添付ファイル名 として、ユニーク化します。
	 *
	 * ※ ディレクトリの作成に失敗した場合、RuntimeException が throw されます。
	 *
	 * @param	dir	セーブするディレクトリ (nullの場合は、セーブしない)
	 * @param	newNm	セーブするファイル名 (nullの場合は、非重複化された添付ファイル名)
	 * @param	fno	添付ファイルの番号
	 */
	public void saveFileName( final String dir, final String newNm, final int fno ) {
		if( dir == null ) { return ; }		// ファイルをセーブしない。

		final File fileDir = new File( dir );
		if( !fileDir.exists() ) {
			final boolean isOk = fileDir.mkdirs();
			if( ! isOk ) {
				final String errMsg = "ディレクトリの作成に失敗しました。[" + dir + "]";
				throw new RuntimeException( errMsg );
			}
		}

		final String newName = ( newNm != null ) ? newNm : names[fno] ;

		InputStream      input  = null;
		FileOutputStream output = null;

		try {
			final Part prt = files.get( fno );
			input = prt.getInputStream();
			output = new FileOutputStream( new File( fileDir,newName ) );
			final byte[] buf = new byte[1024];
			int len;
			while( (len = input.read(buf)) != -1 ) {
				output.write( buf,0,len );
			}
		}
		catch( MessagingException ex ) {
			final String errMsg = "メッセージオブジェクトの操作中にエラーが発生しました。"
							+ "dir=[" + dir + "], file=[" + newName + "], No=[" + fno + "]"
							+ ex.getMessage();			// 5.1.8.0 (2010/07/01) errMsg 修正
			throw new RuntimeException( errMsg,ex );
		}
		catch( IOException ex ) {
			final String errMsg = "添付ファイルの取り扱い中にエラーが発生しました。"
							+ "dir=[" + dir + "], file=[" + newName + "], No=[" + fno + "]"
							+ ex.getMessage();			// 5.1.8.0 (2010/07/01) errMsg 修正
			throw new RuntimeException( errMsg,ex );
		}
		finally {
			Closer.ioClose( output );
			Closer.ioClose( input  );
		}
	}
}
