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

import org.opengion.hayabusa.common.HybsSystem;
import org.opengion.hayabusa.common.HybsSystemException;
import org.opengion.hayabusa.report.GE50Access;
import org.opengion.hayabusa.report.ReportProcessing;
import org.opengion.fukurou.system.LogWriter;
import org.opengion.fukurou.util.StringUtil;
import org.opengion.fukurou.system.ThrowUtil;						// 6.4.2.0 (2016/01/29)
import org.opengion.fukurou.util.HybsTimerTask;
import org.opengion.fukurou.db.ApplicationInfo;
import org.opengion.fukurou.db.DBUtil;
import static org.opengion.fukurou.system.HybsConst.BUFFER_MIDDLE;	// 6.1.0.0 (2014/12/26) refactoring

import java.util.concurrent.ConcurrentMap;							// 6.4.3.3 (2016/03/04)
import java.util.concurrent.ConcurrentHashMap;						// 6.4.3.1 (2016/02/12) refactoring
import java.util.Date;

/**
 * 【レポート出力】帳票要求テーブルを監視して、帳票処理プログラムを呼び出します。
 * このクラスは、HybsTimerTask を継承した タイマータスククラスです。
 * startDaemon() がタイマータスクによって、呼び出されます。
 *
 * @og.rev 4.3.4.4 (2009/01/01) プラグイン化
 * @og.group デーモン
 *
 * @version  4.0
 * @author   Kazuhiko Hasegawa
 * @since    JDK5.0,
 */
public class Daemon_Report extends HybsTimerTask {
	/** このプログラムのVERSION文字列を設定します。	{@value} */
	private static final String VERSION = "6.4.3.3 (2016/03/04)" ;

	/** コネクションにアプリケーション情報を追記するかどうか指定 */
	public static final boolean USE_DB_APPLICATION_INFO  = HybsSystem.sysBool( "USE_DB_APPLICATION_INFO" ) ;

	// 3.7.0.0 (2005/01/18) 複数同時デーモン時の、同一帳票IDは処理できない。
	// 実行中の帳票ID をセットする、static Map
	/** 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。  */
	private static final ConcurrentMap<String,String> USE_LISTID = new ConcurrentHashMap<>();		// 6.4.3.1 (2016/02/12)

	// 3.8.5.0 (2006/03/06) EXCELをオープンするファイル名に要求番号を使う場合は、true
	private static final boolean EXCEL_NAME_USE_YKNO = HybsSystem.sysBool( "REPORT_EXCEL_NAME_USE_YKNO" );

//	// 5.2.0.0 (2010/09/01) Ver4互換モード対応
//	// 6.9.5.0 (2018/04/23) VER4_COMPATIBLE_MODE 廃止
//	private static final String OUT_FILE = HybsSystem.sysBool( "VER4_COMPATIBLE_MODE" ) ? "OUTFILE" : "OUT_FILE";
//	private static final String OUT_DIR = HybsSystem.sysBool( "VER4_COMPATIBLE_MODE" ) ? "OUTDIR" : "OUT_DIR";

	// 3.7.0.0 (2005/01/18) GE53 に DMN_GRP追加による、検索条件の変更
	// 5.1.0.0 (2009/11/04) OUTDIR ⇒ OUT_DIR , OUTFILE ⇒ OUT_FILE
	// 5.2.0.0 (2010/09/01) Ver4互換モード対応

