/*
 * Copyright (c) 2017 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.fileexec;

import java.nio.file.Files;
import java.nio.file.Path;
// import java.nio.file.Paths;
// import java.nio.file.DirectoryStream;
// import java.io.IOException;
import java.util.Arrays;
// import java.util.concurrent.ConcurrentMap;
// import java.util.concurrent.ConcurrentHashMap;
// import java.util.logging.Logger;

import static org.opengion.fukurou.fileexec.CommandLine.GE70;		// enum を簡素化して使用するための定義

/**
 * FileExec は、処理の中心で、デーモン一つに対応する処理開始クラスです。
 *
 *<pre>
 * このクラスは、ファイルスキャンのフォルダ単位に、起動され、ファイルのイベントを処理します。
 *</pre>
 *
 * @og.rev 7.0.0.0 (2017/07/07) 新規作成
 *
 * @version  7.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.8,
 */
public class FileExec {
	private static final XLogger LOGGER= XLogger.getLogger( FileExec.class.getName() );		// ログ出力

	/** システム依存の改行記号(String)。	*/
	public static final String CR = System.getProperty("line.separator");

	private final GE71		tableGE71 ;

	private final String	systemId ;		// ｼｽﾃﾑID
	private final String	rsrvNo;			// 予約番号
	private final String	execId;			// 処理ID

	private final String	fileFltr ;		// 検索条件

	private final BasePath	basePath ;		// 各種パスを管理しているｸﾗｽ

	private final FileWatch fWatch ;		// 取込ﾌｫﾙﾀﾞをイベントで監視する

	/**
	 * コマンドラインを引数に取るコンストラクター
	 *
	 * ファイルの監視を開始します。
	 *
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 *
	 * @param	cmndLine	コマンドラインオブジェクト
	 */
	public FileExec( final CommandLine cmndLine ) {
		LOGGER.debug( () -> "② CommandLine=" + cmndLine );

		systemId	= cmndLine.getValue( GE70.SYSTEM_ID );		// ｼｽﾃﾑID
		rsrvNo		= cmndLine.getValue( GE70.RSRV_NO );		// 予約番号
		execId		= cmndLine.getValue( GE70.EXECID );			// 処理ID
		fileFltr	= cmndLine.getValue( GE70.FILE_FILTER );	// 検索条件

		basePath = new BasePath(
					cmndLine.getValue( GE70.DIR_BASE )		,	// 取込ﾍﾞｰｽﾌｫﾙﾀﾞ
					cmndLine.getValue( GE70.DIR_SUB  )		,	// 取込ｻﾌﾞﾌｫﾙﾀﾞ
					cmndLine.getValue( GE70.DIR_WORK    )	,	// 処理ﾌｫﾙﾀﾞ(WORK)
					cmndLine.getValue( GE70.DIR_BKUP_OK )	,	// 処理済ﾌｫﾙﾀﾞ(正常)
					cmndLine.getValue( GE70.DIR_BKUP_NG ) );	// 処理済ﾌｫﾙﾀﾞ(異常)

		tableGE71 = new GE71( systemId,rsrvNo,execId );

		fWatch	= new FileWatch( basePath.SUB_PATH );			// ｻﾌﾞﾌｫﾙﾀﾞをイベントで監視する
	}

	/**
	 * このコマンドに対応するフォルダの監視を開始します。
	 *
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 */
	public void watchStart() {
		LOGGER.debug( () -> "④ [watchStart()]" );

		fWatch.setEventKinds( FileWatch.CREATE , FileWatch.MODIFY );
		fWatch.setPathMatcher( new PathMatcherSet().addFileName( fileFltr ) );		// ファイルの検索条件
		fWatch.callback( (event,fPath) -> checkFile( event,fPath ) );
		fWatch.start();
	}

	/**
	 * このコマンドに対応するフォルダの監視を終了します。
	 *
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 */
	public void watchStop() {
		LOGGER.debug( () -> "③ [watchStop()]" );

		fWatch.stop();
	}

