/*
 * 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.process;

import org.opengion.fukurou.util.Argument;
import org.opengion.fukurou.system.Closer ;
import org.opengion.fukurou.util.FileUtil ;
import org.opengion.fukurou.util.StringUtil ;
import org.opengion.fukurou.system.LogWriter;
import org.opengion.fukurou.system.ThrowUtil;							// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.mail.MailTX ;

import java.util.Map ;
import java.util.LinkedHashMap ;
import java.io.PrintWriter ;
import java.io.StringWriter ;

/**
 * Process_Logger は、画面出力、ファイルログ、エラーメールを管理する、
 * ロギング関係の LoggerProcess インターフェースの実装クラスです。
 *
 * MainProcess で使用されるログと、各種 Process で使用されるディスプレイを
 * 管理します。また、エラー発生時の、メール送信機能も、ここで用意します。
 *
 * 引数文字列中にスペースを含む場合は、ダブルコーテーション("") で括って下さい。
 * 引数文字列の 『=』の前後には、スペースは挟めません。必ず、-key=value の様に
 * 繋げてください。
 *
 * @og.formSample
 *  Process_Logger -logFile=ABC.txt -dispFile=System.out
 *
 *   [ -logFile=ログ出力先指定  ] ： -logFile=[ファイル名/System.out/System.err] (初期値:null)
 *   [ -dispFile=画面出力先指定 ] ： -dispFile=[ファイル名/System.out/System.err](初期値:null)
 *   [ -host=メールサーバ       ] ： -host=メールサーバー
 *   [ -from=送信From           ] ： -from=送信元アドレス
 *   [ -to=受信To               ] ： -to=送信先アドレスをCSV形式で並べる
 *   [ -charset=ｷｬﾗｸﾀｾｯﾄ        ] ： -charset=メール送信時のキャラクタセット [ISO-2022-JP / Windows-31J]
 *   [ -subject=タイトル        ] ： -subject=タイトル
 *   [ -message=本文雛形        ] ： -message=本文雛形文章
 *   [ -msgFile=本文雛形ﾌｧｲﾙ    ] ： -msgFile=本文を格納しているファイルのアドレス
 *   [ -{&#064;XXXX}=YYYY       ] ： メッセージ本文の {&#064;XXXX} 文字列を、YYYY 文字列に変換します。
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class Process_Logger extends AbstractProcess implements LoggerProcess {

	private String logFile		;		// ログ出力先
	private String dispFile		;		// 画面出力先

	private PrintWriter logWriter	;
	private PrintWriter dispWriter	;

	// 6.3.1.1 (2015/07/10) JspWriter を設定されたかどうかを管理します。
	private boolean isJspLogWriter	;
	private boolean isJspDispWriter	;

	/** メール送信時のデフォルトキャラクタセット {@value} */
	public static final String DEFAULT_CHARSET = "ISO-2022-JP" ;
	private String host = "mail.opengion.org";
	private String from = "DUMMY@DUMMY";
	private String to			;
	private String subject		;			// 5.3.1.0 (2011/03/10)
	private boolean useErrMail	;

	/** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
	private static final Map<String,String> MUST_PROPARTY   ;	// ［プロパティ］必須チェック用 Map
	/** staticイニシャライザ後、読み取り専用にするので、ConcurrentHashMap を使用しません。 */
	private static final Map<String,String> USABLE_PROPARTY ;	// ［プロパティ］整合性チェック Map

	static {
		MUST_PROPARTY = new LinkedHashMap<>();

		USABLE_PROPARTY = new LinkedHashMap<>();
		USABLE_PROPARTY.put( "logFile",		"ログ出力先指定のファイル名を指定します(初期値:null)" +
											CR + "『System.out』,『System.err』は特殊な名称です。" );
		USABLE_PROPARTY.put( "dispFile",		"画面出力先指定のファイル名を指定します(初期値:null)" +
											CR + "『System.out』,『System.err』は特殊な名称です。" );
		USABLE_PROPARTY.put( "host",		"メールサーバー" );
		USABLE_PROPARTY.put( "from",		"送信元アドレス" );
		USABLE_PROPARTY.put( "to",		"送信先アドレスをCSV形式で並べる" );
		USABLE_PROPARTY.put( "charset",	"メール送信時のキャラクタセット [ISO-2022-JP / Windows-31J]" );
		USABLE_PROPARTY.put( "subject",	"タイトル" );
		USABLE_PROPARTY.put( "message",	"本文雛形文章" );
		USABLE_PROPARTY.put( "msgFile",	"本文雛形を格納しているファイルのアドレス" );
		USABLE_PROPARTY.put( "{@",		"{@XXXX}=YYYY 汎用文字変換" +
									CR + "メッセージ本文の {@XXXX} 文字列を、YYYY 文字列に変換します。"  );
		USABLE_PROPARTY.put( "{@ARG.",	"{@ARG.XXX} 予約文字変換 上記引数を割り当てます。" );
		USABLE_PROPARTY.put( "{@DATE.",	"{@DATE.XXX} 予約文字変換 の文字を変換します。" +
									CR + "(SimpleDateFormat 形式の日付、時刻等)" );
		USABLE_PROPARTY.put( "{@ENV.",	"{@ENV.XXX} 予約文字変換 ｼｽﾃﾑﾌﾟﾛﾊﾟﾃｨｰの文字を変換します。" +
									CR + "(java -Dkey=value オプションで引き渡します。)" );
	}

	/**
	 * デフォルトコンストラクター。
	 * このクラスは、動的作成されます。デフォルトコンストラクターで、
	 * super クラスに対して、必要な初期化を行っておきます。
	 *
	 */
	public Process_Logger() {
		super( "org.opengion.fukurou.process.Process_Logger",MUST_PROPARTY,USABLE_PROPARTY );
	}

	/**
	 * プロセスの初期化を行います。初めに一度だけ、呼び出されます。
	 * 初期処理(ファイルオープン、ＤＢオープン等)に使用します。
	 *
	 * @og.rev 5.3.4.0 (2011/04/01) タイトル追加
	 *
	 * @param   paramProcess データベースの接続先情報などを持っているオブジェクト
	 */
	public void init( final ParamProcess paramProcess ) {
		final Argument arg = getArgument();

		logFile  = arg.getProparty( "logFile"  );	// ログ出力先
		dispFile = arg.getProparty( "dispFile" );	// 画面出力先

		if( logWriter == null && logFile != null ) {
			logWriter = FileUtil.getLogWriter( logFile );
		}

		if( dispWriter == null && dispFile != null ) {
			dispWriter = FileUtil.getLogWriter( dispFile );
		}

		host = arg.getProparty( "host",host );	// メールサーバー
		from = arg.getProparty( "from",from );	// 送信元アドレス
		to   = arg.getProparty( "to"  ,to   );	// 送信先アドレス
		subject    = arg.getProparty( "subject" );		// 5.3.4.0 (2011/04/01) タイトル
		useErrMail = host != null && from != null && to != null ;
	}

	/**
	 * プロセスの終了を行います。最後に一度だけ、呼び出されます。
	 * 終了処理(ファイルクローズ、ＤＢクローズ等)に使用します。
	 *
	 * @param   isOK トータルで、OKだったかどうか[true:成功/false:失敗]
	 */
	public void end( final boolean isOK ) {
		if( logWriter != null ) {
			logWriter.flush();
			Closer.ioClose( logWriter );
		}

		if( dispWriter != null ) {
			dispWriter.flush();
			Closer.ioClose( dispWriter );
		}
	}

	/**
	 * ログファイルにメッセージを表示します。
	 *
	 * @og.rev 6.3.1.1 (2015/07/10) JspWriter を使用する場合は、タグをエスケープする必要がある。
	 *
	 * @param	msg	表示するメッセージ
	 */
	@Override
	public void logging( final String msg ) {
		if( logWriter != null ) {
			// 6.3.1.1 (2015/07/10)
			final String msg2 = isJspLogWriter ? StringUtil.htmlFilter( msg ) : msg ;
			logWriter.println( msg2 ) ;
		}
	}

	/**
	 * ディスプレイにメッセージを表示します。
	 *
	 * @param	msg	表示するメッセージ
	 */
	@Override
	public void println( final String msg ) {
		if( dispWriter != null ) {
			// 6.3.1.1 (2015/07/10)
			final String msg2 = isJspDispWriter ? StringUtil.htmlFilter( msg ) : msg ;
			dispWriter.println( msg2 ) ;
		}
	}

	/**
	 * エラーログにメッセージを表示します。
	 * ここに書き込まれたメッセージは、通常ログと、特殊ログの
	 * 両方に書き込まれます。
	 * 特殊ログとは、メール連絡等のことです。
	 *
	 * @og.rev 6.3.1.1 (2015/07/10) JspWriter を使用する場合は、タグをエスケープする必要がある。
	 * @og.rev 6.4.2.0 (2016/01/29) ex.printStackTrace() を、ThrowUtil#ogStackTrace(Throwable) に置き換え。
	 *
	 * @param	msg	表示するメッセージ
	 * @param	th	Throwable例外オブジェクト
	 */
	public void errLog( final String msg,final Throwable th ) {
		String sendMsg = msg;
		if( logWriter != null ) {
			if( th != null ) {
				final StringWriter errMsg = new StringWriter();
				errMsg.append( msg ).append( CR );
				System.err.println( ThrowUtil.ogStackTrace( th ) );				// 6.4.2.0 (2016/01/29)
				sendMsg = errMsg.toString();
			}
			// 6.3.1.1 (2015/07/10)
			final String msg2 = isJspLogWriter ? StringUtil.htmlFilter( sendMsg ) : sendMsg ;
			logWriter.println( msg2 ) ;
		}
		println( sendMsg ) ;
		if( useErrMail ) { sendmail( sendMsg ) ; }
	}

	/**
	 * メール送信を行います。
	 *
	 * @og.rev 5.3.4.0 (2011/04/01) タイトル追加
	 *
	 * @param	msg	送信するメッセージ
	 */
	private void sendmail( final String msg ) {

		final Argument arg = getArgument();

		final String charset = arg.getProparty( "charset", DEFAULT_CHARSET );
		final MailTX mail = new MailTX( host,charset );
		mail.setFrom( from );
		mail.setTo( StringUtil.csv2Array( to ) );
		mail.setSubject( subject );									// 5.3.4.0 (2011/04/01)

		final String message = arg.getFileProparty( "message","msgFile",false );

		// {@XXX} 変換は、Argument クラスの機能を使う。
		// 6.1.0.0 (2014/12/26) refactoring
		mail.setMessage( arg.changeParam( message ) + CR + msg  );
		mail.sendmail();
	}

	/**
	 * ログ出力用のPrintWriterを設定します。
	 * 通常は、引数の -logFile=XXXX で指定しますが、直接 PrintWriter を
	 * 渡す必要があるケース(JSPなどで使用するケース)で使用します。
	 * 引数より、こちらの設定のほうが、優先されます。
	 * ※ JspWriter を渡す場合の PrintWriter は、flushing および、close 処理を
	 * 行わない NonFlushPrintWriter を設定してください。
	 *
	 * ※ 6.3.1.1 (2015/07/10)
	 *    このメソッドは、JspWriter を設定する時のみ使用されるので、出力時には
	 *    StringUtil#htmlFilter(String) を使って、タグをエスケープします。
	 *    そのための、フラグを用意します。
	 *
	 * @og.rev 6.3.1.1 (2015/07/10) JspWriter を使用する場合は、タグをエスケープする必要がある。
	 *
	 * @param  logWriter	ログ出力用のPrintWriter
	 */
	public void setLoggingWriter( final PrintWriter logWriter ) {
		this.logWriter = logWriter;
		if( logWriter != null ) { isJspLogWriter = true; }		// 6.3.1.1 (2015/07/10)
	}

	/**
	 * 画面表示用のPrintWriterを設定します。
	 * 通常は、引数の -dispFile=XXXX で指定しますが、直接 PrintWriter を
	 * 渡す必要があるケース(JSPなどで使用するケース)で使用します。
	 * 引数より、こちらの設定のほうが、優先されます。
	 * ※ JspWriter を渡す場合の PrintWriter は、flushing および、close 処理を
	 * 行わない NonFlushPrintWriter を設定してください。
	 *
	 * ※ 6.3.1.1 (2015/07/10)
	 *    このメソッドは、JspWriter を設定する時のみ使用されるので、出力時には
	 *    StringUtil#htmlFilter(String) を使って、タグをエスケープします。
	 *    そのための、フラグを用意します。
	 *
	 * @og.rev 6.3.1.1 (2015/07/10) JspWriter を使用する場合は、タグをエスケープする必要がある。
	 *
	 * @param  dispWriter	画面表示用のPrintWriter
	 */
	public void setDisplayWriter( final PrintWriter dispWriter ) {
		this.dispWriter = dispWriter;
		if( dispWriter != null ) { isJspDispWriter = true; }		// 6.3.1.1 (2015/07/10)
	}

	/**
	 * プロセスの処理結果のレポート表現を返します。
	 * 処理プログラム名、入力件数、出力件数などの情報です。
	 * この文字列をそのまま、標準出力に出すことで、結果レポートと出来るような
	 * 形式で出してください。
	 *
	 * @og.rev 5.3.4.0 (2011/04/01) タイトル追加
	 *
	 * @return   処理結果のレポート
	 * @og.rtnNotNull
	 */
	public String report() {
		return "[" + getClass().getName() + "]" + CR
				+ TAB + "Subject      : " + subject + CR
				+ TAB + "Log     File : " + logFile + CR
				+ TAB + "Display File : " + dispFile ;
	}

	/**
	 * このクラスの使用方法を返します。
	 *
	 * @return	このクラスの使用方法
	 * @og.rtnNotNull
	 */
	public String usage() {
		final StringBuilder buf = new StringBuilder( BUFFER_LARGE )
			.append( "Process_Logger は、画面出力、ファイルログ、エラーメールを管理する、" 			).append( CR )
			.append( "ロギング関係の LoggerProcess インターフェースの実装クラスです。"				).append( CR )
			.append( CR )
			.append( "MainProcess で使用されるログと、各種 Process で使用されるディスプレイを"		).append( CR )
			.append( "管理します。また、エラー発生時の、メール送信機能も、ここで用意します。"		).append( CR )
			.append( CR )
			.append( "引数文字列中に空白を含む場合は、ダブルコーテーション(\"\") で括って下さい。"	).append( CR )
			.append( "引数文字列の 『=』の前後には、空白は挟めません。必ず、-key=value の様に"		).append( CR )
			.append( "繋げてください。"																).append( CR )
			.append( CR ).append( CR )
			.append( getArgument().usage() ).append( CR );

		return buf.toString();
	}

	/**
	 * このクラスは、main メソッドから実行できません。
	 *
	 * @param	args	コマンド引数配列
	 */
	public static void main( final String[] args ) {
		LogWriter.log( new Process_Logger().usage() );
	}
}