	// 5.9.0.1 (2015/09/11)
	// 6.9.5.0 (2018/04/23) VER4_COMPATIBLE_MODE 廃止
	private static final String GE50_SELECT =
//		"SELECT A.SYSTEM_ID,A.YKNO,A.GROUPID,A.LISTID,A.JOKEN,A."+OUT_DIR+",A."+OUT_FILE+",A.USRSET" +
		"SELECT A.SYSTEM_ID,A.YKNO,A.GROUPID,A.LISTID,A.JOKEN,A.OUT_DIR,A.OUT_FILE,A.USRSET" +
		" FROM GE50 A,GE53 B,GE54 C" +
		" WHERE A.SYSTEM_ID = B.SYSTEM_ID" +
		" AND A.JOKEN = B.JOKEN" +
		" AND A.FGJ = '1'" +
		" AND B.FGJ = '1'" +
		" AND A.FGKAN = '1'" +
		" AND A.SYSTEM_ID = C.SYSTEM_ID" +
		" AND A.LISTID = C.LISTID"+
		" AND C.FGJ = '1'";

	private final String DBID = HybsSystem.sys( "RESOURCE_DBID" );		// 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対応

	// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	private ApplicationInfo appInfo	;
	private ReportProcessing  rc	;

	private static final int LOOP_COUNTER = 24;		// カウンタを24回に設定
	private int		loopCnt		;		// 3.5.4.9 (2004/02/25) メッセージ出力時のループカウント

	// 6.3.9.0 (2015/11/06) Variables should start with a lowercase character(PMD)
	private String	geSELECT	;		// 3.7.0.0 (2005/01/18) GE53 に DMN_GRP追加による、検索条件の変更
	private String	prtID		;		// 3.8.5.0 (2006/03/06) プリンタIDが、引数から渡される場合の対応
	private String	dmnNAME		;		// 3.8.5.0 (2006/03/06) デーモン名を設定します。
	private boolean debug		;		// 3.8.5.0 (2006/03/06) デバッグ用のフラグを追加します。小文字に修正
	private boolean running		= true;	// 3.8.5.3 (2006/06/30) タイマータスクがキャンセルされた場合の停止フラグ

	/**
	 * デフォルトコンストラクター
	 *
	 * @og.rev 6.4.2.0 (2016/01/29) PMD refactoring. Each class should declare at least one constructor.
	 */
	public Daemon_Report() { super(); }		// これも、自動的に呼ばれるが、空のメソッドを作成すると警告されるので、明示的にしておきます。