	/**
	 * 更新されたファイルをチェックします。
	 *
	 * ※ バックアップ処理してから、DB取り込み処理を行います。
	 * よって、DB登録処理中にエラーが発生した場合でも、バックアップ済みです
	 *
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 *
	 * @param	event     発生イベントの名称
	 * @param	filePath ファイルパス(相対パス)
	 */
	private void checkFile( final String event,final Path filePath ) {
		Path bkup   = null;
		Path okFile = null;
		Path ngFile = null;
		String errMsg = "";

		try {
			// FileUtil.stablePath は、書き込まれている途中かもしれないので、安定するまで待つ。
			if( FileUtil.stablePath( filePath ) ) {
				LOGGER.debug( () -> "⑤ event=" + event + " , Path=" + filePath );

				// ワークへ移動してから、DB取り込み処理を行います。
				bkup = FileUtil.backup( filePath,basePath.WORK_PATH );							// WORKに移動します。

				final String  tmStr = StringUtil.getTimeFormat();								// 開始時刻

				final AppliExec appli = AppliExec.newInstance( systemId,execId );

				final int suKekka = appli.exec( bkup );

				final String fgtKan ;											// 取込完了ﾌﾗｸﾞ 1:処理中 2:済 7:ﾃﾞｰﾓﾝｴﾗｰ 8:ｱﾌﾟﾘｴﾗｰ
				if( suKekka >= 0 ) {
					okFile = FileUtil.backup( bkup,basePath.OK_PATH );			// 処理済OKﾌｫﾙﾀﾞに移動
					fgtKan = "2" ;
				}
				else {
					ngFile = FileUtil.backup( bkup,basePath.NG_PATH );			// 処理済NGﾌｫﾙﾀﾞに移動
					fgtKan = "8" ;
				}

				tableGE71.dbInsert( fgtKan , tmStr , filePath , okFile , ngFile , suKekka , errMsg );

				LOGGER.info( () -> "DAT execute. " + filePath + " , FGTKAN=" + fgtKan + " , kensu=" + suKekka );
			}
			else {
				// エラーにせず、保留にします。
				LOGGER.info( () -> "checkFile Not stablePath. " + filePath );
			}
		}
		catch( final Throwable th ) {
			// MSG0021 = 予期せぬエラーが発生しました。\n\tﾒｯｾｰｼﾞ=[{0}]
			errMsg = MsgUtil.errPrintln( th , "MSG0021" , filePath );

			if( Files.exists( bkup ) ) {			// WORKに移動するところまでは、成功しており、その後の、移動ができていない。
				ngFile = FileUtil.backup( bkup,basePath.NG_PATH );			// 処理済NGﾌｫﾙﾀﾞに移動
			}

			tableGE71.dbInsert( "7" , StringUtil.getTimeFormat() , filePath , null , ngFile , -1 , errMsg );		// -1 は、エラーを示します。
		}
	}

	/**
	 *このクラスの文字列表現を返します。
	 *
	 * @return	クラスの文字列表現
	 */
	@Override
	public String toString() {
		return systemId + " , " + execId ;
	}

	/**
	 * GE71 実行結果をデータベースに書き込む内部クラスです。
	 *
	 * @return	クラスの文字列表現
	 */
	private static final class GE71 {
		private static final String[] KEYS		= new String[] {  "SYSTEM_ID","RSRV_NO","EXECID","FGTKAN","TMSTR","TMEND"
																, "FILE_IN","FILE_OK","FILE_NG","SUTORI ","ERRMSG "
																, "DYSET","DYUPD" };
		private static final String[] CON_KEYS	= new String[] { "FGJ","PGSET"  ,"PGUPD"  };
		private static final String[] CON_VALS	= new String[] { "1"  ,"FileExec","FileExec" };

		private static final String INS_QUERY = DBUtil.getInsertSQL( "GE71",KEYS,CON_KEYS,CON_VALS );

		private final String systemId ;			// システムID
		private final String rsrvNo;			// 予約番号
		private final String execId ;			// 処理ID

		/**
		 * GE71 データベースにインサート処理を行うクラスのコンストラクター
		 *
		 * @param	sysId	システムID
		 * @param	rsNo	予約番号
		 * @param	exId	処理ID
		 */
		public GE71( final String sysId , final String rsNo ,final String exId ) {
			systemId = sysId ;
			rsrvNo   = rsNo ;
			execId   = exId;
		}

		/**
		 * データベースにインサート処理を行います。
		 *
		 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
		 *
		 * @param	fgtKan	取込完了ﾌﾗｸﾞ
		 * @param	tmStr	開始時刻
		 * @param	fIn		取込ﾌｧｲﾙパス
		 * @param	fOk		処理済OKﾌｧｲﾙパス
		 * @param	fNg		処理済NGﾌｧｲﾙパス
		 * @param	sutori	取込数
		 * @param	errMsg	ｴﾗｰﾒｯｾｰｼﾞ
		 */
		public void dbInsert( final String fgtKan , final String tmStr , final Path fIn , final Path fOk , final Path fNg , final int sutori , final String errMsg ) {
			final String NOW = StringUtil.getTimeFormat();

			final String fileIn = fIn == null ? "" : fIn.toString() ;
			final String fileOk = fOk == null ? "" : fOk.toString() ;
			final String fileNg = fNg == null ? "" : fNg.toString() ;

			// GE71 テーブルのカラム
			final String[] values = new String[] {
				  systemId					// ｼｽﾃﾑID		SYSTEM_ID
				, rsrvNo					// 予約番号		RSRV_NO
				, execId					// 処理ID		EXECID
				, fgtKan					// 取込完了ﾌﾗｸﾞ	FGTKAN
				, tmStr						// 開始時刻		TMSTR
				, NOW 						// 完了時刻		TMEND
				, fileIn					// 取込ﾌｧｲﾙ		FILE_IN
				, fileOk					// 処理済OKﾌｧｲﾙ	FILE_OK
				, fileNg					// 処理済NGﾌｧｲﾙ	FILE_NG
				, String.valueOf( sutori )	// 取込件数		SUTORI
				, errMsg					// ｴﾗｰﾒｯｾｰｼﾞ	ERRMSG
				, NOW						// 登録日時		DYSET
				, NOW						// 更新日時		DYUPD
			} ;

			LOGGER.debug( () -> "⑥ GE71.dbInsert query=" + INS_QUERY + "\n\t values=" + Arrays.toString( values ) );

			DBUtil.execute( INS_QUERY , values );
		}
	}
}
