/*
 * 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.util.Arrays;
import java.util.List;
import java.util.Set;										// 7.2.5.0 (2020/06/01)
import java.util.HashSet;									// 7.2.5.0 (2020/06/01)
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentHashMap;

import java.util.concurrent.ScheduledFuture;				// 7.2.5.0 (2020/06/01)
import java.util.concurrent.ScheduledExecutorService;		// 7.2.5.0 (2020/06/01)
import java.util.concurrent.Executors;						// 7.2.5.0 (2020/06/01)
import java.util.concurrent.TimeUnit;						// 7.2.5.0 (2020/06/01)

import org.opengion.fukurou.system.HybsConst;				// 7.2.5.0 (2020/06/01)

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

/**
 * MainProcess は、単独で使用する ファイル取込システムのメインクラスです。
 *
 *<pre>
 * このクラスのmainメソッドから起動します。
 * コマンドラインを処理することで、各種処理を実行します。
 *
 *</pre>
 *
 * @og.rev 7.0.0.0 (2017/07/07) 新規作成
 * @og.rev 7.2.5.0 (2020/06/01) TomcatのServletContextListenerから実行できるように修正
 *
 * @version  7.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK1.8,
 */
// public final class MainProcess {
public final class MainProcess implements Runnable {
	private static final XLogger LOGGER= XLogger.getLogger( MainProcess.class.getSimpleName() );	// ログ出力

	/** 7.2.5.0 (2020/06/01) エラーの場合、リロードするが、その待機時間 {@value}(秒) */
	public static final long WAIT_TIME = 30 * 1000L;

	private static final ScheduledExecutorService	scheduler = Executors.newScheduledThreadPool(5);	// 1.5.0 (2020/04/01)
	private static final Set<ScheduledFuture<?>>	futureSet = new HashSet<>();

	/** 7.2.5.0 (2020/06/01) 開始しているかどうかを確認するための状態変数 */
	private static volatile boolean isStart ;

	/** 7.2.5.0 (2020/06/01) MainProcess は、シングルインスタンスとして扱います。 */
	private static MainProcess mainPrcs ;

	private final ConcurrentMap<String,FileExec> execMap = new ConcurrentHashMap<>();		// ｷｰは、systemId + rsrv_no

	private boolean isFirst = true;		// 最初だけ、FGYKAN=3:実行中 を検索する必要がある。
	private int cnt ;

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) シングルインスタンス
	 */
//	public MainProcess() { super(); }			// 必要ないが、とりあえず。
	private MainProcess() {
		LOGGER.info( () -> "MainProcess Start! " );
	}

	/**
	 * MainProcess は、シングルインスタンスです。
	 *
	 * 既存のインスタンスか、新しいインスタンスを作成して返します。
	 * serverフォルダ は必須です。
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) シングルインスタンス
	 *
	 * @return	新しいインスタンス または、既存のインスタンス
	 */
	synchronized public static MainProcess getInstance() {
		if( mainPrcs == null ) {
			mainPrcs = new MainProcess();
		}

		return mainPrcs;
	}

	/**
	 * 開始処理を行います。
	 *
	 * 内部で自身のインスタンスを作成して、ScheduledExecutorService　で繰り返し実行します。
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) シングルインスタンス
	 */
	synchronized public static void start() {
		try {
			if( futureSet.isEmpty() ) {							// 何度でも実行できるように
				final MainProcess mainPrcs = getInstance();

				// ********** 【初期値定義】 **********
				final String loopStr = HybsConst.getenv( "loop"  );	// スキャンするインターバル(秒)
				final long   loopSec = loopStr == null || loopStr.isEmpty() ? 10L : Long.parseLong( loopStr );		// スキャンするインターバル(秒)

		//		// 一定時間ごとに処理を実行					  開始ﾀｽｸ  , 初回遅延 , 繰返間隔 , スケール(秒)
		//		futureSet.add( scheduler.scheduleAtFixedRate( mainPrcs , 0        , loopSec  , TimeUnit.SECONDS ) );

				// エラー時に繰り返し間隔より長く待機させる。
				// 処理の完了を待ってから一定時間待機			 開始ﾀｽｸ  , 初回遅延 , 待機時間 , スケール(秒)
				futureSet.add( scheduler.scheduleWithFixedDelay( mainPrcs , 0        , loopSec  , TimeUnit.SECONDS ) );

				isStart = true;
			}
		}
		catch( final Throwable th ) {
			// MSG0021 = 予期せぬエラーが発生しました。\n\tﾒｯｾｰｼﾞ=[{0}]
			final String errMsg = "MainProcess#start" ;
			LOGGER.warning( th , "MSG0021" , errMsg );

			shutdown( false );		// エラーなので再実行できるようにしておきます。
		}
	}

	/**
	 * 終了処理を行います。
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) シングルインスタンス
	 *
	 * @param	flag 完全終了時は true を設定する。
	 */
	synchronized public static void shutdown( final boolean flag ) {
		LOGGER.info( () -> "MainProcess Shutdown Starting ..." );
		isStart = false;

		futureSet.forEach( fu -> fu.cancel( true ) );		// 実行中のタスクに割り込む
		futureSet.clear();									// 初期化しておく

		if( flag ) {
			scheduler.shutdownNow();						// trueの場合、再実行できなくなる。
		}

		if( mainPrcs != null ) {
			// 必要ないかもしれませんが、正常終了させます。
			mainPrcs.watchStop();
		}
		mainPrcs = null;

		LOGGER.info( () -> "MainProcess Shutdown Complete." );
	}

	/**
	 * MainProcess の処理が起動しているかどうかを返します。
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) 新規追加
	 *
	 * @return	true:起動中/false:停止中
	 */
	public static boolean isStarted() {
		return isStart ;
	}

	/**
	 * 時間起動のタスクオブジェクトを起動します。
	 *
	 * コマンドリストは、予約番号,種別,号機指定,雛形ファイル,開始日時,実行間隔,終了日時,経過終了間隔,パラメータMAP を
	 * 文字列として順番に持っています。
	 * リストの数が、少ない場合は、それぞれ、初期値が使用されます。
	 * 最低、コマンド種別は、必要です。
	 *
	 * @param	cmndLine	CommandLineオブジェクト
	 */