	/**
	 * このタイマータスクによって初期化されるアクションです。
	 * パラメータを使用した初期化を行います。
	 *
	 * @og.rev 3.6.0.7 (2004/11/12) 新規追加
	 * @og.rev 3.7.0.0 (2005/01/18) 帳票定義マスタ(GE54)を参照するように仕様変更
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 4.0.1.0 (2007/12/19) GE50の検索順をシステムリソースで設定可能にする
	 * @og.rev 5.7.3.2 (2014/02/28) GE53の検索条件修正
	 */
	@Override
	public void initDaemon() {

		// 3.7.0.0 (2005/01/18) GE50, GE54 の USRUPD に、デーモン名をセットします。
		// 3.7.1.1 (2005/05/31) GE50Access を使用して、DB登録を行います。

		// 3.8.5.0 (2006/03/06) デーモン名を設定します。
		dmnNAME = getName();					// 6.3.9.0 (2015/11/06)

		final StringBuilder buf = new StringBuilder( BUFFER_MIDDLE );
		buf.append( GE50_SELECT );

		// SYSTEM_ID は、指定がなければ、全件検索対象になります。
		final String systemId = getValue( "SYSTEM_ID" );
		if( ! StringUtil.isNull( systemId ) ) {
			buf.append( " AND A.SYSTEM_ID='" ).append( systemId ).append( '\'' );
		}

		// 3.8.5.0 (2006/03/06) DMN_GRP は、必須指定
		// 5.1.9.0 (2010/08/01) Avoid if(x != y) ..; else ..;
		final String dmnGroup = getValue( "DMN_GRP" );
		if( StringUtil.isNull( dmnGroup ) ) {
			final String errMsg = "デーモングループは必須指定です。" ;
			throw new HybsSystemException( errMsg );
		}
		else {
			buf.append( " AND B.DMN_GRP='" ).append( dmnGroup ).append( '\'' );
		}

		// 3.8.5.0 (2006/03/06) GE50 の検索条件に、MODBASE と MODNO を使用する。
		// デーモン起動時に 最大数(MODBASE)と余り番号(MODNO)を渡します。
		// 最大数(MODBASE)を元に、検索時に、YKNOの余りを求め、これが、
		// 引数の余り番号(MODNO)と一致する場合のみ、処理をします。
		final String modBase	= StringUtil.nval( getValue( "MODBASE" ),null );
		final String modNo	= StringUtil.nval( getValue( "MODNO" ),null );
		if( modBase != null && modNo != null ) {
			buf.append( " AND MOD(A.YKNO," ).append( modBase ).append( ")=" ).append( modNo );
		}

		// 3.8.5.0 (2006/03/06) PRTID が指定されていれば、その値を使用する。なければ NULL
		prtID = StringUtil.nval( getValue( "PRTID" ), null );				// 6.3.9.0 (2015/11/06)

		// 3.8.5.0 (2006/03/06) PRT_GRP が指定されていれば、振分条件検索時に使用する。
		final String prtGgrp = getValue( "PRT_GRP" );
		if( ! StringUtil.isNull( prtGgrp ) ) {
			buf.append( " AND B.PRTID='" ).append( prtGgrp ).append( '\'' ); // 5.7.3.2 (2014/02/28) GE53ではPRTIDにPRI_GRPが指定されている
		}

		buf.append( " ORDER BY " )				// 4.0.1.0 (2007/12/19)
			.append( HybsSystem.sys( "REPORT_DAEMON_ORDER_BY" ) );

		geSELECT = buf.toString() ;				// 6.3.9.0 (2015/11/06)

		// 3.8.5.0 (2006/03/06) デバッグ用のフラグを追加します。
		debug = StringUtil.nval( getValue( "DEBUG" ),debug ) ;

		if( debug ) {
			System.out.println( "DMN_NAME=[" + dmnNAME + "]" );				// 6.3.9.0 (2015/11/06)
			System.out.println( "MODNO=[" + modNo + "]" );
			System.out.println( "QUERY=[" + geSELECT + "]" );
			System.out.println( "EXCEL_NAME_USE_YKNO=[" + EXCEL_NAME_USE_YKNO + "]" );
		}

		// 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
		if( USE_DB_APPLICATION_INFO ) {
			appInfo = new ApplicationInfo();
			// ユーザーID,IPアドレス,ホスト名
			appInfo.setClientInfo( systemId,HybsSystem.HOST_ADRS,HybsSystem.HOST_NAME );
			// 画面ID,操作,プログラムID
			appInfo.setModuleInfo( "ReportDaemon",prtID,dmnNAME );			// 6.3.9.0 (2015/11/06)
		}
		else {
			appInfo = null;
		}
	}

