/*
 * 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.Collections;								// 7.2.9.4 (2020/11/20)
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 java.util.concurrent.atomic.AtomicBoolean;			// 7.2.9.4 (2020/11/20) volatile boolean の代替え

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 ScheduledExecutorService	SCHEDULER = Executors.newScheduledThreadPool(5);	// 7.2.9.4 (2020/11/20) 1.5.0 (2020/04/01)
//	private static final Set<ScheduledFuture<?>>	futureSet = new HashSet<>();
	private static final Set<ScheduledFuture<?>>	FUTURE_SET = Collections.newSetFromMap( new ConcurrentHashMap<ScheduledFuture<?>, Boolean>() );	// 7.2.9.4 (2020/11/20)

	/** 7.2.5.0 (2020/06/01) 開始しているかどうかを確認するための状態変数 */
//	private static volatile boolean isStart ;
	private static final AtomicBoolean IS_START = new AtomicBoolean();			// 7.2.9.4 (2020/11/20) volatile boolean の代替え

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

	// 7.2.9.4 (2020/11/20) staticレベルのロック
	private static final Object STATIC_LOCK = new Object();

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

	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) シングルインスタンス
	 * @og.rev 7.2.9.4 (2020/11/20) staticレベルのロック
	 *
	 * @return	新しいインスタンス または、既存のインスタンス
	 */
//	synchronized public static MainProcess getInstance() {
	public static MainProcess getInstance() {
		synchronized( STATIC_LOCK ) {
			if( mainPrcs == null ) {
				mainPrcs = new MainProcess();
			}
		}

		return mainPrcs;
	}

	/**
	 * 開始処理を行います。
	 *
	 * 内部で自身のインスタンスを作成して、ScheduledExecutorService　で繰り返し実行します。
	 *
	 * @og.rev 7.2.5.0 (2020/06/01) シングルインスタンス
	 * @og.rev 7.2.9.4 (2020/11/20) static final の大文字化
	 * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え。
	 * @og.rev 7.2.9.4 (2020/11/20) staticレベルのロック
	 */
//	synchronized public static void start() {
	public static void start() {
		try {
//			if( futureSet.isEmpty() ) {							// 何度でも実行できるように
			if( FUTURE_SET.isEmpty() ) {						// 何度でも実行できるように
//				final MainProcess mainPrcs = getInstance();
				final MainProcess localPrcs ;
				synchronized( STATIC_LOCK ) {					// 7.2.9.4 (2020/11/20) staticレベルのロック
					localPrcs = getInstance();
				}

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

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

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

//				isStart = true;
				IS_START.set( true );		// 7.2.9.4 (2020/11/20)
			}
		}
		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) シングルインスタンス
	 * @og.rev 7.2.9.4 (2020/11/20) static final の大文字化
	 * @og.rev 7.2.9.4 (2020/11/20) PMD:volatile boolean の代替え。
	 * @og.rev 7.2.9.4 (2020/11/20) staticレベルのロック
	 *
	 * @param	flag 完全終了時は true を設定する。
	 */
//	synchronized public static void shutdown( final boolean flag ) {
	public static void shutdown( final boolean flag ) {
		LOGGER.info( () -> "MainProcess Shutdown Starting ..." );
//		isStart = false;
		IS_START.set( false );		// 7.2.9.4 (2020/11/20)

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

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

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

		synchronized( STATIC_LOCK ) {						// 7.2.9.4 (2020/11/20) staticレベルのロック
			if( mainPrcs != null ) {
				// 必要ないかもしれませんが、正常終了させます。
				mainPrcs.watchStop();
			}
			mainPrcs = null;
		}

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

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

	/**
	 * 時間起動のタスクオブジェクトを起動します。
	 *
	 * コマンドリストは、予約番号,種別,号機指定,雛形ファイル,開始日時,実行間隔,終了日時,経過終了間隔,パラメータMAP を
	 * 文字列として順番に持っています。
	 * リストの数が、少ない場合は、それぞれ、初期値が使用されます。
	 * 最低、コマンド種別は、必要です。
	 *
	 * @param	cmndLine	CommandLineオブジェクト
	 */
	private void startTask( final CommandLine cmndLine ) {
		// タスクオブジェクトの起動前に、一旦過去の依頼は停止しておきます。
		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:実行 の場合のみ起動する。
		final String fgkan = cmndLine.getValue( GE70.FGYKAN );				// 取込予約ﾌﾗｸﾞ 1:実行 2:停止
		if( "1".equals( fgkan ) ) {											// 1:実行 以外は、先に停止されている。
			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	// Runnable
	public void run() {
		try {
			final List<CommandLine> cmndList = CommandLine.dbCommand();
			cmndList.forEach( cmndLine -> startTask( cmndLine ) );
			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 );

			shutdown( true );		// 完全終了

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

// 	/**
// 	 * ファイル取込システムのメインクラスを起動します。
// 	 *
// 	 * <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 );
// 	}
}