//	public void startTask( final CommandLine cmndLine ) {
	private void startTask( final CommandLine cmndLine ) {
//		// 同一予約番号の過去の依頼は停止しておきます。
//		final int yoyakuNo = Integer.parseInt( cmndLine.getValue( GE70.RSRV_NO ) );
//		stopTask( yoyakuNo );										// 一旦、予約を解除します。

		// タスクオブジェクトの起動前に、一旦過去の依頼は停止しておきます。
		final String systemId	= cmndLine.getValue( GE70.SYSTEM_ID );		// ｼｽﾃﾑID
		final String rsrvNo		= cmndLine.getValue( GE70.RSRV_NO );		// 予約番号
		final String mapKey		= systemId + "_" + rsrvNo ;
		stopTask( mapKey );													// 一旦、予約を解除します。

		// ※ 取込予約ﾌﾗｸﾞ(FGYKAN)は、DB検索時に、1:登録 4:停止 しか、来ません。
		final String fgkan = cmndLine.getValue( GE70.FGYKAN );		// 取込予約ﾌﾗｸﾞ 1:登録 2:済 3:実行中 4:停止
		if( "1".equals( fgkan ) || "3".equals( fgkan ) ) {			// 取込予約ﾌﾗｸﾞ(FGYKAN)が、1:登録 3:実行中 以外は、過去の依頼を停止した時点で、終了します。
			final FileExec fExec = new FileExec( cmndLine );

			LOGGER.info( () -> "startTask: yoyakuNo=[" + mapKey + "]" );

			fExec.watchStart();
			execMap.put( mapKey,fExec );
		}
		else {
			LOGGER.warning( () -> "【WARNING】startTask: yoyakuNo=[" + mapKey + "] , fgkan=[" + fgkan + "]" );		// 6.8.1.5 (2017/09/08)
		}
	}

	/**
	 * 時間起動のタスクオブジェクトをキャンセルします。
	 *
	 * @param	mapKey		ｺﾏﾝﾄﾞ予約番号時のｷｰﾜｰﾄﾞ
	 */
	private void stopTask( final String mapKey ) {
		final FileExec	fExec = execMap.remove( mapKey );		// 取り消しなので、Mapから削除します。
		if( fExec != null ) {									// 完了(正常終了、例外、取り消し)以外は、キャンセルします。
			fExec.watchStop();
		}

		LOGGER.info( () -> "stopTask: yoyakuNo=[" + mapKey + "]" );
	}

	/**
	 * すべての成形機のセッションフォルダの監視を終了します。
	 *
	 */
	public void watchStop() {
		execMap.forEach( (no,fExec) -> fExec.watchStop() );
	}

	/**
	 * Runnableインターフェースのrunメソッドです。
	 *
	 * ScheduledExecutorService で繰り返し実行させるので、Throwable 全てのを拾う。
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) TomcatのServletContextListenerから実行できるように修正します。
	 */
	@Override
	public void run() {
		try {
			final List<CommandLine> cmndList = CommandLine.dbCommand( isFirst );
			cmndList.forEach( cmndLine -> startTask( cmndLine ) );
			isFirst = false;
			System.out.println( StringUtil.getTimeFormat( "yyyy/MM/dd HH:mm:ss [" + (cnt++) + "]" ) );	// 6.8.1.5 (2017/09/08)
		}
		catch( final Throwable th ) {
			// MSG0021 = 予期せぬエラーが発生しました。\n\tﾒｯｾｰｼﾞ=[{0}]
			final String errMsg = "MainProcess#run" ;
			LOGGER.warning( th , "MSG0021" , errMsg );

			watchStop();

			// エラーの場合は、少し待って終了します。
			try{ Thread.sleep( WAIT_TIME ); } catch( final InterruptedException ex ){}
		}
	}