	/**
	 * タイマータスクのデーモン処理の開始ポイントです。
	 *
	 * @og.rev 3.5.2.0 (2003/10/20) vals 変数を、ローカルに移動
	 * @og.rev 3.5.4.8 (2004/02/23) タイムスタンプを、１０回に１回とする。
	 * @og.rev 3.6.0.0 (2004/09/17) タイムスタンプを、２４回に１回とする。
	 * @og.rev 3.6.1.0 (2005/01/05) tyr ～ catch を Exception から Throwable に変更。
	 * @og.rev 3.7.0.0 (2005/01/18) 複数同時デーモンでも、同一帳票IDは処理できない為、スキップします。
	 * @og.rev 3.7.0.4 (2005/03/18) エラー発生時に vals が null なら、HybsSystemException を throw する。
	 * @og.rev 3.7.1.1 (2005/05/31) GE50Access を使用して、DB登録を行います。
	 * @og.rev 3.8.0.0 (2005/06/07) EXCEL 取込時の完成フラグは、FG_DBIN とします。
	 * @og.rev 3.8.0.0 (2005/06/07) rc.execute() 実行結果を boolean ではなく、文字列(FGKAN_XX)で返します。
	 * @og.rev 3.8.5.0 (2006/03/06) EXCELファイル名に要求番号を使う場合は、帳票IDでの排他制御は不要。
	 * @og.rev 3.8.5.2 (2006/05/31) DEBUG 情報の強化
	 * @og.rev 3.8.6.0 (2006/06/30) タイマータスクがキャンセルされた場合の処理を追加(running フラグ)
	 * @og.rev 3.8.7.0 (2006/12/15) アクセスログ取得の為,ApplicationInfoオブジェクトを設定
	 * @og.rev 5.3.0.0 (2010/12/01) エラーハンドリングを修正
	 * @og.rev 5.5.5.1 (2012/08/07) リソース系DBID 付け忘れ対策
	 * @og.rev 5.7.0.4 (2013/11/29) listIdをGE50Accessに渡すようにする
	 * @og.rev 6.4.2.0 (2016/01/29) StringUtil#stringStackTrace(Throwable) を、ThrowUtil#ogStackTrace(String,Throwable) に置き換え。
	 * @og.rev 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
	 * @og.rev 6.4.3.3 (2016/03/04) Map#putIfAbsent で対応する。
	 */
	@Override
	protected void startDaemon() {
		if( loopCnt % LOOP_COUNTER == 0 ) {
			loopCnt = 1;
			System.out.println();
			System.out.print( toString() + " " + new Date()  + " " );
		}
		else {
			System.out.print( "." );
			loopCnt++ ;
		}

		// 3.7.1.1 (2005/05/31) GE50Access を使用して、DB登録を行います。
		GE50Access ge50 = null ;

		String listId = null;				// 6.4.3.1 (2016/02/12) vals をローカル化して、listId を大域化します。
		try {
			final String[][] vals = DBUtil.dbExecute( geSELECT,null,appInfo, DBID );		// 6.3.9.0 (2015/11/06)
			if( vals != null && vals.length > 0 ) {

				// 3.7.1.1 (2005/05/31) GE50Access を使用して、DB登録を行います。
				// 毎回 オブジェクトを構築します。登録日付が初期化されます。
				ge50 = new GE50Access( null,null,dmnNAME ) ;			// 6.3.9.0 (2015/11/06)

				if( rc == null ) { rc = new ReportProcessing(); }
				// 3.8.6.0 (2006/06/30) タイマータスクがキャンセルされた場合の処理を追加(running フラグ)
				for( int row=0; running && row<vals.length; row++ ) {	// 6.4.3.1 (2016/02/12)
					// 3.7.0.0 (2005/01/18) 使用中の帳票IDのチェックと、使用時の登録
					final String systemId = vals[row][0] ;
					final String ykno     = vals[row][1] ;
					listId				  = vals[row][3] ;				// 6.4.3.1 (2016/02/12)
					// 3.8.5.0 (2006/03/06) EXCELファイル名に要求番号を使う場合は、帳票IDでの排他制御は不要。
					if( ! EXCEL_NAME_USE_YKNO ) {
						// 6.4.3.3 (2016/03/04) Map#putIfAbsent で対応します。
						// Map#putIfAbsent ： 戻り値は、以前の値。追加有り、置換なし、削除なし
						if( USE_LISTID.putIfAbsent( listId,"DUMMY" ) != null ) { continue; }	// 使用中なら、飛ばす。

						// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
					}

					// デバッグ情報を出力します。
					if( debug ) {
						System.out.println();
						System.out.print( "[" + dmnNAME + "]:[" + ykno + "] START = " );			// 6.3.9.0 (2015/11/06)
						System.out.println( new Date() );
					}

					// 3.7.1.1 (2005/05/31) GE50Access を使用して、DB登録を行います。
					ge50.setSystemId( systemId );
					ge50.setYkno( ykno );
					ge50.updateGE50( GE50Access.FG_RUN );

					ge50.setListId( listId ); // 5.7.0.4 (2013/11/29)

					// system_id,ykno,groupid,listid,joken,outdir,outfile,usrset
					rc.setSystemId( systemId	);
					rc.setYkno(		ykno		);
					rc.setGroupId(	vals[row][2]);
					rc.setListId(	listId		);
					rc.setJoken(	vals[row][4]);
					rc.setPrtId(	prtID		);		// 6.3.9.0 (2015/11/06)
					rc.setOutDir(	vals[row][5]);
					rc.setOutFile(	vals[row][6]);
					rc.setDebug(	debug		);		// 3.8.5.0 (2006/03/06) DEBUGを追加。

	 				// 3.8.0.0 (2005/06/07) 実行結果を boolean ではなく、文字列(FGKAN_XX)で返します。
					String fgkan = rc.execute();
					if( fgkan == null ) {
						fgkan = GE50Access.FG_ERR2 ;
						final String errMsg = rc.getErrMsg();
						// 3.7.1.1 (2005/05/31) GE50Access を使用して、DB登録を行います。
						ge50.insertErrorGE56( errMsg );
					}

					// 3.7.1.1 (2005/05/31) GE50Access を使用して、DB登録を行います。
					ge50.updateGE50( fgkan );

					rc.clear();
					// 3.8.5.0 (2006/03/06) EXCELファイル名に要求番号を使う場合は、帳票IDでの排他制御は不要。
					if( ! EXCEL_NAME_USE_YKNO ) {
						// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
						USE_LISTID.remove( listId );

					}

					// デバッグ情報を出力します。
					if( debug ) {
						System.out.println();
						System.out.print( "[" + dmnNAME + "]:[" + ykno + "] END = " );			// 6.3.9.0 (2015/11/06)
						System.out.println( new Date() );
					}
				}
			}
		}
	 	// 5.3.0.0 (2010/12/01) エラーハンドリングを修正
		catch( final Throwable ex ) {		// 3.6.1.0 (2005/01/05)
			rc = null;

			final String errMsg = ThrowUtil.ogStackTrace( ex ) ;								// 6.4.2.0 (2016/01/29)
			System.out.println( errMsg );
			LogWriter.log( errMsg );

			// 6.9.7.0 (2018/05/14) PMD These nested if statements could be combined
//			if( ! EXCEL_NAME_USE_YKNO ) {
//				// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
//				if( listId != null ) { USE_LISTID.remove( listId ); }
//			}
			if( ! EXCEL_NAME_USE_YKNO && listId != null ) {
				// 6.4.3.1 (2016/02/12) PMD refactoring. HashMap → ConcurrentHashMap に置き換え。
				USE_LISTID.remove( listId );
			}

			// 3.7.1.1 (2005/05/31) GE50Access を使用して、DB登録を行います。
			if( ge50 != null ) {
				ge50.insertErrorGE56( errMsg );
				ge50.updateGE50( GE50Access.FG_ERR1 );
			}
			// 6.4.3.1 (2016/02/12) 条件は異なるが、ほぼ、近いはず
			else if( listId == null ) {
				final String errMsg2 = "SQL=[" + geSELECT + "] , appInfo=[" + appInfo + "] , DBID=[" + DBID + "]";
				System.out.println( errMsg2 );
				LogWriter.log( errMsg2 );
				throw new HybsSystemException( errMsg + errMsg2 , ex );
			}

		}
	}

	/**
	 * このタイマータスクのcancel() メソッドをオーバーライドします。
	 * HybsTimerTaskManager#cancelTask( int ) を実行します。
	 *
	 * @og.rev 3.8.5.3 (2006/06/30) 新規追加
	 *
	 * @return	スケジュールされている 1 回以上実行されない場合に true
	 * @see java.util.TimerTask#cancel()
	 */
	@Override
	public boolean cancel() {
		running = false;
		return super.cancel();
	}
}
