/*
 * 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.StringUtil ;
import org.opengion.fukurou.util.HybsEntry ;
import org.opengion.fukurou.util.LogWriter;

import java.util.Properties;
import java.util.List;
import java.util.ArrayList;

import javax.mail.Session;
import javax.mail.Store;
import javax.mail.Folder;
import javax.mail.Message;
import javax.mail.Flags;
import javax.mail.MessagingException;
import javax.mail.NoSuchProviderException;
import javax.mail.search.SearchTerm;
import javax.mail.search.SubjectTerm;
import javax.mail.search.FromStringTerm;
import javax.mail.search.BodyTerm;
import javax.mail.search.HeaderTerm;
import javax.mail.search.AndTerm;

/**
 * MailRX は、POP3プロトコルによるメール受信プログラムです。
 *
 * メールへの接続条件(host,user,passwd など)と、選択条件(matchTermなど)を指定し、
 * MailReceiveListener をセットして、start() メソッドを呼びます。
 * 実際のメール処理は、MailReceiveListener を介して、１メールずつ処理します。
 * 添付ファイルを処理する場合は、MailAttachFiles クラスを使用します。
 *
 *        host          メールサーバー(必須)
 *        user          メールを取得するログインユーザー(必須)
 *        passwd        メールを取得するログインパスワード(必須)
 *        protocol      受信サーバーのプロトコル[imap/pop3]を指定(初期値:pop3)
 *        port          受信サーバーのポートを指定(初期値:-1)
 *        mbox          受信サーバーのメールボックスを指定(初期値:INBOX)
 *        maxRowCount   受信メールの最大取り込み件数(初期値:100)(0:[無制限])
 *        charset       メールのデフォルトエンコード(初期値:ISO-2022-JP)
 *        matchTerm     受信メールを選択する条件のMINEntryオブジェクト
 *        delete        検索後、メールをサーバーから削除するかどうかを、true/falseで指定(初期値:false)。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class MailRX {

	/** 受信メールの最大取り込み件数を指定します。 {@value}  */
	public static final int MAX_ROW_COUNT = 100 ;

	/** 検索後、メールをサーバーから削除するかどうかを、true/falseで指定します。 {@value}  */
	public static final boolean DELETE_MESSAGE = false ;

	/** メールサーバーのデフォルトプロトコル {@value}  */
	public static final String PROTOCOL = "pop3" ;

	/** メールサーバーのデフォルトポート番号 {@value}  */
	public static final int PORT = -1 ;

	/** メールサーバーのデフォルトメールボックス {@value}  */
	public static final String MBOX = "INBOX" ;

	/** メールのデフォルトエンコード {@value}
	 * Windwos-31J , MS932 , ISO-2022-JP を指定します。
	 */
	public static final String CHARSET = "ISO-2022-JP" ;

	// メール受信毎に発生するイベントを伝えるリスナーを登録します。
	private MailReceiveListener listener ;

	private String	host		;
	private String	user		;
	private String	passwd		;
	private String	protocol	= PROTOCOL;
	private int		port		= PORT;
	private String	mbox		= MBOX;
	private boolean	deleteFlag	= DELETE_MESSAGE;

	private String	charset		= CHARSET;

	private int		maxRowCount	= MAX_ROW_COUNT;
	private final List<HybsEntry>	matchList	= new ArrayList<HybsEntry>();
	private boolean	debug		;

	/**
	 * レシーバーを開始します。
	 *
	 * @throws MessagingException レシーバー処理中に、なんらかのエラーが発生した場合。
	 * @throws NoSuchProviderException なんらかのエラーが発生した場合。
	 */
	public void start() throws MessagingException,NoSuchProviderException {

		// パラメータの解析、取得
		debugMsg( "パラメータの解析、取得" );

		// 指定の条件にマッチしたメッセージのみ抜き出す為の、SearchTerm オブジェクトの作成
		debugMsg( "指定の条件にマッチしたメッセージのみ抜き出す条件を設定します。"  );
		final HybsEntry[] matchs = matchList.toArray( new HybsEntry[matchList.size()] );
		SearchTerm[] term = new SearchTerm[matchs.length];
		for( int i=0; i<matchs.length; i++ ) {
			final String key = matchs[i].getKey();
			if( "Subject".equalsIgnoreCase( key ) ) {
				term[i] = new SubjectTerm( matchs[i].getValue() );
			}
			else if( "From".equalsIgnoreCase( key ) ) {
				term[i] = new FromStringTerm( matchs[i].getValue() );
			}
			else if( "Body".equalsIgnoreCase( key ) ) {
				term[i] = new BodyTerm( matchs[i].getValue() );
			}
			else {
				term[i] = new HeaderTerm( key,matchs[i].getValue() );
			}
		}
		final SearchTerm srchTerm = new AndTerm( term );

		// 空の properties を設定。気休め程度に、初期値を設定しておきます。
		debugMsg( "空の properties を設定"  );
		final Properties prop = new Properties();
		prop.setProperty("mail.mime.charset", charset);
		prop.setProperty("mail.mime.decodetext.strict", "false");
		prop.setProperty("mail.mime.address.strict", "false");

		// session を取得
		debugMsg( "session を取得" );
		final Session session = Session.getInstance(prop, null);

		Store store = null;
		Folder folder = null;
		try {
			// store の取得
			debugMsg( "store の取得 protocol=",protocol );
			store = session.getStore( protocol );

			// サーバーと connect します。
			debugMsg( "サーバーと connect します。" );
			store.connect(host, port, user, passwd);

			// folder の取得
			debugMsg( "folder の取得" );
			folder = store.getFolder( mbox );
			if( deleteFlag ) {
				folder.open(Folder.READ_WRITE);
			}
			else {
				folder.open(Folder.READ_ONLY);
			}

			// メッセージ情報の取得
			debugMsg( "メッセージ情報の取得" );
	//		Message[] message = folder.getMessages();
			final Message[] message = folder.search( srchTerm );

			final int len = message.length;				// 6.1.0.0 (2014/12/26) refactoring
			for(int i=0; i<len && i<maxRowCount; i++) {
				final MailMessage mailMessage = new MailMessage( message[i],host,user );
				debugMsg( "[" , String.valueOf(i) , "]" , mailMessage.getMessageID() , " 受信中" );

				// メールの削除[true/false]:先にフラグを立てているので、エラーでも削除されます。
				message[i].setFlag(Flags.Flag.DELETED, deleteFlag);

				boolean okFlag = true;
				if( listener != null ) {
					// メール本体の処理
					okFlag = listener.receive( mailMessage );
				}

				// 受領確認の返信メール
				final String notifyTo = mailMessage.getNotificationTo() ;
				if( okFlag && notifyTo != null ) {
					final MailTX tx = new MailTX( host );
					tx.setFrom( user );
					tx.setTo( StringUtil.csv2Array( notifyTo ) );
					tx.setSubject( "受領:" + mailMessage.getSubject() );
					tx.setMessage( mailMessage.getContent() );
					tx.sendmail();
				}
			}
		}
		finally {
			// セッション終了
			debugMsg( "セッション終了処理" );
			if( folder != null ) {
				folder.close(deleteFlag);		// true の場合は、終了時に実際に削除します。
			}
			if( store != null ) {
				store.close();
			}
		}
	}

	/**
	 * メールサーバーをセットします。
	 *
	 * @param	host メールサーバー
	 * @throws	IllegalArgumentException 引数が null の場合。
	 */
	public void setHost( final String host ) {
		if( host == null ) {
			final String errMsg = "host に null はセット出来ません。";
			throw new IllegalArgumentException( errMsg );
		}

		this.host = host;
	}

	/**
	 * 受信ユーザーをセットします。
	 *
	 * @param	user 受信ユーザー
	 * @throws	IllegalArgumentException 引数が null の場合。
	 */
	public void setUser( final String user ) {
		if( user == null ) {
			final String errMsg = "user に null はセット出来ません。";
			throw new IllegalArgumentException( errMsg );
		}
		this.user = user;
	}

	/**
	 * パスワードをセットします。
	 *
	 * @param	passwd パスワード
	 * @throws	IllegalArgumentException 引数が null の場合。
	 */
	public void setPasswd( final String passwd ) {
		if( passwd == null ) {
			final String errMsg = "passwd に null はセット出来ません。";
			throw new IllegalArgumentException( errMsg );
		}
		this.passwd = passwd;
	}

	/**
	 * 受信プロトコルをセットします。
	 *
	 * @param	protocol 受信プロトコル名
	 * @throws	IllegalArgumentException 引数が null の場合。
	 */
	public void setProtocol( final String protocol ) {
		if( protocol == null ) {
			final String errMsg = "protocol に null はセット出来ません。";
			throw new IllegalArgumentException( errMsg );
		}
		this.protocol = protocol;
	}

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

	/**
	 * 受信メイルボックスをセットします。
	 *
	 * @param	mbox 受信メイルボックス名
	 * @throws	IllegalArgumentException 引数が null の場合。
	 */
	public void setMbox( final String mbox ) {
		if( mbox == null ) {
			final String errMsg = "mbox に null はセット出来ません。";
			throw new IllegalArgumentException( errMsg );
		}
		this.mbox = mbox;
	}

	/**
	 * メール受信毎に発生するイベントを伝えるリスナーをセットします。
	 *
	 * @param	listener MailReceiveリスナー
	 */
	public void setMailReceiveListener( final MailReceiveListener listener ) {
		this.listener = listener;
	}

	/**
	 * メッセージをメールサーバーから削除するかどうかをセットします。
	 *
	 * @param	deleteFlag 削除するかどうか[true:行う/false:行わない]
	 */
	public void setDelete( final boolean deleteFlag ) {
		this.deleteFlag = deleteFlag;
	}

	/**
	 * 文字エンコーディングをセットします。
	 *
	 * 文字エンコーディングには、Windwos-31J , MS932 , ISO-2022-JP を指定できます。
	 * 初期値は、SystemResource.properties ファイルの MAIL_DEFAULT_CHARSET 属性で
	 * 設定できます。
	 *
	 * @param   charset 文字エンコーディング
	 * @throws	IllegalArgumentException 引数が null の場合。
	 */
	public void setCharset( final String charset ) {
		if( charset == null ) {
			final String errMsg = "charset に null はセット出来ません。";
			throw new IllegalArgumentException( errMsg );
		}
		this.charset = charset;
	}

	/**
	 * 最大取り込み件数をセットします(初期値:100)(0:[無制限])。
	 *
	 * @og.rev 5.5.8.5 (2012/11/27) 0を無制限として処理します。
	 *
	 * @param	maxCount 最大取り込み件数
	 */
	public void setMaxRowCount( final int maxCount ) {
		maxRowCount = maxCount>0 ? maxCount : Integer.MAX_VALUE ;			// 6.0.2.5 (2014/10/31) refactoring
	}

	/**
	 * メール検索する場合のマッチ条件のキーと値の HybsEntry をセットします。
	 * Subject,From,Body,それ以外は、Header 文字列をキーにします。
	 *
	 * @param	matchTerm HybsEntryオブジェクト
	 */
	public void addMatchTerm( final HybsEntry matchTerm ) {
		matchList.add( matchTerm );
	}

	/**
	 * デバッグ情報の表示を行うかどうかをセットします。
	 *
	 * @param	debug 有無[true/false]
	 */
	public void setDebug( final boolean debug ) {
		this.debug = debug;
	}

	/**
	 * デバッグ情報の表示を行います。
	 * 実際の処理は、debug フラグに設定値によります。
	 *
	 * @param	msgs デバッグ情報(可変長引数)
	 */
	private void debugMsg( final String... msgs ) {
		if( debug ) {
			for( final String msg : msgs ) {
				System.out.print( msg );
			}
			System.out.println();
		}
	}

	/**
	 * コマンドから実行できる、テスト用の main メソッドです。
	 *
	 * Usage: java org.opengion.fukurou.mail.MailTX MailRX host user passwd [saveDir]
	 * で、複数の添付ファイルを送付することができます。
	 *
	 * @param   args 引数配列
	 * @throws Exception なんらかのエラーが発生した場合。
	 */
	public static void main( final String[] args ) throws Exception {
		if(args.length < 3) {
			LogWriter.log("Usage: java org.opengion.fukurou.mail.MailRX host user passwd [saveDir]");
			System.exit(1);
		}
		final String dir = (args.length == 4) ? args[3] : null;

		final MailRX recive = new MailRX();

		recive.setHost( args[0] );
		recive.setUser( args[1] );
		recive.setPasswd( args[2] );
		recive.setCharset( "ISO-2022-JP" );

		final MailReceiveListener listener = new MailReceiveListener() {
			/**
			 * メール受信処理で、１メール受信ごとに呼び出されます。
			 * 処理結果を、boolean で返します。
			 *
			 * @param message MailMessageオブジェクト
			 * @return	処理結果(正常:true / 異常:false)
			 */
			public boolean receive( final MailMessage message ) {
				System.out.println( message.getSimpleMessage() );

				if( dir != null ) {
					message.saveSimpleMessage( dir );
				}
				return true ;
			}
		};
		recive.setMailReceiveListener( listener );

		recive.start();
	}
}