//	/** main メソッドから呼ばれる ヘルプメッセージです。 {@value} */
//	public static final String USAGE = "Usage: java org.opengion.fukurou.fileexec.MainProcess [-LOOP n(秒)] [-help]" ;

//	/**
//	 * Euromapのミドルウエア(成形条件管理システム)のメインクラスを起動します。
//	 *
//	 * ベースフォルダを、指定しない場合は、アプリケーションの起動フォルダになります。(./)
//	 *
//	 * {@value #USAGE}
//	 *
//	 * @param	args 引数配列
//	 */
//	public static void main( final String[] args ) {
//		// ********** 【整合性チェック】 **********
//
//		// ********** 【引数定義】 **********
//		int     loopTime	= 10;							// スキャンするインターバル(秒)
//
//		// ********** 【引数処理】 **********
//		for( int i=0; i<args.length; i++ ) {
//			final String arg = args[i];
//
//			if( "-help" .equalsIgnoreCase( arg ) ) { System.out.println( USAGE ); return ; }
//			else if( "-LOOP" .equalsIgnoreCase( arg ) ) {
//				loopTime = Integer.parseInt( args[++i] );		// ひとつ進める
//			}
//			else {
//				// MSG2011 = コマンドリストの操作に失敗ました。形式をご確認ください。[{0}]
//				throw MsgUtil.throwException( "MSG2011" , Arrays.toString( args ) );
//			}
//		}
//
//		// ********** 【本体処理】 **********
//		final MainProcess mainPrcs = new MainProcess();
//
//		// ほぼ、暫定対応。無限ループで、DBを監視します。
//		boolean isFirst = true;		// 最初だけ、FGYKAN=3:実行中 を検索する必要がある。
//		int cnt = 0;
//		while( true ) {
//			try {
//				final List<CommandLine> cmndList = CommandLine.dbCommand( isFirst );
//				cmndList.forEach( cmndLine -> mainPrcs.startTask( cmndLine ) );
//				try{ Thread.sleep( loopTime * 1000L ); } catch( final InterruptedException ex ){}
//				isFirst = false;
//				System.out.println( StringUtil.getTimeFormat( "yyyyMMdd HH:mm:ss [" + (cnt++) + "]" ) );	// 6.8.1.5 (2017/09/08)
//			}
//			catch( final Throwable th ) {
//				// MSG0021 = 予期せぬエラーが発生しました。\n\tﾒｯｾｰｼﾞ=[{0}]
//				MsgUtil.errPrintln( th , "MSG0021" , th.getMessage() );
//				try{ Thread.sleep( 10 * loopTime * 1000L ); } catch( final InterruptedException ex ){}
//
//				mainPrcs.watchStop();
//			}
//		}
//	}

	/**
	 * ファイル取込システムのメインクラスを起動します。
	 *
	 * <pre>
	 * システムプロパティー定義(例：初期値)
	 *
	 *　[-Dloop="10"]	: データベースの状態をチェックする間隔(秒)。初期値は、10秒です。
	 * </pre>
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) TomcatのServletContextListenerから実行できるように修正します。
	 *
	 * @param	args 引数配列
	 */
	public static void main( final String[] args ) {
		try {
			MainProcess.start();
		}
		catch( final Throwable th ) {
			// MSG0021 = 予期せぬエラーが発生しました。\n\tﾒｯｾｰｼﾞ=[{0}]
			final String errMsg = "MainProcess#main" ;
			LOGGER.warning( th , "MSG0021" , errMsg );

			MainProcess.shutdown( true );			// 完全終了処理
			System.exit( 1 );						// 強制終了します。
		}

		// 仮想マシンのシャットダウン・フックを登録
		final Thread shutdownHook = new Thread( "MainProcess" ) {
			/**
			 * シャットダウン・フックの実行メソッドです。
			 */
			@Override
			public void run() {
				MainProcess.shutdown( true );
			}
		};
		Runtime.getRuntime().addShutdownHook( shutdownHook );
	}
}
