/*
 * 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.util.Arrays;
import java.nio.file.PathMatcher;

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.getSimpleName() );		// ログ出力

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

	private final TBL_GE71	tableGE71 ;		// 6.9.7.0 (2018/05/14) PMD

	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 ;		// 取込ﾌｫﾙﾀﾞをイベントで監視する

	// 7.2.1.0 (2020/03/13) 拡張子が properties と bat と jar は対象外にします。
	private static final String PROP = ".properties" ;
	private static final String BAT  = ".bat" ;
	private static final String JAR  = ".jar" ;

	// 7.2.1.0 (2020/03/13) 拡張子が properties , bat , jar と WORK,OK,NG は対象外にします。
	private static final PathMatcher SCAN_EXT = path -> {
//		final String fname = path.getFileName().toString();
		final String fname = FileUtil.pathFileName( path );		// 7.2.9.4 (2020/11/20) Path.getFileName().toString()
		return  !( fname.endsWith( PROP ) || fname.endsWith( BAT ) || fname.endsWith( JAR ) );
	};

	/**
	 * コマンドラインを引数に取るコンストラクター
	 *
	 * ファイルの監視を開始します。
	 *
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 * @og.rev 7.2.1.0 (2020/03/13) WORK_PATH,OK_PATH,NG_PATH のスキャンは行わない
	 *
	 * @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 TBL_GE71( systemId,rsrvNo,execId );		// 6.9.7.0 (2018/05/14) PMD

		fWatch	= new FileWatch( basePath.SUB_PATH );			// ｻﾌﾞﾌｫﾙﾀﾞをイベントで監視する
		fWatch.setPathMatcher( SCAN_EXT );								// 7.2.1.0 (2020/03/13)
		fWatch.setPathMatcher( path -> basePath.isScanPath( path ) );	// 7.2.1.0 (2020/03/13)
	}

	/**
	 * このコマンドに対応するフォルダの監視を開始します。
	 *
	 * @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()]" );

		AppliExec.removeInstance( systemId,rsrvNo );		// 7.2.1.0 (2020/03/13)

		fWatch.stop();
	}

	/**
	 * 更新されたファイルをチェックします。
	 *
	 * ※ バックアップ処理してから、DB取り込み処理を行います。
	 * よって、DB登録処理中にエラーが発生した場合でも、バックアップ済みです
	 *
	 * @og.rev 6.8.1.5 (2017/09/08) LOGGER.debug 情報の追加
	 * @og.rev 7.2.1.0 (2020/03/13) ﾃﾞｨﾚｸﾄﾘか存在しない場合は、処理を実行しません。
	 *
	 * @param	event     発生イベントの名称
	 * @param	filePath ファイルパス(相対パス)
	 */
	private void checkFile( final String event,final Path filePath ) {
		if( Files.isDirectory( filePath ) || !Files.exists( filePath ) ) { return; }	// 7.2.1.0 (2020/03/13)

		String	fgtKan	= "0" ;			// 取込完了ﾌﾗｸﾞ 0:取込なし 1:処理中 2:済 7:ﾃﾞｰﾓﾝｴﾗｰ 8:ｱﾌﾟﾘｴﾗｰ
		Path	bkup	= null;
		String	errMsg	= "";
		int		suKekka	= -1;

		final String  tmStr = StringUtil.getTimeFormat();						// 開始時刻 (PMDでexit point警告が出るが、開始時刻なので仕方がない)
		AppliExec appli	= null;
		try {
			// FileUtil.stablePath は、書き込まれている途中かもしれないので、安定するまで待つ。
			if( FileUtil.stablePath( filePath ) ) {
				LOGGER.debug( () -> "⑤ event=" + event + " , Path=" + filePath );

				appli = AppliExec.newInstance( systemId,rsrvNo,execId );		// 7.2.1.0 (2020/03/13)
				// ワークへ移動してから、DB取り込み処理を行います。
				bkup = FileUtil.backup( filePath,basePath.WORK_PATH );			// WORKに移動します。
				suKekka = appli.exec( bkup );

				if( suKekka >= 0 ) {
//					okFile = FileUtil.backup( bkup,basePath.OK_PATH );			// 処理済OKﾌｫﾙﾀﾞに移動
					fgtKan = "2" ;		// 2:済
				}
				else {
//					ngFile = FileUtil.backup( bkup,basePath.NG_PATH );			// 処理済NGﾌｫﾙﾀﾞに移動
					fgtKan = "8" ;		// 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 );
				return ;
			}
		}
		catch( final Throwable th ) {
			fgtKan = "7" ;				// 7:ﾃﾞｰﾓﾝｴﾗｰ

			// MSG0021 = 予期せぬエラーが発生しました。\n\tﾒｯｾｰｼﾞ=[{0}]
			errMsg = MsgUtil.errPrintln( th,"MSG0021",filePath );

			// 6.9.8.0 (2018/05/28) FindBugs: メソッド呼び出しは非 null パラメータに対して null を渡している
//			if( Files.exists( bkup ) ) {								// WORKに移動するところまでは、成功しており、その後の、移動ができていない。
//			if( bkup != null && Files.exists( bkup ) ) {				// WORKに移動するところまでは、成功しており、その後の、移動ができていない。
//				ngFile = FileUtil.backup( bkup,basePath.NG_PATH );		// 処理済NGﾌｫﾙﾀﾞに移動
//			}

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

		// checkFile Not stablePath. の時は、appli == null
		if( appli != null ) {
			try {
				// appli.exec が正常でもｴﾗｰでも実行する。
				appli.endExec( bkup,fgtKan,errMsg );
			}
			catch( final Throwable th ) {
				if( ! "7".equals( fgtKan ) ) {						// 7:ﾃﾞｰﾓﾝｴﾗｰは、上書きしない。
					fgtKan = "8" ;
				}
				// MSG0032 = PL/SQｌの実行時にｴﾗｰが発生しました。\n\t[{0}]
				errMsg = MsgUtil.errPrintln( th,"MSG0032",filePath );
			}
		}

		// 6.9.8.0 (2018/05/28) FindBugs: メソッド呼び出しは非 null パラメータに対して null を渡している
		Path	okFile	= null;
		Path	ngFile	= null;
		if( bkup != null && Files.exists( bkup ) ) {				// WORKに移動するところまでは、成功しており、その後の、移動ができていない。
			if( "2".equals( fgtKan ) ) {
				okFile = FileUtil.backup( bkup,basePath.OK_PATH );		// 処理済OKﾌｫﾙﾀﾞに移動
			}
			else {
				ngFile = FileUtil.backup( bkup,basePath.NG_PATH );		// 処理済NGﾌｫﾙﾀﾞに移動
			}
		}

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

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

	/**
	 * GE71 実行結果をデータベースに書き込む内部クラスです。 ( 6.9.7.0 (2018/05/14) PMD )
	 */
	private static final class TBL_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 TBL_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 情報の追加
		 * @og.rev 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
		 *
		 * @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();

			// ﾌｧｲﾙは、ﾌｧｲﾙ名のみとします。
			// 7.2.9.4 (2020/11/20) spotbugs:null になっている可能性があるメソッドの戻り値を利用している
//			final String fileIn = fIn == null ? "" : fIn.getFileName().toString() ;
//			final String fileOk = fOk == null ? "" : fOk.getFileName().toString() ;
//			final String fileNg = fNg == null ? "" : fNg.getFileName().toString() ;

			final String fileIn = FileUtil.pathFileName( fIn ) ;		// 7.2.9.4 (2020/11/20) Path.getFileName().toString()
			final String fileOk = FileUtil.pathFileName( fOk ) ;		// 7.2.9.4 (2020/11/20) Path.getFileName().toString()
			final String fileNg = FileUtil.pathFileName( fNg ) ;		// 7.2.9.4 (2020/11/20) Path.getFileName().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 );
		}
	}
}
